#!/usr/bin/env node import { Command } from 'commander'; import dotenv from 'dotenv'; import path from 'path'; import { download, downloadVideo, downloadPlaylist, getInfo } from './services/youtube.js'; import { transcribeFile, transcribeAndSave, transcribeMultiple, getAvailableModels } from './services/transcription.js'; // Load environment variables dotenv.config(); const program = new Command(); program .name('ytmp3') .description('Download YouTube videos/playlists to MP3 and transcribe them') .version('1.0.0'); // Download command program .command('download ') .alias('dl') .description('Download a YouTube video or playlist as MP3') .option('-o, --output ', 'Output directory', './output') .action(async (url, options) => { try { console.log('Fetching video info...'); const result = await download(url, { outputDir: options.output }); console.log('\n--- Download Complete ---'); if (result.playlistTitle) { console.log(`Playlist: ${result.playlistTitle}`); } console.log(`Downloaded: ${result.successCount}/${result.totalVideos} videos`); result.videos.forEach(v => { if (v.success) { console.log(` ✓ ${v.title}`); } else { console.log(` ✗ ${v.title} - ${v.error}`); } }); } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); } }); // Transcribe command (from existing MP3) program .command('transcribe ') .alias('tr') .description('Transcribe an existing audio file') .option('-l, --language ', 'Language code (e.g., en, fr, zh)') .option('-f, --format ', 'Output format (txt, srt, vtt)', 'txt') .option('-m, --model ', 'Transcription model (gpt-4o-transcribe, gpt-4o-mini-transcribe, whisper-1)', 'gpt-4o-transcribe') .action(async (file, options) => { try { if (!process.env.OPENAI_API_KEY) { console.error('Error: OPENAI_API_KEY not set in environment'); process.exit(1); } console.log(`Transcribing: ${file}`); const result = await transcribeAndSave(file, { language: options.language, responseFormat: options.format === 'txt' ? 'text' : options.format, outputFormat: options.format, model: options.model, }); console.log('\n--- Transcription Complete ---'); console.log(`Model: ${result.model}`); console.log(`Output: ${result.transcriptionPath}`); console.log('\nPreview:'); console.log(result.text.substring(0, 500) + (result.text.length > 500 ? '...' : '')); } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); } }); // Download + Transcribe command program .command('process ') .alias('p') .description('Download and transcribe a YouTube video or playlist') .option('-o, --output ', 'Output directory', './output') .option('-l, --language ', 'Language code for transcription') .option('-f, --format ', 'Transcription format (txt, srt, vtt)', 'txt') .option('-m, --model ', 'Transcription model (gpt-4o-transcribe, gpt-4o-mini-transcribe, whisper-1)', 'gpt-4o-transcribe') .action(async (url, options) => { try { if (!process.env.OPENAI_API_KEY) { console.error('Error: OPENAI_API_KEY not set in environment'); process.exit(1); } // Step 1: Download console.log('Step 1: Downloading...'); const downloadResult = await download(url, { outputDir: options.output }); console.log(`Downloaded: ${downloadResult.successCount}/${downloadResult.totalVideos} videos\n`); // Step 2: Transcribe console.log(`Step 2: Transcribing with ${options.model}...`); const successfulDownloads = downloadResult.videos.filter(v => v.success); const filePaths = successfulDownloads.map(v => v.filePath); const transcribeResult = await transcribeMultiple(filePaths, { language: options.language, responseFormat: options.format === 'txt' ? 'text' : options.format, outputFormat: options.format, model: options.model, }); console.log('\n--- Process Complete ---'); if (downloadResult.playlistTitle) { console.log(`Playlist: ${downloadResult.playlistTitle}`); } console.log(`Downloaded: ${downloadResult.successCount}/${downloadResult.totalVideos}`); console.log(`Transcribed: ${transcribeResult.successCount}/${transcribeResult.totalFiles}`); transcribeResult.results.forEach(r => { if (r.success) { console.log(` ✓ ${path.basename(r.transcriptionPath)}`); } else { console.log(` ✗ ${path.basename(r.filePath)} - ${r.error}`); } }); } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); } }); // Info command program .command('info ') .description('Get info about a YouTube video or playlist') .action(async (url) => { try { const info = await getInfo(url); console.log('\n--- Video/Playlist Info ---'); console.log(`Title: ${info.title}`); console.log(`Type: ${info._type || 'video'}`); if (info._type === 'playlist') { console.log(`Videos: ${info.entries?.length || 0}`); if (info.entries) { info.entries.slice(0, 10).forEach((e, i) => { console.log(` ${i + 1}. ${e.title}`); }); if (info.entries.length > 10) { console.log(` ... and ${info.entries.length - 10} more`); } } } else { console.log(`Duration: ${Math.floor(info.duration / 60)}:${String(info.duration % 60).padStart(2, '0')}`); console.log(`Channel: ${info.channel}`); } } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); } }); program.parse();