mirror of
https://github.com/LuanRT/googlevideo.git
synced 2026-06-19 03:31:37 +00:00
92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
import { EnabledTrackTypes } from 'googlevideo/utils';
|
|
import { promises as fs } from 'node:fs';
|
|
import ffmpeg from 'fluent-ffmpeg';
|
|
|
|
import {
|
|
createOutputStream,
|
|
createStreamSink,
|
|
createSabrStream,
|
|
createMultiProgressBar,
|
|
setupProgressBar
|
|
} from './utils/sabr-stream-factory.js';
|
|
|
|
import type { SabrPlaybackOptions } from 'googlevideo/sabr-stream';
|
|
|
|
const VIDEO_ID = 'hzGmbwS_Drs';
|
|
const OPTIONS: SabrPlaybackOptions = {
|
|
preferWebM: true,
|
|
preferOpus: true,
|
|
videoQuality: '480p',
|
|
audioQuality: 'AUDIO_QUALITY_MEDIUM',
|
|
enabledTrackTypes: EnabledTrackTypes.VIDEO_AND_AUDIO
|
|
};
|
|
|
|
async function cleanupTempFiles(files: string[]) {
|
|
for (const file of files) {
|
|
try {
|
|
await fs.unlink(file);
|
|
} catch (error) {
|
|
console.warn(`Failed to delete temp file ${file}:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function mergeAudioAndVideo(videoTitle: string, audioPath: string, videoPath: string, mergeBar: any): Promise<string> {
|
|
const sanitizedTitle = videoTitle?.replace(/[^a-z0-9]/gi, '_') || 'output';
|
|
const outputPath = `${sanitizedTitle}.webm`;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
mergeBar.update(10);
|
|
|
|
ffmpeg()
|
|
.input(videoPath)
|
|
.input(audioPath)
|
|
.outputOptions([ '-c:v copy', '-c:a copy', '-map 0:v:0', '-map 1:a:0' ])
|
|
.on('progress', (progress) => {
|
|
if (progress.percent) {
|
|
mergeBar.update(Math.min(progress.percent, 99));
|
|
}
|
|
})
|
|
.on('end', () => {
|
|
mergeBar.update(100);
|
|
resolve(outputPath);
|
|
})
|
|
.on('error', (err) => {
|
|
reject(new Error(`Error merging files: ${err.message}`));
|
|
})
|
|
.save(outputPath);
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const progressBars = createMultiProgressBar();
|
|
|
|
try {
|
|
const { streamResults } = await createSabrStream(VIDEO_ID, OPTIONS);
|
|
const { videoStream, audioStream, selectedFormats, videoTitle } = streamResults;
|
|
|
|
const audioOutputStream = createOutputStream(videoTitle, selectedFormats.audioFormat.mimeType!);
|
|
const videoOutputStream = createOutputStream(videoTitle, selectedFormats.videoFormat.mimeType!);
|
|
|
|
const audioBar = setupProgressBar(progressBars, 'audio', selectedFormats.audioFormat.contentLength || 0);
|
|
const videoBar = setupProgressBar(progressBars, 'video', selectedFormats.videoFormat.contentLength || 0);
|
|
const mergeBar = setupProgressBar(progressBars, 'merge');
|
|
|
|
await Promise.all([
|
|
videoStream.pipeTo(createStreamSink(selectedFormats.videoFormat, videoOutputStream.stream, videoBar)),
|
|
audioStream.pipeTo(createStreamSink(selectedFormats.audioFormat, audioOutputStream.stream, audioBar))
|
|
]);
|
|
|
|
await mergeAudioAndVideo(videoTitle, audioOutputStream.filePath, videoOutputStream.filePath, mergeBar);
|
|
await cleanupTempFiles([ audioOutputStream.filePath, videoOutputStream.filePath ]);
|
|
|
|
progressBars.stop();
|
|
console.log(`Download complete! Output saved as "${videoTitle.replace(/[^a-z0-9]/gi, '_')}.webm"`);
|
|
} catch (error) {
|
|
console.error('Download failed:', error);
|
|
progressBars.stop();
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().then(); |