From fecc2d6596966bafe3565b9fe62ee15fb88f38b5 Mon Sep 17 00:00:00 2001 From: kevin Date: Fri, 21 Feb 2025 14:51:51 +0800 Subject: [PATCH] chore: bump version to 0.6.11 --- package.json | 2 +- src/index.mts | 98 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 7a26866..e0a72ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevinwatt/yt-dlp-mcp", - "version": "0.6.10", + "version": "0.6.11", "description": "yt-dlp MCP Server - Download video content via Model Context Protocol", "keywords": [ "mcp", diff --git a/src/index.mts b/src/index.mts index fe07be9..4d6569e 100644 --- a/src/index.mts +++ b/src/index.mts @@ -14,10 +14,10 @@ import * as path from "path"; import { spawnPromise } from "spawn-rx"; import { rimraf } from "rimraf"; -const VERSION = '0.6.10'; +const VERSION = '0.6.11'; /** - * 系統配置 + * System Configuration */ const CONFIG = { MAX_FILENAME_LENGTH: 50, @@ -27,16 +27,16 @@ const CONFIG = { } as const; /** - * 驗證系統配置 - * @throws {Error} 當配置無效時 + * Validate system configuration + * @throws {Error} when configuration is invalid */ async function validateConfig(): Promise { - // 檢查下載目錄 + // Check downloads directory if (!fs.existsSync(CONFIG.DOWNLOADS_DIR)) { throw new Error(`Downloads directory does not exist: ${CONFIG.DOWNLOADS_DIR}`); } - // 檢查下載目錄權限 + // Check downloads directory permissions try { const testFile = path.join(CONFIG.DOWNLOADS_DIR, '.write-test'); fs.writeFileSync(testFile, ''); @@ -45,7 +45,7 @@ async function validateConfig(): Promise { throw new Error(`No write permission in downloads directory: ${CONFIG.DOWNLOADS_DIR}`); } - // 檢查臨時目錄權限 + // Check temporary directory permissions try { const testDir = fs.mkdtempSync(path.join(os.tmpdir(), CONFIG.TEMP_DIR_PREFIX)); await safeCleanup(testDir); @@ -55,8 +55,8 @@ async function validateConfig(): Promise { } /** - * 檢查必要的外部依賴 - * @throws {Error} 當依賴不滿足時 + * Check required external dependencies + * @throws {Error} when dependencies are not satisfied */ async function checkDependencies(): Promise { for (const tool of CONFIG.REQUIRED_TOOLS) { @@ -69,7 +69,7 @@ async function checkDependencies(): Promise { } /** - * 初始化服務 + * Initialize service */ async function initialize(): Promise { try { @@ -144,7 +144,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { }); /** - * 自定義錯誤類型 + * Custom error types */ class VideoDownloadError extends Error { constructor( @@ -165,7 +165,7 @@ class SubtitleError extends VideoDownloadError { } /** - * 錯誤代碼映射 + * Error code mappings */ const ERROR_CODES = { UNSUPPORTED_URL: 'Unsupported or invalid URL', @@ -180,8 +180,8 @@ const ERROR_CODES = { } as const; /** - * 安全地清理臨時目錄 - * @param directory 要清理的目錄路徑 + * Safely clean up temporary directory + * @param directory Directory path to clean up */ async function safeCleanup(directory: string): Promise { try { @@ -192,9 +192,9 @@ async function safeCleanup(directory: string): Promise { } /** - * 驗證 URL 格式 - * @param url 要驗證的 URL - * @throws {VideoDownloadError} 當 URL 無效時 + * Validate URL format + * @param url URL to validate + * @throws {VideoDownloadError} when URL is invalid */ function validateUrl(url: string, ErrorClass = VideoDownloadError): void { try { @@ -208,8 +208,8 @@ function validateUrl(url: string, ErrorClass = VideoDownloadError): void { } /** - * 生成格式化的時間戳 - * @returns 格式化的時間戳字符串 + * Generate formatted timestamp + * @returns Formatted timestamp string */ function getFormattedTimestamp(): string { return new Date().toISOString() @@ -219,8 +219,8 @@ function getFormattedTimestamp(): string { } /** - * 檢查是否為 YouTube URL - * @param url 要檢查的 URL + * Check if URL is a YouTube URL + * @param url URL to check */ function isYouTubeUrl(url: string): boolean { try { @@ -288,12 +288,12 @@ async function downloadSubtitles(url: string, language: string = "en"): Promise< ); } + // 首先嘗試下載一般字幕 try { await spawnPromise( "yt-dlp", [ "--write-sub", - "--write-auto-sub", "--sub-lang", language, "--skip-download", @@ -304,19 +304,36 @@ async function downloadSubtitles(url: string, language: string = "en"): Promise< { cwd: tempDirectory } ); } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (errorMessage.includes('no subtitles')) { + // 如果一般字幕下載失敗,嘗試下載自動生成的字幕 + try { + await spawnPromise( + "yt-dlp", + [ + "--write-auto-sub", + "--sub-lang", + language, + "--skip-download", + "--sub-format", + "srt", + url, + ], + { cwd: tempDirectory } + ); + } catch (autoSubError) { + const errorMessage = autoSubError instanceof Error ? autoSubError.message : String(autoSubError); + if (errorMessage.includes('no subtitles')) { + throw new SubtitleError( + ERROR_CODES.SUBTITLE_NOT_AVAILABLE, + 'SUBTITLE_NOT_AVAILABLE', + autoSubError as Error + ); + } throw new SubtitleError( - ERROR_CODES.SUBTITLE_NOT_AVAILABLE, - 'SUBTITLE_NOT_AVAILABLE', - error as Error + ERROR_CODES.SUBTITLE_ERROR, + 'SUBTITLE_ERROR', + autoSubError as Error ); } - throw new SubtitleError( - ERROR_CODES.SUBTITLE_ERROR, - 'SUBTITLE_ERROR', - error as Error - ); } let subtitlesContent = ""; @@ -328,7 +345,16 @@ async function downloadSubtitles(url: string, language: string = "en"): Promise< ); } - for (const file of files) { + // 只讀取 .srt 文件 + const srtFiles = files.filter(file => file.endsWith('.srt')); + if (srtFiles.length === 0) { + throw new SubtitleError( + ERROR_CODES.SUBTITLE_NOT_AVAILABLE, + 'SUBTITLE_NOT_AVAILABLE' + ); + } + + for (const file of srtFiles) { const filePath = path.join(tempDirectory, file); try { const fileData = fs.readFileSync(filePath, "utf8"); @@ -485,9 +511,9 @@ export async function downloadVideo(url: string, resolution = "720p"): Promise( action: () => Promise,