diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index ac840133b..890992ba4 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -81,6 +81,8 @@ Downloaded from their official release pages when the user chooses to install th | calibre (ebook-convert) | GPL-3.0 | https://calibre-ebook.com | | Tesseract OCR 5.4 (UB Mannheim Windows build, with Leptonica and the MinGW libraries it ships; their notices are in its `doc` folder) | Apache-2.0 (Tesseract); Leptonica BSD-2-Clause; others as listed in the build | https://github.com/tesseract-ocr/tesseract, build: https://github.com/UB-Mannheim/tesseract | | Tesseract language data (tessdata_fast 4.1.0), downloaded per language when used | Apache-2.0 | https://github.com/tesseract-ocr/tessdata_fast | +| whisper.cpp (whisper-cli.exe Windows build) | MIT | https://github.com/ggml-org/whisper.cpp | +| ggml Whisper base model (`ggml-base.bin`) | MIT | https://huggingface.co/ggerganov/whisper.cpp | These programs run as separate processes. Their source code is available from the linked projects. diff --git a/docs/ENGINES.md b/docs/ENGINES.md index 575a7e559..7026af659 100644 --- a/docs/ENGINES.md +++ b/docs/ENGINES.md @@ -37,6 +37,7 @@ Filee chooses engines automatically. Settings → *Engines* shows their status, | Apple Vision | part of macOS (Mac only) | **text recognition (OCR) on a Mac**, the same step as Tesseract without a download; Tesseract reads the languages Vision doesn't (see "Apple Vision on macOS") | | FFmpeg | **optional download** (~100 MB, 272 MB on disk) | **all video and audio**: video ↔ video (H.264, HEVC, AV1), video → animated GIF / APNG, a still frame or every frame, audio and subtitle extraction, audio ↔ audio (also APE, WavPack, TTA, DSD, DTS, ALAC, …), GIF, APNG and photos → MP4/WEBM/MOV, Live / Motion Photos → their video, a clip of a video → an iPhone Live Photo or Android Motion Photo (experimental), images → HEIC, clips or audio files joined into one, pictures → a slideshow MP4, copies without metadata (see "Media tools") | | Calibre | **optional download** (~216 MB, 660 MB on disk) | rare e-book formats: LIT, LRF, CHM, PDB, PML, RB, SNB, TCR, OEB → EPUB; EPUB → MOBI, AZW3, LIT, LRF, PDB, PML, RB, SNB, TCR; e-books → PDF (ranked above the HWPX writer, see below) | +| Whisper | **optional download** (Windows only; ~157 MB with model, ~160 MB on disk) | **Speech-to-text transcription**: video and audio → SRT subtitles in ~100 languages using OpenAI Whisper (whisper.cpp); runs locally and offline; needs FFmpeg | DOCX, XLSX, XLS, ODS and PPTX → PDF need neither Microsoft Office nor LibreOffice: they are read in-process, written as HWPX and rendered by rhwp. Anything the built-in readers understand (including PDF) is written as DOCX by the DOCX writer. @@ -1762,3 +1763,18 @@ Tesseract and is registered before it, so the route planner takes it; on Windows autorelease pool per page. A cancelled job cancels the running request. - Tests: `VisionLanguageTests`, `OcrTextLayerTests` and `VisionConverterTests` (a stand-in recognizer) run everywhere; `AppleVisionTests` read real pictures on macOS (CI's Apple silicon and Intel jobs). + +## Whisper + +Filee transcribes speech in video and audio files into SRT subtitle files using OpenAI Whisper (`whisper.cpp`). +Transcription runs entirely locally and offline on your computer; no audio or data is sent to external servers. + +- **Platform**: Windows only (x64). On macOS and Linux, the Whisper package is not offered and the engine reports + `engine.reason.unsupported_platform` ("Not available on this platform"). +- **Download size**: ~157 MB total (~9 MB for `whisper-cli.exe` and ~148 MB for the default `ggml-base.bin` model; ~160 MB on disk). + Downloads are pinned by SHA-256 in `engines.json`. Users can also place larger Whisper models (such as `ggml-small.bin` + or `ggml-large-v3-turbo.bin`) into the engine directory or point to a custom installation in Settings → *Engines*. +- **FFmpeg requirement**: Whisper operates on 16 kHz mono 16-bit WAV audio. Extracting audio from arbitrary video and audio + formats requires FFmpeg. FFmpeg must be installed (via Settings → *Engines*, bundled, or found on `PATH`). +- **Languages**: Whisper supports auto-detection (`auto`) as well as specific language codes (`-l `, e.g. `hi` for + Hindi, `en` for English, `es` for Spanish, `fr` for French, `ja` for Japanese, `zh` for Chinese). diff --git a/src/Filee.App/Assets/i18n/ar.json b/src/Filee.App/Assets/i18n/ar.json index 97a575707..c3191ee9f 100644 --- a/src/Filee.App/Assets/i18n/ar.json +++ b/src/Filee.App/Assets/i18n/ar.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "التحويل بين ملفات الترجمة: SRT وASS/SSA وWebVTT وSAMI ‏(SMI) وLRC وTTML وMicroDVD وSCC، وحفظها بترميز UTF-8.\nتُقرأ ملفات SMI وSRT الكورية بترميز CP949 / EUC-KR بصورة صحيحة.", "engine.ffmpeg.description": "الفيديو والصوت: MP4 وMOV وMKV وWEBM وMP3 وM4A وWAV وFLAC وغيرها.\nواستخراج الصوت والترجمة، وإنشاء رسوم GIF وAPNG المتحركة، وحفظ الصور بصيغة HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "تحويل الكلام إلى نص مدعوم بواسطة OpenAI Whisper. يُنشئ ترجمات SRT تلقائياً من الكلام في الفيديو والصوت.", "engine.pandoc.description": "التحويل بين Markdown وHTML وبين ODT وRTF، ومن DOCX إلى Markdown\nالتحويل بين reStructuredText وLaTeX وOrg وAsciiDoc وTypst وMediaWiki وDocBook وJupyter وغيرها وبين Markdown وHTML وDOCX وODT\nتحويل Markdown وOrg إلى شرائح PPTX", "engine.ghostscript.description": "تحويل EPS وPostScript وملفات Illustrator القديمة إلى PDF وصور؛ وتحويل PDF إلى EPS وPS.", "engine.tesseract.description": "يتعرّف على نص الصور والمستندات الممسوحة (OCR): PDF قابل للبحث وDOCX وTXT.", @@ -409,6 +411,8 @@ "preset.extract": "استخراج", "preset.zip_all": "ZIP واحد", "preset.extract_subtitles": "استخراج الترجمة", + "preset.caption_video": "تسميات توضيحية", + "preset.caption_video.donut": "تسميات", "preset.trace_svg": "تتبّع إلى SVG", "preset.trace_svg.donut": "تتبّع إلى SVG", "preset.trace_line_art": "رسم خطي إلى SVG", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (الأصغر، بطيء)", "presets.hardware_encoding": "استخدام تسريع العتاد (معالج الرسوميات)", "presets.hardware_encoding_hint": "ترميز H.264 وHEVC وAV1 باستخدام معالج الرسوميات.\nأسرع بكثير وأقل استهلاكًا للطاقة.\nالجودة أقل قليلًا عند حجم الملف نفسه.\nعند عدم توفر معالج مناسب (يتطلب AV1 معالجًا حديثًا)، يُرمَّز الفيديو بالطريقة المعتادة.", + "presets.caption_video": "إنشاء ترجمات (Whisper)", + "presets.caption_video_hint": "تحويل الكلام في الفيديو والصوت إلى ملف ترجمة SRT باستخدام Whisper.", + "presets.spoken_language": "اللغة المنطوقة", + "presets.spoken_language.auto": "تلقائي", "presets.audio_codec": "ترميز الصوت", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (دون فقدان)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "يقرأ نص ملفات PDF الممسوحة والصور: PDF قابل للبحث وDOCX وTXT.\nتُنزَّل كل لغة عند استخدامها أول مرة.", "engines.package.ffmpeg.name": "FFmpeg (الفيديو والصوت)", "engines.package.ffmpeg.description": "يضيف تحويل الفيديو والصوت، وتحويل الفيديو إلى GIF أو صورة ثابتة، واستخراج الصوت من الفيديو، وتحويل GIF إلى MP4.\nمطلوب للفيديو والصوت فقط.", + "engines.package.whisper.name": "Whisper (ترجمة بالذكاء الاصطناعي)", + "engines.package.whisper.description": "تحويل الكلام في الفيديو والصوت إلى ملفات ترجمة SRT. يعمل بدون اتصال بالإنترنت.", "engines.package.calibre.name": "Calibre (Kindle وصيغ الكتب النادرة)", "engines.package.calibre.description": "يحفظ MOBI وAZW3 لأجهزة Kindle، ويحوّل صيغ الكتب الإلكترونية النادرة (LIT وLRF وPDB وCHM…).\nتُقرأ EPUB وMOBI وAZW3 وFB2 والقصص المصورة بدونه.", "engines.package.size": "التنزيل {0} · على القرص {1}", diff --git a/src/Filee.App/Assets/i18n/de.json b/src/Filee.App/Assets/i18n/de.json index 3130e13a8..ac26311f5 100644 --- a/src/Filee.App/Assets/i18n/de.json +++ b/src/Filee.App/Assets/i18n/de.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Untertitel (integriert)", "engine.subtitle.description": "Untertitel: Konvertiert SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD und SCC untereinander und speichert sie als UTF-8.\nKoreanische SMI- und SRT-Dateien in CP949 / EUC-KR werden korrekt gelesen.", "engine.ffmpeg.description": "Video und Audio: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC und mehr.\nExtrahiert auch Ton und Untertitel, erstellt GIF- und APNG-Animationen und speichert Bilder als HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Sprache-zu-Text-Transkription powered by OpenAI Whisper. Erstellt automatisch SRT-Untertitel aus Sprache in Video und Audio.", "engine.pandoc.description": "Markdown und HTML ↔ ODT und RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter und mehr ↔ Markdown, HTML, DOCX und ODT\nMarkdown und Org → PPTX-Folien", "engine.ghostscript.description": "EPS, PostScript und ältere Illustrator-Dateien in PDF und Bilder; PDF in EPS und PS.", "engine.tesseract.description": "Erkennt Text in Scans und Bildern (OCR): durchsuchbare PDF, DOCX, TXT.", @@ -409,6 +411,8 @@ "preset.extract": "Entpacken", "preset.zip_all": "Eine ZIP-Datei", "preset.extract_subtitles": "Untertitel extrahieren", + "preset.caption_video": "Untertitel", + "preset.caption_video.donut": "Untertitel", "preset.trace_svg": "Vektor-SVG", "preset.trace_svg.donut": "Vektorisieren", "preset.trace_line_art": "Linien-SVG", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (am kleinsten, langsam)", "presets.hardware_encoding": "Hardwarebeschleunigung nutzen (Grafikchip)", "presets.hardware_encoding_hint": "Kodiert H.264, HEVC und AV1 auf dem Grafikchip.\nDeutlich schneller und stromsparender.\nEtwas geringere Qualität bei gleicher Dateigröße.\nOhne geeigneten Chip (AV1 benötigt einen neueren) wird das Video wie gewohnt kodiert.", + "presets.caption_video": "Untertitel erstellen (Whisper)", + "presets.caption_video_hint": "Transkribiert Sprache in Videos und Audio mit Whisper in eine SRT-Untertiteldatei.", + "presets.spoken_language": "Gesprochene Sprache", + "presets.spoken_language.auto": "Automatisch", "presets.audio_codec": "Audio-Codec", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (verlustfrei)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "Liest Text aus gescannten PDFs und Bildern: durchsuchbare PDF, DOCX, TXT.\nJede Sprache wird bei der ersten Verwendung heruntergeladen.", "engines.package.ffmpeg.name": "FFmpeg (Video und Audio)", "engines.package.ffmpeg.description": "Ergänzt Video- und Audiokonvertierung, Video → GIF oder Einzelbild, Ton aus Videos und GIF → MP4.\nNur für Video und Audio erforderlich.", + "engines.package.whisper.name": "Whisper (KI-Untertitel)", + "engines.package.whisper.description": "Transkribiert Sprache in Videos und Audio in SRT-Untertiteldateien. Funktioniert offline ohne Internetverbindung.", "engines.package.calibre.name": "Calibre (Kindle und seltene E-Book-Formate)", "engines.package.calibre.description": "Speichert MOBI und AZW3 für Kindle und konvertiert seltene E-Book-Formate (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 und Comics lassen sich auch ohne Calibre lesen.", "engines.package.size": "Download {0} · Platzbedarf {1}", diff --git a/src/Filee.App/Assets/i18n/en.json b/src/Filee.App/Assets/i18n/en.json index 7db719e74..f562d0957 100644 --- a/src/Filee.App/Assets/i18n/en.json +++ b/src/Filee.App/Assets/i18n/en.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "Subtitles: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD and SCC into each other, saved as UTF-8.\nKorean SMI and SRT files in CP949 / EUC-KR read correctly.", "engine.ffmpeg.description": "Video and audio: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC and more.\nAlso extracts sound and subtitles, makes GIF and APNG animations and saves images as HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Speech-to-text transcription powered by OpenAI Whisper. Automatically generates SRT subtitles from speech in video and audio.", "engine.pandoc.description": "Markdown and HTML ↔ ODT and RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter and more ↔ Markdown, HTML, DOCX and ODT\nMarkdown and Org → PPTX slides", "engine.ghostscript.description": "EPS, PostScript and older Illustrator files to PDF and images; PDF to EPS and PS.", "engine.tesseract.description": "Recognizes the text of scans and pictures (OCR): searchable PDF, DOCX, TXT.", @@ -419,6 +421,8 @@ "preset.extract": "Extract", "preset.zip_all": "One ZIP", "preset.extract_subtitles": "Extract subtitles", + "preset.caption_video": "Caption", + "preset.caption_video.donut": "Caption", "preset.trace_svg": "Trace to SVG", "preset.trace_svg.donut": "Trace", "preset.trace_line_art": "Line art to SVG", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1 (smallest, slow)", "presets.hardware_encoding": "Use hardware acceleration (graphics chip)", "presets.hardware_encoding_hint": "Encodes H.264, HEVC and AV1 on the graphics chip.\nMuch faster and uses less power.\nSlightly lower quality at the same file size.\nWithout a usable chip (AV1 needs a recent one), the video is encoded as usual.", + "presets.caption_video": "Generate subtitles (Whisper)", + "presets.caption_video_hint": "Transcribes speech in videos and audio to an SRT subtitle file using Whisper.", + "presets.spoken_language": "Spoken language", + "presets.spoken_language.auto": "Auto", "presets.audio_codec": "Audio codec", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (lossless)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "Reads the text of scanned PDFs and pictures: searchable PDF, DOCX, TXT.\nEach language downloads the first time it is used.", "engines.package.ffmpeg.name": "FFmpeg (video and audio)", "engines.package.ffmpeg.description": "Adds video and audio conversion, video → GIF or still image, sound from videos and GIF → MP4.\nOnly needed for video and audio.", + "engines.package.whisper.name": "Whisper (AI Captions)", + "engines.package.whisper.description": "Transcribes speech in video and audio to SRT subtitle files. Runs offline without an internet connection.", "engines.package.calibre.name": "Calibre (Kindle and rare e-book formats)", "engines.package.calibre.description": "Saves MOBI and AZW3 for Kindle and converts rare e-book formats (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 and comics are read without it.", "engines.package.size": "Download {0} · {1} on disk", diff --git a/src/Filee.App/Assets/i18n/es.json b/src/Filee.App/Assets/i18n/es.json index 6bb31d3be..d56c8b153 100644 --- a/src/Filee.App/Assets/i18n/es.json +++ b/src/Filee.App/Assets/i18n/es.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "Subtítulos: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD y SCC entre sí, guardados como UTF-8.\nLee correctamente archivos SMI y SRT coreanos en CP949 / EUC-KR.", "engine.ffmpeg.description": "Vídeo y audio: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC y más.\nTambién extrae sonido y subtítulos, crea animaciones GIF y APNG y guarda imágenes como HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Transcripción de voz a texto con OpenAI Whisper. Genera automáticamente subtítulos SRT a partir de voz en vídeo y audio.", "engine.pandoc.description": "Markdown y HTML ↔ ODT y RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter y más ↔ Markdown, HTML, DOCX y ODT\nMarkdown y Org → diapositivas PPTX", "engine.ghostscript.description": "EPS, PostScript y archivos antiguos de Illustrator a PDF e imágenes; PDF a EPS y PS.", "engine.tesseract.description": "Reconoce el texto de escaneos e imágenes (OCR): PDF con búsqueda, DOCX, TXT.", @@ -409,6 +411,8 @@ "preset.extract": "Extraer", "preset.zip_all": "Un solo ZIP", "preset.extract_subtitles": "Extraer subtítulos", + "preset.caption_video": "Subtítulos", + "preset.caption_video.donut": "Subtítulos", "preset.trace_svg": "Trazar a SVG", "preset.trace_svg.donut": "Trazar a SVG", "preset.trace_line_art": "Líneas a SVG", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (el más pequeño, lento)", "presets.hardware_encoding": "Usar aceleración por hardware (chip gráfico)", "presets.hardware_encoding_hint": "Codifica H.264, HEVC y AV1 en el chip gráfico.\nMucho más rápido y consume menos energía.\nCalidad ligeramente inferior con el mismo tamaño de archivo.\nSin un chip compatible (AV1 necesita uno reciente), el vídeo se codifica de la forma habitual.", + "presets.caption_video": "Generar subtítulos (Whisper)", + "presets.caption_video_hint": "Transcribe la voz de vídeos y audios a un archivo de subtítulos SRT usando Whisper.", + "presets.spoken_language": "Idioma hablado", + "presets.spoken_language.auto": "Automático", "presets.audio_codec": "Códec de audio", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (sin pérdida)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "Lee el texto de PDF escaneados e imágenes: PDF con búsqueda, DOCX, TXT.\nCada idioma se descarga la primera vez que se usa.", "engines.package.ffmpeg.name": "FFmpeg (vídeo y audio)", "engines.package.ffmpeg.description": "Añade conversión de vídeo y audio, vídeo → GIF o imagen fija, sonido de vídeos y GIF → MP4.\nSolo es necesario para vídeo y audio.", + "engines.package.whisper.name": "Whisper (subtítulos IA)", + "engines.package.whisper.description": "Transcribe la voz de vídeos y audios a archivos de subtítulos SRT. Funciona sin conexión a internet.", "engines.package.calibre.name": "Calibre (Kindle y libros electrónicos poco habituales)", "engines.package.calibre.description": "Guarda MOBI y AZW3 para Kindle y convierte formatos de libro electrónico poco habituales (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 y cómics se leen sin él.", "engines.package.size": "Descargar {0} · {1} en disco", diff --git a/src/Filee.App/Assets/i18n/fr.json b/src/Filee.App/Assets/i18n/fr.json index 02064de8c..f01e05d9f 100644 --- a/src/Filee.App/Assets/i18n/fr.json +++ b/src/Filee.App/Assets/i18n/fr.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "Sous-titres : SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD et SCC entre eux, enregistrés en UTF-8.\nLes fichiers SMI et SRT coréens en CP949 / EUC-KR sont lus correctement.", "engine.ffmpeg.description": "Vidéo et audio : MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC et plus.\nExtrait aussi le son et les sous-titres, crée des animations GIF et APNG et enregistre des images en HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Transcription parole-texte optimisée par OpenAI Whisper. Génère automatiquement des sous-titres SRT à partir de la voix dans les vidéos et audios.", "engine.pandoc.description": "Markdown et HTML ↔ ODT et RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter et plus ↔ Markdown, HTML, DOCX et ODT\nMarkdown et Org → diapositives PPTX", "engine.ghostscript.description": "EPS, PostScript et anciens fichiers Illustrator en PDF et images ; PDF en EPS et PS.", "engine.tesseract.description": "Reconnaît le texte des numérisations et des images (OCR) : PDF consultable, DOCX, TXT.", @@ -419,6 +421,8 @@ "preset.extract": "Extraire", "preset.zip_all": "Un seul ZIP", "preset.extract_subtitles": "Extraire les sous-titres", + "preset.caption_video": "Sous-titres", + "preset.caption_video.donut": "Sous-titres", "preset.trace_svg": "Vectoriser en SVG", "preset.trace_svg.donut": "Vectoriser en SVG", "preset.trace_line_art": "Trait en SVG", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1 (le plus léger, lent)", "presets.hardware_encoding": "Utiliser l'accélération matérielle (puce graphique)", "presets.hardware_encoding_hint": "Encode le H.264, le HEVC et l'AV1 sur la puce graphique.\nBeaucoup plus rapide et moins gourmand en énergie.\nQualité légèrement inférieure à taille de fichier égale.\nSans puce compatible (l'AV1 en exige une récente), la vidéo est encodée normalement.", + "presets.caption_video": "Générer des sous-titres (Whisper)", + "presets.caption_video_hint": "Transcrit la parole des vidéos et audios en un fichier de sous-titres SRT à l'aide de Whisper.", + "presets.spoken_language": "Langue parlée", + "presets.spoken_language.auto": "Automatique", "presets.audio_codec": "Codec audio", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (sans perte)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "Lit le texte des PDF numérisés et des images : PDF consultable, DOCX, TXT.\nChaque langue est téléchargée à sa première utilisation.", "engines.package.ffmpeg.name": "FFmpeg (vidéo et audio)", "engines.package.ffmpeg.description": "Ajoute la conversion vidéo et audio, vidéo → GIF ou image fixe, le son des vidéos et GIF → MP4.\nNécessaire uniquement pour la vidéo et l'audio.", + "engines.package.whisper.name": "Whisper (sous-titres IA)", + "engines.package.whisper.description": "Transcrit la parole des vidéos et audios en fichiers de sous-titres SRT. Fonctionne hors ligne sans connexion Internet.", "engines.package.calibre.name": "Calibre (Kindle et formats de livres numériques rares)", "engines.package.calibre.description": "Enregistre en MOBI et AZW3 pour Kindle et convertit les formats de livres numériques rares (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 et bandes dessinées sont lus sans lui.", "engines.package.size": "Téléchargement {0} · {1} sur le disque", diff --git a/src/Filee.App/Assets/i18n/hi.json b/src/Filee.App/Assets/i18n/hi.json index bb04086fe..52931e1eb 100644 --- a/src/Filee.App/Assets/i18n/hi.json +++ b/src/Filee.App/Assets/i18n/hi.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "सबटाइटल: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD और SCC एक-दूसरे में, UTF-8 में सहेजे जाते हैं।\nCP949 / EUC-KR में कोरियाई SMI और SRT फ़ाइलें सही पढ़ी जाती हैं।", "engine.ffmpeg.description": "वीडियो और ऑडियो: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC और अन्य।\nआवाज़ और सबटाइटल निकालता है, GIF और APNG एनिमेशन बनाता है और इमेज को HEIC में सहेजता है।", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "OpenAI Whisper द्वारा संचालित स्पीच-टू-टेक्स्ट ट्रांसक्रिप्शन। वीडियो और ऑडियो में भाषण से स्वचालित रूप से SRT उपशीर्षक बनाता है।", "engine.pandoc.description": "Markdown और HTML ↔ ODT और RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter और अन्य ↔ Markdown, HTML, DOCX और ODT\nMarkdown और Org → PPTX स्लाइड", "engine.ghostscript.description": "EPS, PostScript और पुरानी Illustrator फ़ाइलें PDF और इमेज में; PDF से EPS और PS।", "engine.tesseract.description": "स्कैन और तस्वीरों का टेक्स्ट पहचानता है (OCR): खोजने योग्य PDF, DOCX, TXT।", @@ -409,6 +411,8 @@ "preset.extract": "निकालें", "preset.zip_all": "एक ZIP", "preset.extract_subtitles": "सबटाइटल निकालें", + "preset.caption_video": "उपशीर्षक", + "preset.caption_video.donut": "उपशीर्षक", "preset.trace_svg": "SVG में ट्रेस करें", "preset.trace_svg.donut": "SVG में ट्रेस करें", "preset.trace_line_art": "लाइन आर्ट को SVG में", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (सबसे छोटा, धीमा)", "presets.hardware_encoding": "हार्डवेयर एक्सेलरेशन इस्तेमाल करें (ग्राफ़िक्स चिप)", "presets.hardware_encoding_hint": "H.264, HEVC और AV1 को ग्राफ़िक्स चिप पर एन्कोड करता है।\nबहुत तेज़ है और कम बिजली लगती है।\nउसी फ़ाइल साइज़ पर क्वालिटी थोड़ी कम होती है।\nउपयोगी चिप न होने पर (AV1 के लिए नई चिप चाहिए) वीडियो सामान्य तरीके से एन्कोड होता है।", + "presets.caption_video": "उपशीर्षक बनाएं (Whisper)", + "presets.caption_video_hint": "Whisper का उपयोग करके वीडियो और ऑडियो में भाषण को SRT उपशीर्षक फ़ाइल में ट्रांसक्राइब करता है।", + "presets.spoken_language": "बोली जाने वाली भाषा", + "presets.spoken_language.auto": "स्वचालित", "presets.audio_codec": "ऑडियो कोडेक", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (लॉसलेस)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "स्कैन किए गए PDF और तस्वीरों का टेक्स्ट पढ़ता है: खोजने योग्य PDF, DOCX, TXT।\nहर भाषा पहली बार इस्तेमाल पर डाउनलोड होती है।", "engines.package.ffmpeg.name": "FFmpeg (वीडियो और ऑडियो)", "engines.package.ffmpeg.description": "वीडियो और ऑडियो कन्वर्ज़न, वीडियो → GIF या स्टिल इमेज, वीडियो से आवाज़ और GIF → MP4 जोड़ता है।\nकेवल वीडियो और ऑडियो के लिए ज़रूरी।", + "engines.package.whisper.name": "Whisper (AI उपशीर्षक)", + "engines.package.whisper.description": "वीडियो और ऑडियो के भाषण को SRT उपशीर्षक फ़ाइलों में ट्रांसक्राइब करता है। इंटरनेट कनेक्शन के बिना ऑफ़लाइन काम करता है।", "engines.package.calibre.name": "Calibre (Kindle और दुर्लभ ई-बुक फ़ॉर्मैट)", "engines.package.calibre.description": "Kindle के लिए MOBI और AZW3 सहेजता है और दुर्लभ ई-बुक फ़ॉर्मैट (LIT, LRF, PDB, CHM…) कन्वर्ट करता है।\nEPUB, MOBI, AZW3, FB2 और कॉमिक्स इसके बिना भी पढ़े जाते हैं।", "engines.package.size": "डाउनलोड {0} · डिस्क पर {1}", diff --git a/src/Filee.App/Assets/i18n/id.json b/src/Filee.App/Assets/i18n/id.json index 9e4cf17b9..e7c892a01 100644 --- a/src/Filee.App/Assets/i18n/id.json +++ b/src/Filee.App/Assets/i18n/id.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Subtitle (bawaan)", "engine.subtitle.description": "Subtitle: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD, dan SCC saling konversi, disimpan sebagai UTF-8.\nFile SMI dan SRT Korea dalam CP949 / EUC-KR terbaca dengan benar.", "engine.ffmpeg.description": "Video dan audio: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC, dan lainnya.\nJuga mengekstrak suara dan subtitle, membuat animasi GIF dan APNG, serta menyimpan gambar sebagai HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Transkripsi ucapan ke teks dengan OpenAI Whisper. Membuat subtitle SRT secara otomatis dari ucapan dalam video dan audio.", "engine.pandoc.description": "Markdown dan HTML ↔ ODT dan RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter, dan lainnya ↔ Markdown, HTML, DOCX, dan ODT\nMarkdown dan Org → slide presentasi PPTX", "engine.ghostscript.description": "File EPS, PostScript, dan file Illustrator lama ke PDF dan gambar; PDF ke EPS dan PS.", "engine.tesseract.description": "Mengenali teks dari hasil pindai dan gambar (OCR): PDF yang dapat dicari, DOCX, TXT.", @@ -409,6 +411,8 @@ "preset.extract": "Ekstrak", "preset.zip_all": "Satu file ZIP", "preset.extract_subtitles": "Ekstrak subtitle", + "preset.caption_video": "Buat subtitle", + "preset.caption_video.donut": "Subtitle", "preset.trace_svg": "Vektorisasi ke SVG", "preset.trace_svg.donut": "Vektorisasi", "preset.trace_line_art": "Gambar garis ke SVG", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (terkecil, lambat)", "presets.hardware_encoding": "Gunakan akselerasi perangkat keras (kartu grafis)", "presets.hardware_encoding_hint": "Mengodekan H.264, HEVC, dan AV1 pada kartu grafis.\nJauh lebih cepat dan hemat daya.\nKualitas sedikit lebih rendah pada ukuran file yang sama.\nTanpa chip yang kompatibel (AV1 memerlukan chip terbaru), video dikodekan secara standar.", + "presets.caption_video": "Buat subtitle (Whisper)", + "presets.caption_video_hint": "Mentranskripsikan ucapan dalam video dan audio menjadi file subtitle SRT dengan Whisper.", + "presets.spoken_language": "Bahasa yang diucapkan", + "presets.spoken_language.auto": "Otomatis", "presets.audio_codec": "Kodek audio", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (tanpa penurunan mutu)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "Membaca teks dari PDF hasil pindai dan gambar: PDF yang dapat dicari, DOCX, TXT.\nSetiap bahasa diunduh saat pertama kali digunakan.", "engines.package.ffmpeg.name": "FFmpeg (video dan audio)", "engines.package.ffmpeg.description": "Menambahkan konversi video dan audio, video → GIF atau gambar diam, ekstraksi suara dari video, dan GIF → MP4.\nHanya diperlukan untuk video dan audio.", + "engines.package.whisper.name": "Whisper (Subtitle AI)", + "engines.package.whisper.description": "Mentranskripsikan ucapan dalam video dan audio menjadi file subtitle SRT. Berjalan offline tanpa koneksi internet.", "engines.package.calibre.name": "Calibre (Kindle dan format e-book langka)", "engines.package.calibre.description": "Menyimpan MOBI dan AZW3 untuk Kindle serta mengonversi format e-book langka (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2, dan komik dapat dibaca tanpanya.", "engines.package.size": "Unduhan {0} · {1} di disk", diff --git a/src/Filee.App/Assets/i18n/it.json b/src/Filee.App/Assets/i18n/it.json index e31db7734..c7f6ba981 100644 --- a/src/Filee.App/Assets/i18n/it.json +++ b/src/Filee.App/Assets/i18n/it.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Sottotitoli (integrato)", "engine.subtitle.description": "Sottotitoli: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD e SCC convertibili tra loro, salvati in UTF-8.\\nI file SMI e SRT coreani in CP949 / EUC-KR vengono letti correttamente.", "engine.ffmpeg.description": "Video e audio: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC e altro.\\nEstrae anche l'audio e i sottotitoli, crea animazioni GIF e APNG e salva le immagini come HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Trascrizione vocale con tecnologia OpenAI Whisper. Genera automaticamente sottotitoli SRT dal parlato in video e audio.", "engine.pandoc.description": "Markdown e HTML ↔ ODT e RTF, DOCX → Markdown\\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter e altro ↔ Markdown, HTML, DOCX e ODT\\nMarkdown e Org → slide PPTX", "engine.ghostscript.description": "EPS, PostScript e vecchi file Illustrator in PDF e immagini; PDF in EPS e PS.", "engine.tesseract.description": "Riconosce il testo di scansioni e immagini (OCR): PDF ricercabile, DOCX, TXT.", @@ -409,6 +411,8 @@ "preset.extract": "Estrai", "preset.zip_all": "Un unico ZIP", "preset.extract_subtitles": "Estrai sottotitoli", + "preset.caption_video": "Sottotitoli", + "preset.caption_video.donut": "Sottotitoli", "preset.trace_svg": "Vettorializza in SVG", "preset.trace_svg.donut": "Vettorializza", "preset.trace_line_art": "Disegno al tratto in SVG", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (il più piccolo, lento)", "presets.hardware_encoding": "Usa l'accelerazione hardware (chip grafico)", "presets.hardware_encoding_hint": "Codifica H.264, HEVC e AV1 sul chip grafico.\\nMolto più veloce e consuma meno energia.\\nQualità leggermente inferiore a parità di dimensione del file.\\nSenza un chip utilizzabile (AV1 ne richiede uno recente), il video viene codificato normalmente.", + "presets.caption_video": "Genera sottotitoli (Whisper)", + "presets.caption_video_hint": "Trascrive la voce in video e audio in un file di sottotitoli SRT utilizzando Whisper.", + "presets.spoken_language": "Lingua parlata", + "presets.spoken_language.auto": "Automatico", "presets.audio_codec": "Codec audio", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (senza perdita)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "Legge il testo di PDF acquisiti da scansione e di immagini: PDF ricercabile, DOCX, TXT.\\nOgni lingua viene scaricata la prima volta che la usi.", "engines.package.ffmpeg.name": "FFmpeg (video e audio)", "engines.package.ffmpeg.description": "Aggiunge la conversione di video e audio, video → GIF o immagine fissa, audio dai video e GIF → MP4.\\nServe solo per video e audio.", + "engines.package.whisper.name": "Whisper (sottotitoli IA)", + "engines.package.whisper.description": "Trascrive la voce in video e audio in file di sottotitoli SRT. Funziona offline senza connessione Internet.", "engines.package.calibre.name": "Calibre (Kindle e formati di e-book rari)", "engines.package.calibre.description": "Salva MOBI e AZW3 per Kindle e converte formati di e-book rari (LIT, LRF, PDB, CHM…).\\nEPUB, MOBI, AZW3, FB2 e fumetti si leggono senza.", "engines.package.size": "Download {0} · {1} su disco", diff --git a/src/Filee.App/Assets/i18n/ja.json b/src/Filee.App/Assets/i18n/ja.json index 62fea701f..507537f83 100644 --- a/src/Filee.App/Assets/i18n/ja.json +++ b/src/Filee.App/Assets/i18n/ja.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "字幕形式(SRT、ASS/SSA、WebVTT、SAMI(SMI)、LRC、TTML、MicroDVD、SCC)を相互変換し、UTF-8で保存します。\nCP949 / EUC-KRの韓国語SMI、SRTファイルも正しく読み込めます。", "engine.ffmpeg.description": "動画と音声(MP4、MOV、MKV、WEBM、MP3、M4A、WAV、FLACなど)を変換します。\n音声や字幕の抽出、GIFやAPNGアニメーションの作成、画像のHEIC保存にも対応します。", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "OpenAI Whisperによる音声テキスト変換。動画や音声の音声をSRT字幕ファイルに自動生成します。", "engine.pandoc.description": "Markdown、HTMLとODT、RTFを相互変換し、DOCXをMarkdownに変換します。\nreStructuredText、LaTeX、Org、AsciiDoc、Typst、MediaWiki、DocBook、JupyterなどをMarkdown、HTML、DOCX、ODTと相互変換します。\nMarkdown、OrgからPPTXスライドを作成します。", "engine.ghostscript.description": "EPS、PostScript、古いIllustratorファイルをPDFや画像に変換し、PDFをEPSやPSに変換します。", "engine.tesseract.description": "スキャンや画像の文字を認識します(OCR):検索可能な PDF、DOCX、TXT。", @@ -409,6 +411,8 @@ "preset.extract": "展開", "preset.zip_all": "1つに", "preset.extract_subtitles": "字幕抽出", + "preset.caption_video": "字幕生成", + "preset.caption_video.donut": "字幕", "preset.trace_svg": "SVG化", "preset.trace_svg.donut": "SVG化", "preset.trace_line_art": "線画", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1(最小、変換に時間がかかる)", "presets.hardware_encoding": "ハードウェアアクセラレーションを使う(GPU)", "presets.hardware_encoding_hint": "GPUでH.264、HEVC、AV1にエンコードします。\n大幅に速くなり、消費電力も抑えられます。\n同じファイルサイズでは、画質がわずかに低くなります。\n利用可能なGPUがない場合(AV1には新しいGPUが必要です)は、通常の方法でエンコードします。", + "presets.caption_video": "字幕を生成 (Whisper)", + "presets.caption_video_hint": "Whisperを使用して動画や音声の音声をSRT字幕ファイルに書き起こします。", + "presets.spoken_language": "音声言語", + "presets.spoken_language.auto": "自動", "presets.audio_codec": "音声コーデック", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC(可逆圧縮)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "スキャンした PDF や画像の文字を読み取ります:検索可能な PDF、DOCX、TXT。\n言語は初めて使うときにダウンロードされます。", "engines.package.ffmpeg.name": "FFmpeg(動画・音声)", "engines.package.ffmpeg.description": "動画・音声の変換、動画 → GIF・静止画、動画からの音声抽出、GIF → MP4に対応します。\n動画と音声の変換にのみ必要です。", + "engines.package.whisper.name": "Whisper (AI字幕)", + "engines.package.whisper.description": "動画や音声の音声をSRT字幕ファイルに書き起こします。インターネット接続なしでオフラインで動作します。", "engines.package.calibre.name": "Calibre(Kindle形式・一般的でない電子書籍形式)", "engines.package.calibre.description": "Kindle用にMOBI、AZW3形式で保存できるほか、一般的でない電子書籍形式(LIT、LRF、PDB、CHMなど)を変換できます。\nEPUB、MOBI、AZW3、FB2、コミックの読み込みには、このエンジンは必要ありません。", "engines.package.size": "ダウンロード:{0} · ディスク使用量:{1}", diff --git a/src/Filee.App/Assets/i18n/ko.json b/src/Filee.App/Assets/i18n/ko.json index 8f47fda89..c37fdbeda 100644 --- a/src/Filee.App/Assets/i18n/ko.json +++ b/src/Filee.App/Assets/i18n/ko.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "SRT, ASS/SSA, WebVTT, SAMI(SMI), LRC, TTML, MicroDVD, SCC 자막을 서로 바꾸고 UTF-8로 저장해요.\nCP949/EUC-KR로 된 SMI·SRT 파일도 한글이 깨지지 않아요.", "engine.ffmpeg.description": "MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC 등 동영상·오디오를 변환해요.\n소리·자막 추출, GIF·APNG 만들기, 이미지 → HEIC도 해요.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "OpenAI Whisper 기반 음성 텍스트 변환. 동영상과 오디오의 말소리를 SRT 자막 파일로 바꿔요.", "engine.pandoc.description": "마크다운·HTML ↔ ODT·RTF, DOCX → 마크다운\nreStructuredText·LaTeX·Org·AsciiDoc·Typst·MediaWiki·DocBook·Jupyter 등 ↔ 마크다운·HTML·DOCX·ODT\n마크다운·Org → PPTX 슬라이드", "engine.ghostscript.description": "EPS, 포스트스크립트, 예전 일러스트레이터 파일을 PDF와 이미지로, PDF를 EPS·PS로.", "engine.tesseract.description": "스캔본과 사진의 글자를 인식해요(OCR): 검색 가능한 PDF, DOCX, TXT.", @@ -419,6 +421,8 @@ "preset.extract": "압축 풀기", "preset.zip_all": "ZIP 하나로", "preset.extract_subtitles": "자막 추출", + "preset.caption_video": "자막 생성", + "preset.caption_video.donut": "자막", "preset.trace_svg": "벡터화", "preset.trace_svg.donut": "벡터화", "preset.trace_line_art": "흑백 선화", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1 (가장 작게, 느림)", "presets.hardware_encoding": "하드웨어 가속 사용 (그래픽 칩)", "presets.hardware_encoding_hint": "H.264, HEVC, AV1을 그래픽 칩으로 인코딩해요.\n훨씬 빠르고 전력도 덜 써요.\n같은 용량이면 화질이 조금 낮아요.\n쓸 수 있는 칩이 없으면(AV1은 최신 칩이 필요해요) 평소처럼 인코딩해요.", + "presets.caption_video": "자막 생성 (Whisper)", + "presets.caption_video_hint": "Whisper로 비디오와 오디오의 말소리를 SRT 자막 파일로 변환해요.", + "presets.spoken_language": "음성 언어", + "presets.spoken_language.auto": "자동", "presets.audio_codec": "오디오 코덱", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (무손실)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "스캔한 PDF와 사진의 글자를 읽어요: 검색 가능한 PDF, DOCX, TXT.\n언어는 처음 쓸 때 내려받아요.", "engines.package.ffmpeg.name": "FFmpeg (동영상·오디오)", "engines.package.ffmpeg.description": "동영상·오디오 변환, 동영상 → GIF·정지 이미지, 소리 추출, GIF → MP4를 추가해요.\n동영상과 오디오에만 필요해요.", + "engines.package.whisper.name": "Whisper (AI 자막)", + "engines.package.whisper.description": "동영상과 오디오의 말소리를 SRT 자막 파일로 바꿔요. 인터넷 연결 없이 작동해요.", "engines.package.calibre.name": "Calibre (킨들과 드문 전자책 형식)", "engines.package.calibre.description": "킨들용 MOBI·AZW3로 저장하고, 드문 전자책 형식(LIT, LRF, PDB, CHM 등)을 변환해요.\nEPUB·MOBI·AZW3·FB2와 만화책은 없어도 읽어요.", "engines.package.size": "다운로드 {0} · 설치 후 {1}", diff --git a/src/Filee.App/Assets/i18n/ne.json b/src/Filee.App/Assets/i18n/ne.json index cd3083151..e7ece85b0 100644 --- a/src/Filee.App/Assets/i18n/ne.json +++ b/src/Filee.App/Assets/i18n/ne.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "उपशीर्षकहरू: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD र SCC एकअर्कामा, UTF-8 मा सुरक्षित गरिन्छ।\nCP949 / EUC-KR मा कोरियाली SMI र SRT फाइलहरू सही रूपमा पढ्छ।", "engine.ffmpeg.description": "भिडियो र अडियो: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC र थप।\nध्वनि र उपशीर्षक पनि निकाल्छ, GIF र APNG एनिमेसन बनाउँछ र छविहरूलाई HEIC मा सुरक्षित गर्दछ।", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "OpenAI Whisper द्वारा सञ्चालित स्पीच-टु-टेक्स्ट ट्रान्सक्रिप्सन। भिडियो र अडियोमा भएको भाषणबाट स्वचालित रूपमा SRT उपशीर्षकहरू उत्पन्न गर्दछ।", "engine.pandoc.description": "Markdown र HTML ↔ ODT र RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter र थप ↔ Markdown, HTML, DOCX र ODT\nMarkdown र Org → PPTX स्लाइडहरू", "engine.ghostscript.description": "EPS, PostScript र पुराना Illustrator फाइलहरूलाई PDF र छविहरूमा; PDF लाई EPS र PS मा।", "engine.tesseract.description": "स्क्यान र तस्बिरको पाठ पहिचान गर्छ (OCR): खोज्न मिल्ने PDF, DOCX, TXT।", @@ -409,6 +411,8 @@ "preset.extract": "निकाल्नुहोस्", "preset.zip_all": "एउटा ZIP", "preset.extract_subtitles": "उपशीर्षकहरू निकाल्नुहोस्", + "preset.caption_video": "उपशीर्षक", + "preset.caption_video.donut": "उपशीर्षक", "preset.trace_svg": "SVG मा ट्रेस गर्नुहोस्", "preset.trace_svg.donut": "SVG मा ट्रेस गर्नुहोस्", "preset.trace_line_art": "लाइन आर्टबाट SVG", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (सबैभन्दा सानो, सुस्त)", "presets.hardware_encoding": "हार्डवेयर इन्कोडिङ प्रयोग गर्नुहोस्", "presets.hardware_encoding_hint": "H.264, HEVC र AV1 लाई ग्राफिक्स चिपमा इन्कोड गर्दछ।\nधेरै छिटो हुन्छ र कम पावर खपत गर्छ।\nसमान फाइल साइजमा गुणस्तर थोरै कम हुन्छ।\nप्रयोगयोग्य चिप नभएमा (AV1 का लागि नयाँ चिप चाहिन्छ), भिडियो सामान्य तरिकाले इन्कोड हुन्छ।", + "presets.caption_video": "उपशीर्षकहरू सिर्जना गर्नुहोस् (Whisper)", + "presets.caption_video_hint": "Whisper प्रयोग गरी भिडियो र अडियोमा भएको भाषणलाई SRT उपशीर्षक फाइलमा ट्रान्सक्राइब गर्दछ।", + "presets.spoken_language": "बोलिने भाषा", + "presets.spoken_language.auto": "स्वचालित", "presets.audio_codec": "अडियो कोडेक", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (लसलेस)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "स्क्यान गरिएका PDF र तस्बिरको पाठ पढ्छ: खोज्न मिल्ने PDF, DOCX, TXT।\nहरेक भाषा पहिलो पटक प्रयोग गर्दा डाउनलोड हुन्छ।", "engines.package.ffmpeg.name": "FFmpeg (भिडियो र अडियो)", "engines.package.ffmpeg.description": "भिडियो र अडियो रूपान्तरण, भिडियो → GIF वा स्थिर छवि, भिडियोबाट ध्वनि र GIF → MP4 थप्छ।\nकेवल भिडियो र अडियोका लागि मात्र आवश्यक छ।", + "engines.package.whisper.name": "Whisper (AI उपशीर्षकहरू)", + "engines.package.whisper.description": "भिडियो र अडियोमा भएको भाषणलाई SRT उपशीर्षक फाइलहरूमा ट्रान्सक्राइब गर्दछ। इन्टरनेट जडान बिना अफलाइन काम गर्दछ।", "engines.package.calibre.name": "Calibre (Kindle र दुर्लभ इ-पुस्तक ढाँचाहरू)", "engines.package.calibre.description": "Kindle का लागि MOBI र AZW3 सुरक्षित गर्दछ र दुर्लभ इ-पुस्तक ढाँचाहरू (LIT, LRF, PDB, CHM…) रूपान्तरण गर्दछ।\nEPUB, MOBI, AZW3, FB2 र कमिक्स यस बिना पनि पढ्न सकिन्छ।", "engines.package.size": "डाउनलोड {0} · डिस्कमा {1}", diff --git a/src/Filee.App/Assets/i18n/pl.json b/src/Filee.App/Assets/i18n/pl.json index 67c789ec2..3cde8246d 100644 --- a/src/Filee.App/Assets/i18n/pl.json +++ b/src/Filee.App/Assets/i18n/pl.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "Napisy: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD i SCC między sobą, zapisywane w UTF-8.\nKoreańskie pliki SMI i SRT w CP949 / EUC-KR są odczytywane poprawnie.", "engine.ffmpeg.description": "Wideo i audio: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC i inne.\nWyodrębnia też dźwięk i napisy, tworzy animacje GIF i APNG oraz zapisuje obrazy jako HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Transkrypcja mowy na tekst oparta na OpenAI Whisper. Automatycznie tworzy napisy SRT z mowy w plikach wideo i audio.", "engine.pandoc.description": "Markdown i HTML ↔ ODT i RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter i inne ↔ Markdown, HTML, DOCX i ODT\nMarkdown i Org → slajdy PPTX", "engine.ghostscript.description": "EPS, PostScript i starsze pliki Illustratora do PDF i obrazów; PDF do EPS i PS.", "engine.tesseract.description": "Rozpoznaje tekst skanów i obrazów (OCR): przeszukiwalny PDF, DOCX, TXT.", @@ -419,6 +421,8 @@ "preset.extract": "Rozpakuj", "preset.zip_all": "Jeden ZIP", "preset.extract_subtitles": "Wyodrębnij napisy", + "preset.caption_video": "Napisy", + "preset.caption_video.donut": "Napisy", "preset.trace_svg": "Wektoryzuj do SVG", "preset.trace_svg.donut": "Wektoryzuj do SVG", "preset.trace_line_art": "Rysunek do SVG", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1 (najmniejszy, wolny)", "presets.hardware_encoding": "Użyj akceleracji sprzętowej (karta graficzna)", "presets.hardware_encoding_hint": "Koduje H.264, HEVC i AV1 na karcie graficznej.\nZnacznie szybciej i przy mniejszym zużyciu energii.\nNieco niższa jakość przy tym samym rozmiarze pliku.\nBez obsługiwanej karty (AV1 wymaga nowszej) wideo jest kodowane jak zwykle.", + "presets.caption_video": "Generuj napisy (Whisper)", + "presets.caption_video_hint": "Transkrybuje mowę z filmów i nagrań audio do pliku napisów SRT za pomocą Whisper.", + "presets.spoken_language": "Język mówiony", + "presets.spoken_language.auto": "Automatycznie", "presets.audio_codec": "Kodek audio", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (bezstratny)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "Odczytuje tekst zeskanowanych PDF i obrazów: przeszukiwalny PDF, DOCX, TXT.\nKażdy język jest pobierany przy pierwszym użyciu.", "engines.package.ffmpeg.name": "FFmpeg (wideo i audio)", "engines.package.ffmpeg.description": "Dodaje konwersję wideo i audio, wideo → GIF lub klatka, dźwięk z wideo i GIF → MP4.\nPotrzebny tylko do wideo i audio.", + "engines.package.whisper.name": "Whisper (napisy SI)", + "engines.package.whisper.description": "Transkrybuje mowę z filmów i nagrań audio do plików napisów SRT. Działa offline bez połączenia z internetem.", "engines.package.calibre.name": "Calibre (Kindle i rzadkie formaty e-booków)", "engines.package.calibre.description": "Zapisuje MOBI i AZW3 dla Kindle i konwertuje rzadkie formaty e-booków (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 i komiksy są odczytywane bez niego.", "engines.package.size": "Pobieranie {0} · {1} na dysku", diff --git a/src/Filee.App/Assets/i18n/pt-BR.json b/src/Filee.App/Assets/i18n/pt-BR.json index 8736951e2..c04db90d3 100644 --- a/src/Filee.App/Assets/i18n/pt-BR.json +++ b/src/Filee.App/Assets/i18n/pt-BR.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Legendas (mecanismo integrado)", "engine.subtitle.description": "Legendas: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD e SCC entre si, salvas em UTF-8.\nArquivos SMI e SRT em coreano, em CP949 / EUC-KR, são lidos corretamente.", "engine.ffmpeg.description": "Vídeo e áudio: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC e mais.\nTambém extrai som e legendas, faz animações GIF e APNG e salva imagens como HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Transcrição de voz para texto com tecnologia OpenAI Whisper. Gera legendas SRT automaticamente a partir da fala em vídeo e áudio.", "engine.pandoc.description": "Markdown e HTML ↔ ODT e RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter e mais ↔ Markdown, HTML, DOCX e ODT\nMarkdown e Org → slides PPTX", "engine.ghostscript.description": "EPS, PostScript e arquivos antigos do Illustrator para PDF e imagens; PDF para EPS e PS.", "engine.tesseract.description": "Reconhece o texto de digitalizações e imagens (OCR): PDF pesquisável, DOCX, TXT.", @@ -419,6 +421,8 @@ "preset.extract": "Extrair", "preset.zip_all": "Um ZIP", "preset.extract_subtitles": "Extrair legendas", + "preset.caption_video": "Legendas", + "preset.caption_video.donut": "Legendas", "preset.trace_svg": "Vetorizar em SVG", "preset.trace_svg.donut": "Vetorizar", "preset.trace_line_art": "Traço em SVG", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1 (menor, lento)", "presets.hardware_encoding": "Usar aceleração de hardware (chip gráfico)", "presets.hardware_encoding_hint": "Codifica H.264, HEVC e AV1 no chip gráfico.\nBem mais rápido e gasta menos energia.\nQualidade um pouco menor no mesmo tamanho de arquivo.\nSem um chip utilizável (o AV1 precisa de um recente), o vídeo é codificado como de costume.", + "presets.caption_video": "Gerar legendas (Whisper)", + "presets.caption_video_hint": "Transcreve a fala de vídeos e áudios para um arquivo de legendas SRT usando Whisper.", + "presets.spoken_language": "Idioma falado", + "presets.spoken_language.auto": "Automático", "presets.audio_codec": "Codec de áudio", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (sem perdas)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "Lê o texto de PDFs digitalizados e imagens: PDF pesquisável, DOCX, TXT.\nCada idioma é baixado na primeira vez que é usado.", "engines.package.ffmpeg.name": "FFmpeg (vídeo e áudio)", "engines.package.ffmpeg.description": "Adiciona conversão de vídeo e áudio, vídeo → GIF ou imagem parada, som de vídeos e GIF → MP4.\nSó é necessário para vídeo e áudio.", + "engines.package.whisper.name": "Whisper (legendas com IA)", + "engines.package.whisper.description": "Transcreve a fala de vídeos e áudios para arquivos de legendas SRT. Funciona offline sem conexão com a internet.", "engines.package.calibre.name": "Calibre (Kindle e formatos raros de e-book)", "engines.package.calibre.description": "Salva MOBI e AZW3 para Kindle e converte formatos raros de e-book (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 e quadrinhos são lidos sem ele.", "engines.package.size": "Download de {0} · {1} em disco", diff --git a/src/Filee.App/Assets/i18n/pt-PT.json b/src/Filee.App/Assets/i18n/pt-PT.json index 36162e340..d6cd14982 100644 --- a/src/Filee.App/Assets/i18n/pt-PT.json +++ b/src/Filee.App/Assets/i18n/pt-PT.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Legendas (motor integrado)", "engine.subtitle.description": "Converte legendas entre SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD e SCC, guardadas em UTF-8.\nLê corretamente ficheiros SMI e SRT coreanos em CP949 / EUC-KR.", "engine.ffmpeg.description": "Vídeo e áudio: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC e outros.\nTambém extrai som e legendas, cria animações GIF e APNG e guarda imagens em HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Transcrição de voz para texto com tecnologia OpenAI Whisper. Gera legendas SRT automaticamente a partir da fala em vídeo e áudio.", "engine.pandoc.description": "Markdown e HTML ↔ ODT e RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter e outros ↔ Markdown, HTML, DOCX e ODT\nMarkdown e Org → diapositivos PPTX", "engine.ghostscript.description": "EPS, PostScript e ficheiros Illustrator antigos para PDF e imagens; PDF para EPS e PS.", "engine.tesseract.description": "Reconhece o texto de digitalizações e imagens (OCR): PDF pesquisável, DOCX e TXT.", @@ -419,6 +421,8 @@ "preset.extract": "Extrair", "preset.zip_all": "Um só ZIP", "preset.extract_subtitles": "Extrair legendas", + "preset.caption_video": "Legendas", + "preset.caption_video.donut": "Legendas", "preset.trace_svg": "Vetorizar para SVG", "preset.trace_svg.donut": "Vetorizar", "preset.trace_line_art": "Traços para SVG", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1 (menor tamanho, lento)", "presets.hardware_encoding": "Utilizar aceleração por hardware (chip gráfico)", "presets.hardware_encoding_hint": "Codifica H.264, HEVC e AV1 no chip gráfico.\nMuito mais rápido e com menor consumo de energia.\nQualidade ligeiramente inferior para o mesmo tamanho de ficheiro.\nSem um chip compatível (o AV1 exige um chip recente), o vídeo é codificado da forma habitual.", + "presets.caption_video": "Gerar legendas (Whisper)", + "presets.caption_video_hint": "Transcreve a fala de vídeos e áudios para um ficheiro de legendas SRT usando Whisper.", + "presets.spoken_language": "Idioma falado", + "presets.spoken_language.auto": "Automático", "presets.audio_codec": "Codec de áudio", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (sem perdas)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "Lê o texto de PDF digitalizados e de imagens: PDF pesquisável, DOCX e TXT.\nOs dados de cada idioma são descarregados na primeira utilização.", "engines.package.ffmpeg.name": "FFmpeg (vídeo e áudio)", "engines.package.ffmpeg.description": "Adiciona conversão de vídeo e áudio, vídeo → GIF ou imagem fixa, extração de som de vídeos e GIF → MP4.\nSó é necessário para vídeo e áudio.", + "engines.package.whisper.name": "Whisper (legendas com IA)", + "engines.package.whisper.description": "Transcreve a fala de vídeos e áudios para ficheiros de legendas SRT. Funciona offline sem ligação à Internet.", "engines.package.calibre.name": "Calibre (Kindle e formatos raros de livros digitais)", "engines.package.calibre.description": "Guarda em MOBI e AZW3 para Kindle e converte formatos raros de livros digitais (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 e banda desenhada são lidos sem ele.", "engines.package.size": "Transferência de {0} · {1} no disco", diff --git a/src/Filee.App/Assets/i18n/ru.json b/src/Filee.App/Assets/i18n/ru.json index 1855f214e..7c8dccf03 100644 --- a/src/Filee.App/Assets/i18n/ru.json +++ b/src/Filee.App/Assets/i18n/ru.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "Субтитры: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD и SCC друг в друга, с сохранением в UTF-8.\nКорейские SMI и SRT в CP949 / EUC-KR читаются правильно.", "engine.ffmpeg.description": "Видео и аудио: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC и другие.\nА ещё достаёт звук и субтитры, делает анимации GIF и APNG и сохраняет картинки в HEIC.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "Распознавание речи на базе OpenAI Whisper. Автоматически создает субтитры SRT из речи в видео и аудио.", "engine.pandoc.description": "Markdown и HTML ↔ ODT и RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter и другие ↔ Markdown, HTML, DOCX и ODT\nMarkdown и Org → слайды PPTX", "engine.ghostscript.description": "EPS, PostScript и старые файлы Illustrator — в PDF и картинки; PDF — в EPS и PS.", "engine.tesseract.description": "Распознаёт текст сканов и изображений (OCR): PDF с поиском, DOCX, TXT.", @@ -419,6 +421,8 @@ "preset.extract": "Распаковать", "preset.zip_all": "Один ZIP", "preset.extract_subtitles": "Извлечь субтитры", + "preset.caption_video": "Субтитры", + "preset.caption_video.donut": "Субтитры", "preset.trace_svg": "Обвести в SVG", "preset.trace_svg.donut": "Обвести в SVG", "preset.trace_line_art": "Линии в SVG", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1 (ещё меньше, медленно)", "presets.hardware_encoding": "Аппаратное ускорение (видеочип)", "presets.hardware_encoding_hint": "Кодирует H.264, HEVC и AV1 на видеочипе.\nНамного быстрее и тратит меньше энергии.\nКачество чуть ниже при том же размере файла.\nЕсли подходящего чипа нет (для AV1 нужен современный), видео кодируется как обычно.", + "presets.caption_video": "Создать субтитры (Whisper)", + "presets.caption_video_hint": "Преобразует речь из видео и аудио в файл субтитров SRT с помощью Whisper.", + "presets.spoken_language": "Язык речи", + "presets.spoken_language.auto": "Авто", "presets.audio_codec": "Аудиокодек", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (без потерь)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "Читает текст отсканированных PDF и изображений: PDF с поиском, DOCX, TXT.\nКаждый язык скачивается при первом использовании.", "engines.package.ffmpeg.name": "FFmpeg (видео и аудио)", "engines.package.ffmpeg.description": "Добавляет конвертацию видео и аудио, видео → GIF или стоп-кадр, звук из видео и GIF → MP4.\nНужен только для видео и аудио.", + "engines.package.whisper.name": "Whisper (ИИ-субтитры)", + "engines.package.whisper.description": "Преобразует речь из видео и аудио в файлы субтитров SRT. Работает автономно без подключения к Интернету.", "engines.package.calibre.name": "Calibre (Kindle и редкие форматы книг)", "engines.package.calibre.description": "Сохраняет MOBI и AZW3 для Kindle и конвертирует редкие форматы электронных книг (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 и комиксы читаются и без него.", "engines.package.size": "Загрузка: {0} · на диске: {1}", diff --git a/src/Filee.App/Assets/i18n/th.json b/src/Filee.App/Assets/i18n/th.json index 538664d14..7e7b19eb0 100644 --- a/src/Filee.App/Assets/i18n/th.json +++ b/src/Filee.App/Assets/i18n/th.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "คำบรรยาย: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD และ SCC สลับกันได้ บันทึกเป็น UTF-8\nอ่านไฟล์ SMI และ SRT ภาษาเกาหลีในรหัส CP949 / EUC-KR ได้ถูกต้อง", "engine.ffmpeg.description": "วิดีโอและเสียง: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC และอื่นๆ\nรวมถึงแยกเสียงและคำบรรยาย สร้างภาพเคลื่อนไหว GIF และ APNG และบันทึกภาพเป็น HEIC", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "การถอดเสียงเป็นข้อความขับเคลื่อนโดย OpenAI Whisper สร้างคำบรรยาย SRT จากคำพูดในวิดีโอและเสียงโดยอัตโนมัติ", "engine.pandoc.description": "Markdown และ HTML ↔ ODT และ RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter และอื่นๆ ↔ Markdown, HTML, DOCX และ ODT\nMarkdown และ Org → สไลด์ PPTX", "engine.ghostscript.description": "EPS, PostScript และไฟล์ Illustrator รุ่นเก่าเป็น PDF และรูปภาพ แปลง PDF เป็น EPS และ PS", "engine.tesseract.description": "อ่านข้อความจากภาพสแกนและรูปภาพ (OCR): PDF ที่ค้นหาได้, DOCX, TXT", @@ -409,6 +411,8 @@ "preset.extract": "แตกไฟล์", "preset.zip_all": "รวมเป็น ZIP เดียว", "preset.extract_subtitles": "แยกคำบรรยาย", + "preset.caption_video": "คำบรรยาย", + "preset.caption_video.donut": "คำบรรยาย", "preset.trace_svg": "แปลงเป็น SVG", "preset.trace_svg.donut": "แปลงเป็น SVG", "preset.trace_line_art": "แปลงลายเส้นเป็น SVG", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (ขนาดเล็กที่สุด, ช้า)", "presets.hardware_encoding": "ใช้การเร่งความเร็วด้วยฮาร์ดแวร์ (ชิปกราฟิก)", "presets.hardware_encoding_hint": "เข้ารหัส H.264, HEVC และ AV1 บนชิปกราฟิก\nเร็วกว่ามากและประหยัดพลังงาน\nคุณภาพอาจลดลงเล็กน้อยเมื่อเทียบกับขนาดไฟล์เดียวกัน\nหากไม่มีชิปที่รองรับ (AV1 ต้องใช้ชิปรุ่นใหม่) วิดีโอจะเข้ารหัสตามปกติ", + "presets.caption_video": "สร้างคำบรรยาย (Whisper)", + "presets.caption_video_hint": "ถอดเสียงคำพูดในวิดีโอและเสียงเป็นไฟล์คำบรรยาย SRT โดยใช้ Whisper", + "presets.spoken_language": "ภาษาพูด", + "presets.spoken_language.auto": "อัตโนมัติ", "presets.audio_codec": "โคเดกเสียง", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (ไม่สูญเสียรายละเอียด)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "อ่านข้อความจาก PDF ที่สแกนและรูปภาพ: PDF ที่ค้นหาได้, DOCX, TXT\nแต่ละภาษาจะดาวน์โหลดเมื่อใช้ครั้งแรก", "engines.package.ffmpeg.name": "FFmpeg (วิดีโอและเสียง)", "engines.package.ffmpeg.description": "เพิ่มการแปลงวิดีโอและเสียง วิดีโอ → GIF หรือภาพนิ่ง แยกเสียงจากวิดีโอ และ GIF → MP4\n(จำเป็นเฉพาะสำหรับงานวิดีโอและเสียงเท่านั้น)", + "engines.package.whisper.name": "Whisper (คำบรรยาย AI)", + "engines.package.whisper.description": "ถอดเสียงคำพูดในวิดีโอและเสียงเป็นไฟล์คำบรรยาย SRT ทำงานแบบออฟไลน์โดยไม่ต้องเชื่อมต่ออินเทอร์เน็ต", "engines.package.calibre.name": "Calibre (Kindle และอีบุ๊กหายาก)", "engines.package.calibre.description": "บันทึกเป็น MOBI และ AZW3 สำหรับ Kindle และแปลงฟอร์แมตอีบุ๊กหายาก (LIT, LRF, PDB, CHM…)\n(EPUB, MOBI, AZW3, FB2 และการ์ตูนสามารถอ่านได้โดยไม่ต้องติดตั้ง)", "engines.package.size": "ดาวน์โหลด {0} · ขนาดบนดิสก์ {1}", diff --git a/src/Filee.App/Assets/i18n/tr.json b/src/Filee.App/Assets/i18n/tr.json index 2808bfa64..8d74f8d44 100644 --- a/src/Filee.App/Assets/i18n/tr.json +++ b/src/Filee.App/Assets/i18n/tr.json @@ -206,6 +206,8 @@ "engine.subtitle.name": "Altyazılar (yerleşik)", "engine.subtitle.description": "Altyazılar: SRT, ASS/SSA, WebVTT, SAMI (SMI), LRC, TTML, MicroDVD ve SCC arasında dönüştürme; UTF-8 olarak kaydedilir.\nCP949 / EUC-KR kodlamalı Korece SMI ve SRT dosyalarını doğru okur.", "engine.ffmpeg.description": "Video ve ses: MP4, MOV, MKV, WEBM, MP3, M4A, WAV, FLAC ve daha fazlası.\nSes ve altyazı da çıkarır; GIF ve APNG animasyonları oluşturur, görselleri HEIC olarak kaydeder.", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "OpenAI Whisper destekli sesten metne transkripsiyon. Video ve sesteki konuşmalardan otomatik olarak SRT altyazıları oluşturur.", "engine.pandoc.description": "Markdown ve HTML ↔ ODT ve RTF, DOCX → Markdown\nreStructuredText, LaTeX, Org, AsciiDoc, Typst, MediaWiki, DocBook, Jupyter ve daha fazlası ↔ Markdown, HTML, DOCX ve ODT\nMarkdown ve Org → PPTX slaytları", "engine.ghostscript.description": "EPS, PostScript ve eski Illustrator dosyaları → PDF ve görseller; PDF → EPS ve PS.", "engine.tesseract.description": "Tarama ve görsellerdeki metni tanır (OCR): aranabilir PDF, DOCX, TXT.", @@ -409,6 +411,8 @@ "preset.extract": "Arşivi aç", "preset.zip_all": "Tek ZIP", "preset.extract_subtitles": "Altyazıları çıkar", + "preset.caption_video": "Altyazı", + "preset.caption_video.donut": "Altyazı", "preset.trace_svg": "SVG'ye vektörleştir", "preset.trace_svg.donut": "SVG'ye vektörleştir", "preset.trace_line_art": "Çizgileri SVG'ye dönüştür", @@ -840,6 +844,10 @@ "presets.video_codec.av1": "AV1 (en küçük, yavaş)", "presets.hardware_encoding": "Donanım hızlandırması kullan (grafik işlemcisi)", "presets.hardware_encoding_hint": "H.264, HEVC ve AV1'i grafik işlemcisinde kodlar.\nÇok daha hızlıdır ve daha az güç tüketir.\nAynı dosya boyutunda kalite biraz daha düşüktür.\nUygun işlemci yoksa (AV1 için yeni bir model gerekir) video normal şekilde kodlanır.", + "presets.caption_video": "Altyazı oluştur (Whisper)", + "presets.caption_video_hint": "Whisper kullanarak video ve sesteki konuşmaları bir SRT altyazı dosyasına dönüştürür.", + "presets.spoken_language": "Konuşulan dil", + "presets.spoken_language.auto": "Otomatik", "presets.audio_codec": "Ses codec'i", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC (kayıpsız)", @@ -962,6 +970,8 @@ "engines.package.tesseract.description": "Taranmış PDF ve görsellerdeki metni okur: aranabilir PDF, DOCX, TXT.\nHer dil ilk kullanıldığında indirilir.", "engines.package.ffmpeg.name": "FFmpeg (video ve ses)", "engines.package.ffmpeg.description": "Video ve ses dönüştürme, video → GIF veya durağan görsel, videodan ses çıkarma ve GIF → MP4 ekler.\nYalnızca video ve ses için gereklidir.", + "engines.package.whisper.name": "Whisper (YZ Altyazı)", + "engines.package.whisper.description": "Video ve sesteki konuşmaları SRT altyazı dosyalarına dönüştürür. İnternet bağlantısı olmadan çevrimdışı çalışır.", "engines.package.calibre.name": "Calibre (Kindle ve nadir e-kitap biçimleri)", "engines.package.calibre.description": "Kindle için MOBI ve AZW3 kaydeder; nadir e-kitap biçimlerini dönüştürür (LIT, LRF, PDB, CHM…).\nEPUB, MOBI, AZW3, FB2 ve çizgi romanlar bu motor olmadan okunur.", "engines.package.size": "İndirme {0} · Diskte {1}", diff --git a/src/Filee.App/Assets/i18n/zh-CN.json b/src/Filee.App/Assets/i18n/zh-CN.json index 2f0272bb8..87f876055 100644 --- a/src/Filee.App/Assets/i18n/zh-CN.json +++ b/src/Filee.App/Assets/i18n/zh-CN.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "SRT、ASS/SSA、WebVTT、SAMI(SMI)、LRC、TTML、MicroDVD、SCC 字幕互转,保存为 UTF-8。\n也能正确读取 CP949/EUC-KR 编码的韩文字幕。", "engine.ffmpeg.description": "MP4、MOV、MKV、WEBM、MP3、M4A、WAV、FLAC 等视频和音频。\n还能提取声音和字幕、制作 GIF 和 APNG、把图片保存为 HEIC。", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "基于 OpenAI Whisper 的语音转文本。自动将视频和音频中的语音生成为 SRT 字幕。", "engine.pandoc.description": "Markdown 和 HTML ↔ ODT、RTF,DOCX → Markdown\nreStructuredText、LaTeX、Org、AsciiDoc、Typst、MediaWiki、DocBook、Jupyter 等 ↔ Markdown、HTML、DOCX、ODT\nMarkdown 和 Org → PPTX 幻灯片", "engine.ghostscript.description": "EPS、PostScript 和旧版 Illustrator 文件转为 PDF 和图片;PDF 转为 EPS 和 PS。", "engine.tesseract.description": "识别扫描件和图片中的文字(OCR):可搜索的 PDF、DOCX、TXT。", @@ -419,6 +421,8 @@ "preset.extract": "解压", "preset.zip_all": "打包成一个 ZIP", "preset.extract_subtitles": "提取字幕", + "preset.caption_video": "字幕", + "preset.caption_video.donut": "字幕", "preset.trace_svg": "描摹", "preset.trace_svg.donut": "描摹", "preset.trace_line_art": "黑白描摹", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1(最小,较慢)", "presets.hardware_encoding": "使用硬件加速(显卡)", "presets.hardware_encoding_hint": "用显卡编码 H.264、HEVC 和 AV1。\n速度快得多,也更省电。\n相同文件大小时画质略低。\n没有可用的显卡时(AV1 需要较新的显卡)照常编码。", + "presets.caption_video": "生成字幕 (Whisper)", + "presets.caption_video_hint": "使用 Whisper 将视频和音频中的语音转录为 SRT 字幕文件。", + "presets.spoken_language": "语音语言", + "presets.spoken_language.auto": "自动", "presets.audio_codec": "音频编码", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC(无损)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "读取扫描 PDF 和图片中的文字:可搜索的 PDF、DOCX、TXT。\n每种语言在首次使用时下载。", "engines.package.ffmpeg.name": "FFmpeg(视频和音频)", "engines.package.ffmpeg.description": "增加视频和音频转换、视频 → GIF 或静态图片、提取声音以及 GIF → MP4。\n仅视频和音频转换需要它。", + "engines.package.whisper.name": "Whisper (AI字幕)", + "engines.package.whisper.description": "将视频和音频中的语音转录为 SRT 字幕文件。无需互联网连接即可离线运行。", "engines.package.calibre.name": "Calibre(Kindle 与少见电子书格式)", "engines.package.calibre.description": "可保存为 Kindle 用的 MOBI 和 AZW3,并转换少见的电子书格式(LIT、LRF、PDB、CHM 等)。\nEPUB、MOBI、AZW3、FB2 和漫画无需它即可读取。", "engines.package.size": "下载 {0} · 占用 {1}", diff --git a/src/Filee.App/Assets/i18n/zh-TW.json b/src/Filee.App/Assets/i18n/zh-TW.json index 40453d5cd..f6308192c 100644 --- a/src/Filee.App/Assets/i18n/zh-TW.json +++ b/src/Filee.App/Assets/i18n/zh-TW.json @@ -213,6 +213,8 @@ "engine.subtitle.name": "Subtitles (built-in)", "engine.subtitle.description": "SRT、ASS/SSA、WebVTT、SAMI(SMI)、LRC、TTML、MicroDVD、SCC 字幕互轉,儲存為 UTF-8。\n也能正確讀取 CP949/EUC-KR 編碼的韓文字幕。", "engine.ffmpeg.description": "MP4、MOV、MKV、WEBM、MP3、M4A、WAV、FLAC 等影片和音訊。\n還能擷取聲音和字幕、製作 GIF 和 APNG、把圖片儲存為 HEIC。", + "engine.whisper.name": "Whisper", + "engine.whisper.description": "以 OpenAI Whisper 驅動的語音轉文字。自動將影片與音訊中的語音產生為 SRT 字幕。", "engine.pandoc.description": "Markdown 和 HTML ↔ ODT、RTF,DOCX → Markdown\nreStructuredText、LaTeX、Org、AsciiDoc、Typst、MediaWiki、DocBook、Jupyter 等 ↔ Markdown、HTML、DOCX、ODT\nMarkdown 和 Org → PPTX 投影片", "engine.ghostscript.description": "EPS、PostScript 和舊版 Illustrator 檔案轉為 PDF 和圖片;PDF 轉為 EPS 和 PS。", "engine.tesseract.description": "辨識掃描檔和圖片中的文字(OCR):可搜尋的 PDF、DOCX、TXT。", @@ -419,6 +421,8 @@ "preset.extract": "解壓縮", "preset.zip_all": "打包成一個 ZIP", "preset.extract_subtitles": "擷取字幕", + "preset.caption_video": "字幕", + "preset.caption_video.donut": "字幕", "preset.trace_svg": "描摹", "preset.trace_svg.donut": "描摹", "preset.trace_line_art": "黑白描摹", @@ -855,6 +859,10 @@ "presets.video_codec.av1": "AV1(最小,較慢)", "presets.hardware_encoding": "使用硬體加速(顯示卡)", "presets.hardware_encoding_hint": "用顯示卡編碼 H.264、HEVC 和 AV1。\n速度快得多,也更省電。\n相同檔案大小時畫質略低。\n沒有可用的顯示卡時(AV1 需要較新的顯示卡)照常編碼。", + "presets.caption_video": "產生字幕 (Whisper)", + "presets.caption_video_hint": "使用 Whisper 將影片與音訊中的語音轉錄為 SRT 字幕檔。", + "presets.spoken_language": "語音語言", + "presets.spoken_language.auto": "自動", "presets.audio_codec": "音訊編碼", "presets.audio_codec.aac": "AAC", "presets.audio_codec.alac": "ALAC(無損)", @@ -979,6 +987,8 @@ "engines.package.tesseract.description": "讀取掃描 PDF 和圖片中的文字:可搜尋的 PDF、DOCX、TXT。\n每種語言在第一次使用時下載。", "engines.package.ffmpeg.name": "FFmpeg(影片和音訊)", "engines.package.ffmpeg.description": "增加影片和音訊轉換、影片 → GIF 或靜態圖片、擷取聲音以及 GIF → MP4。\n僅影片和音訊轉換需要它。", + "engines.package.whisper.name": "Whisper (AI字幕)", + "engines.package.whisper.description": "將影片與音訊中的語音轉錄為 SRT 字幕檔。無需網際網路連線即可離線執行。", "engines.package.calibre.name": "Calibre(Kindle 與少見電子書格式)", "engines.package.calibre.description": "可儲存為 Kindle 用的 MOBI 和 AZW3,並轉換少見的電子書格式(LIT、LRF、PDB、CHM 等)。\nEPUB、MOBI、AZW3、FB2 和漫畫無需它即可讀取。", "engines.package.size": "下載 {0} · 佔用 {1}", diff --git a/src/Filee.App/Services/PresetAvailability.cs b/src/Filee.App/Services/PresetAvailability.cs index f24514078..7f3072bc1 100644 --- a/src/Filee.App/Services/PresetAvailability.cs +++ b/src/Filee.App/Services/PresetAvailability.cs @@ -195,6 +195,23 @@ private PresetJudgement Evaluate(Preset preset, IReadOnlyList format : new(PresetUse.Unusable, _loc["error.no_route"]); } + // "Caption": Whisper transcribes speech in video and audio into SRT subtitles. + if (formats.Any(f => preset.CaptionsVideo(f.Id))) + { + if (!OperatingSystem.IsWindows()) + return new(PresetUse.Unusable, _loc["engine.reason.unsupported_platform"]); + + var mediaFiles = formats.Where(f => f.Category is FormatCategory.Video or FormatCategory.Audio).ToList(); + if (mediaFiles.Count == 0) + return new(PresetUse.Unusable, _loc["error.unsupported_source"]); + bool CanCaption(RoutePlanner planner) => mediaFiles.Any(f => planner.CanConvert(f.Id, preset.TargetFormat, caption: true)); + if (CanCaption(installedPlanner())) + return new(PresetUse.Usable, null); + return packageEnabling(CanCaption) is { } needed + ? new(PresetUse.NeedsPackage, _loc.Format("engine.reason.needs_package", needed.Name)) + : new(PresetUse.Unusable, _loc["error.no_route"]); + } + // "Merge videos" / "Merge audio" / "Video from images": FFmpeg joins the files, so a route into the target (or, // for pictures, into PNG and from PNG into a video) says whether it is installed. if (preset.JoinsIntoOneMedia) diff --git a/src/Filee.App/ViewModels/PresetEditorViewModel.cs b/src/Filee.App/ViewModels/PresetEditorViewModel.cs index a7fc5460c..ace632fc8 100644 --- a/src/Filee.App/ViewModels/PresetEditorViewModel.cs +++ b/src/Filee.App/ViewModels/PresetEditorViewModel.cs @@ -122,6 +122,13 @@ .. FormatRegistry.Known // A speed set outside the editor (a hand-edited preset file) is offered too, instead of being lost. AnimationSpeeds = [.. ImageOptions.AnimationSpeeds.Append(_edit.Image.PlaybackSpeed).Distinct().Order() .Select(s => new Choice(s, s == 1 ? loc["presets.animation_speed.original"] : SpeedLabel(s)))]; + SpokenLanguageChoices = + [ + new Choice(WhisperLanguages.Auto, loc["presets.spoken_language.auto"]), + .. WhisperLanguages.All + .OrderBy(l => l.DisplayName, StringComparer.CurrentCulture) + .Select(l => new Choice(l.Code, l.DisplayName)) + ]; _name = string.IsNullOrWhiteSpace(_edit.Name) ? loc.DisplayName(_edit) : _edit.Name; _target = Targets.FirstOrDefault(t => t.Value == _edit.TargetFormat) ?? Targets[1]; @@ -153,6 +160,9 @@ .. FormatRegistry.Known _videoCodecValue = _edit.Media.VideoCodec; _audioCodecValue = _edit.Media.AudioCodec; _hardwareEncoding = _edit.Media.HardwareEncoding; + _caption = _edit.Media.Caption; + _spokenLanguage = SpokenLanguageChoices.FirstOrDefault(l => string.Equals(l.Value, _edit.Media.SpokenLanguage, StringComparison.OrdinalIgnoreCase)) + ?? SpokenLanguageChoices[0]; _archiveLevel = ArchiveLevels.First(l => l.Value == _edit.Archive.Level); _combineIntoOne = _edit.Archive.CombineIntoOne; _saveEveryFrame = _edit.Media.SaveEveryFrame; @@ -231,6 +241,8 @@ .. FormatRegistry.Known /// Languages Tesseract reads (PdfOptions.OcrLanguage), English first. public IReadOnlyList> OcrLanguageChoices { get; } + /// The spoken languages Whisper recognizes (MediaOptions.SpokenLanguage), Auto first. + public IReadOnlyList> SpokenLanguageChoices { get; } /// What "Recognize text" needs: Tesseract, or on a Mac Apple Vision (Tesseract only for other languages). public string OcrHint { get; } @@ -270,7 +282,7 @@ .. FormatRegistry.Known nameof(ShowZipResult), nameof(ShowPdf), nameof(ShowPdfBuild), nameof(ShowDocument), nameof(ShowLeaveOutPictures), nameof(ShowPdfSplit), nameof(ShowPdfCombine), nameof(ShowPdfCompression), nameof(ShowPdfCompressionOptions), nameof(ShowOcr), nameof(ShowPageSize), nameof(ShowPdfTablesNote), nameof(ShowMedia), nameof(ShowMediaQuality), nameof(ShowMaxHeight), nameof(ShowVideoMaxSize), nameof(ShowAudioBitrate), nameof(ShowRemoveAudio), - nameof(ShowAudioChannels), nameof(ShowWav), nameof(VideoCodecs), nameof(VideoCodecChoice), nameof(ShowVideoCodec), nameof(ShowHardwareEncoding), + nameof(ShowAudioChannels), nameof(ShowWav), nameof(VideoCodecs), nameof(VideoCodecChoice), nameof(ShowVideoCodec), nameof(ShowHardwareEncoding), nameof(ShowCaption), nameof(AudioCodecs), nameof(AudioCodecChoice), nameof(ShowAudioCodec), nameof(ShowArchive), nameof(ShowArchiveOptions), nameof(ShowExtractNote), nameof(ShowSaveEveryFrame), nameof(ShowAnimationSpeed), nameof(ShowTrace), nameof(ShowCad), nameof(ShowCadColors), nameof(ShowExtractMotionPhoto), nameof(ShowSlideshow), @@ -547,6 +559,10 @@ partial void OnPdfPasswordChanged(string value) /// Encode H.264 / HEVC / AV1 on the graphics chip when one works (MediaOptions.HardwareEncoding). [ObservableProperty] private bool _hardwareEncoding; + /// Burn subtitles from speech recognized by Whisper AI (MediaOptions.Caption). + [ObservableProperty] private bool _caption; + /// Language spoken in the audio for Whisper transcription (MediaOptions.SpokenLanguage). + [ObservableProperty] private Choice _spokenLanguage; [ObservableProperty] private Choice _archiveLevel; [ObservableProperty] private bool _combineIntoOne; [ObservableProperty] private bool _saveEveryFrame; @@ -676,7 +692,7 @@ partial void OnAnimationSpeedChanged(Choice? oldValue, Choice ne public bool ShowDocument => TargetId == "pdf"; /// Markdown can't hold pictures: they go into a folder next to it, or are left out. public bool ShowLeaveOutPictures => TargetId == "md"; - public bool ShowMedia => ShowMediaQuality || ShowMaxHeight || ShowRemoveAudio || ShowAudioChannels || ShowVideoCodec || ShowAudioCodec; + public bool ShowMedia => ShowMediaQuality || ShowMaxHeight || ShowRemoveAudio || ShowAudioChannels || ShowVideoCodec || ShowAudioCodec || ShowCaption; /// Mono / stereo for every audio target but AMR, which is mono by definition. public bool ShowAudioChannels => TargetIsAudio && TargetId != "amr"; /// Sample rate and sample format: the PCM of WAV and W64 takes any. @@ -685,6 +701,8 @@ partial void OnAnimationSpeedChanged(Choice? oldValue, Choice ne public bool ShowVideoCodec => TargetIsVideo && VideoCodecs.Count > 1; /// Hardware encoding applies to H.264, HEVC and AV1 only (not VP9, MPEG-2, …). public bool ShowHardwareEncoding => TargetIsVideo && MediaCodecs.TakesHardwareEncoding(TargetId, _videoCodecValue); + /// Generate Whisper AI subtitles for speech. + public bool ShowCaption => TargetId == "srt"; /// M4A / M4B can hold Apple Lossless besides AAC. public bool ShowAudioCodec => AudioCodecs.Count > 1; public bool ShowMediaQuality => TargetIsLossyMedia; @@ -857,6 +875,8 @@ public bool Apply() : MediaCodecs.Effective(TargetId, _videoCodecValue); media.AudioCodec = MediaCodecs.Effective(TargetId, _audioCodecValue); media.HardwareEncoding = HardwareEncoding; + media.Caption = ShowCaption && Caption; + media.SpokenLanguage = SpokenLanguage.Value; // Only where the option is shown: a box ticked for PNG and left behind by another target saves nothing. media.SaveEveryFrame = ShowSaveEveryFrame && SaveEveryFrame; media.FrameInterval = media.SaveEveryFrame ? Math.Clamp(FrameInterval, 1, 1000) : 1; diff --git a/src/Filee.App/Views/PresetEditorView.axaml b/src/Filee.App/Views/PresetEditorView.axaml index c0dcc27a5..816b7031a 100644 --- a/src/Filee.App/Views/PresetEditorView.axaml +++ b/src/Filee.App/Views/PresetEditorView.axaml @@ -303,6 +303,23 @@ + + + + + + + + + + + + + + + + diff --git a/src/Filee.Core/Conversion/IConverter.cs b/src/Filee.Core/Conversion/IConverter.cs index 7dfbce73e..48fa9dcfc 100644 --- a/src/Filee.Core/Conversion/IConverter.cs +++ b/src/Filee.Core/Conversion/IConverter.cs @@ -30,7 +30,7 @@ namespace Filee.Core.Conversion; /// /// The edge writes every frame of a video as numbered pictures into a folder (). /// It is never part of an ordinary route (a video → JPG stays one still frame), and with -/// asking for frames it is the whole route. +/// asking for frames it is the whole route. /// /// /// The engine draws text one character after the other, left to right, without complex-script shaping (no joined @@ -43,22 +43,25 @@ namespace Filee.Core.Conversion; /// /// The edge takes the video out of a Live / Motion Photo (). It is /// never part of an ordinary route (a photo → MP4 is otherwise a video of the still picture), and with -/// asking for it it is the whole route. +/// asking for it it is the whole route. /// /// /// The edge makes a Live / Motion Photo of a short clip of a video (). It is /// never part of an ordinary route (a video → JPG is otherwise one still frame), and with -/// asking for it it is the whole route. +/// asking for it it is the whole route. /// /// /// The edge recognizes the text of a scan or a picture and writes a searchable PDF (). -/// It is never part of an ordinary route, and with +/// It is never part of an ordinary route, and with /// asking for it the route starts with it. /// +/// +/// The edge transcribes speech to SRT subtitles using Whisper (). +/// public readonly record struct ConversionEdge( string From, string To, int Cost = 10, bool ImageSourcesOnly = false, bool NotForImageSources = false, bool DirectOnly = false, bool FrameSequence = false, bool Unshaped = false, bool MotionPhoto = false, bool LivePhoto = false, - bool Ocr = false); + bool Ocr = false, bool Caption = false); /// /// A conversion an engine refuses with a reason the user can act on, shown in their language (the file's @@ -128,6 +131,7 @@ public interface IOutputAllocator /// The step takes a edge: a clip of the video becomes a Live / Motion Photo /// ( says which kind). /// +/// The step takes a edge to transcribe speech to SRT subtitles using Whisper. public sealed record ConversionStep( string InputPath, string From, @@ -140,7 +144,8 @@ public sealed record ConversionStep( bool MotionPhoto = false, bool LivePhoto = false, Action>? MissingFonts = null, - string? OriginalSourcePath = null); + string? OriginalSourcePath = null, + bool Caption = false); /// /// A conversion engine (ImageMagick, LibreOffice, rhwp, ...). diff --git a/src/Filee.Core/Conversion/JobQueue.cs b/src/Filee.Core/Conversion/JobQueue.cs index 2c9dd41f6..2d0de8273 100644 --- a/src/Filee.Core/Conversion/JobQueue.cs +++ b/src/Filee.Core/Conversion/JobQueue.cs @@ -765,10 +765,11 @@ private async Task> RunFileAsync( var frames = finalOutput && runWith.SavesFramesOf(source.Id); var motionPhoto = finalOutput && runWith.ExtractsMotionOf(source.Id); var livePhoto = finalOutput && runWith.MakesLivePhotoOf(source.Id); + var caption = finalOutput && runWith.CaptionsVideo(source.Id); // "Recognize text": Tesseract writes a searchable PDF first, read on into DOCX, TXT, … from there. Also for the // PDFs that are merged into one. var ocr = (finalOutput || target == "pdf") && runWith.RecognizesTextOf(source.Id); - var route = planner.Plan(source.Id, target, frames, motionPhoto: motionPhoto, livePhoto: livePhoto, ocr: ocr); + var route = planner.Plan(source.Id, target, frames, motionPhoto: motionPhoto, livePhoto: livePhoto, ocr: ocr, caption: caption); if (route is null) return Fail(file, "error.no_route", $"{source.Id} → {target}", new RouteRequest(source.Id, target, Frames: frames, MotionPhoto: motionPhoto, LivePhoto: livePhoto, Ocr: ocr)); @@ -866,7 +867,7 @@ private async Task> RunFileAsync( file.MissingFonts = file.MissingFonts.Concat(fonts) .Distinct(StringComparer.OrdinalIgnoreCase).ToList(); Notify(job); - }, file.SourcePath); + }, file.SourcePath, Caption: step.Caption); outputs.AddRange(await step.Converter.ConvertAsync(stepInfo, progress, ct)); } finally diff --git a/src/Filee.Core/Conversion/RoutePlanner.cs b/src/Filee.Core/Conversion/RoutePlanner.cs index be4fdb7fa..d6f4cd31d 100644 --- a/src/Filee.Core/Conversion/RoutePlanner.cs +++ b/src/Filee.Core/Conversion/RoutePlanner.cs @@ -13,8 +13,9 @@ namespace Filee.Core.Conversion; /// The hop draws text without complex-script shaping (). /// The hop takes the video of a Live / Motion Photo (). /// The hop makes a Live / Motion Photo of a video (). +/// The hop transcribes speech to SRT subtitles using Whisper (). public sealed record RouteStep(IConverter Converter, string From, string To, bool FrameSequence = false, bool Unshaped = false, - bool MotionPhoto = false, bool LivePhoto = false); + bool MotionPhoto = false, bool LivePhoto = false, bool Caption = false); /// A planned conversion from one format to another. public sealed record Route(IReadOnlyList Steps, int Cost) @@ -79,8 +80,9 @@ public RoutePlanner(IEnumerable converters, Func? p /// edge to PDF, from the file itself or from a PNG of a picture Tesseract can't read, then the cheapest route from /// that PDF on. Without it those edges are never taken. /// + /// Transcribe speech to SRT subtitles using Whisper (). public Route? Plan(string from, string to, bool frames = false, bool shaping = false, bool motionPhoto = false, bool livePhoto = false, - bool ocr = false) + bool ocr = false, bool caption = false) { from = from.ToLowerInvariant(); to = to.ToLowerInvariant(); @@ -88,18 +90,19 @@ public RoutePlanner(IEnumerable converters, Func? p if (ocr) return PlanWithOcr(from, to, shaping); - if (frames || motionPhoto || livePhoto) + if (frames || motionPhoto || livePhoto || caption) { - // One of them: frames, then the video of a photo, then a photo of a video. + // One of them: frames, then the video of a photo, then a photo of a video, then caption. motionPhoto &= !frames; livePhoto &= !frames && !motionPhoto; + caption &= !frames && !motionPhoto && !livePhoto; var special = EdgesFrom(from) - .Where(e => e.Edge.To == to && (frames ? e.Edge.FrameSequence : motionPhoto ? e.Edge.MotionPhoto : e.Edge.LivePhoto)) + .Where(e => e.Edge.To == to && (frames ? e.Edge.FrameSequence : motionPhoto ? e.Edge.MotionPhoto : livePhoto ? e.Edge.LivePhoto : e.Edge.Caption)) .OrderBy(e => e.Cost) .FirstOrDefault(); return special.Converter is null ? null - : new Route([new RouteStep(special.Converter, from, to, FrameSequence: frames, MotionPhoto: motionPhoto, LivePhoto: livePhoto)], + : new Route([new RouteStep(special.Converter, from, to, FrameSequence: frames, MotionPhoto: motionPhoto, LivePhoto: livePhoto, Caption: caption)], special.Cost); } @@ -107,7 +110,7 @@ public RoutePlanner(IEnumerable converters, Func? p if (from == to) { var best = EdgesFrom(from) - .Where(e => e.Edge.To == to && !e.Edge.FrameSequence && !e.Edge.MotionPhoto && !e.Edge.LivePhoto && !e.Edge.Ocr) + .Where(e => e.Edge.To == to && !e.Edge.FrameSequence && !e.Edge.MotionPhoto && !e.Edge.LivePhoto && !e.Edge.Ocr && !e.Edge.Caption) .OrderBy(e => e.Cost) .FirstOrDefault(); return best.Converter is null @@ -136,7 +139,7 @@ public RoutePlanner(IEnumerable converters, Func? p foreach (var (converter, edge, edgeCost) in EdgesFrom(node)) { - if (edge.To == edge.From || edge.FrameSequence || edge.MotionPhoto || edge.LivePhoto || edge.Ocr + if (edge.To == edge.From || edge.FrameSequence || edge.MotionPhoto || edge.LivePhoto || edge.Ocr || edge.Caption || (edge.ImageSourcesOnly && !imageSource) || (edge.NotForImageSources && imageSource) || (edge.Unshaped && shaping) || (edge.DirectOnly && (node != from || edge.To != to))) @@ -167,8 +170,9 @@ public RoutePlanner(IEnumerable converters, Func? p /// The video of a Live / Motion Photo (see ). /// A Live / Motion Photo of a video (see ). /// Recognize the text first (see ). - public bool CanConvert(string from, string to, bool frames = false, bool motionPhoto = false, bool livePhoto = false, bool ocr = false) => - Plan(from, to, frames, motionPhoto: motionPhoto, livePhoto: livePhoto, ocr: ocr) is not null; + /// Transcribe speech to SRT subtitles using Whisper (see ). + public bool CanConvert(string from, string to, bool frames = false, bool motionPhoto = false, bool livePhoto = false, bool ocr = false, bool caption = false) => + Plan(from, to, frames, motionPhoto: motionPhoto, livePhoto: livePhoto, ocr: ocr, caption: caption) is not null; private Route? PlanWithOcr(string from, string to, bool shaping) { diff --git a/src/Filee.Core/Presets/BuiltInData.cs b/src/Filee.Core/Presets/BuiltInData.cs index ffab9bc78..d92aa7d8c 100644 --- a/src/Filee.Core/Presets/BuiltInData.cs +++ b/src/Filee.Core/Presets/BuiltInData.cs @@ -381,6 +381,12 @@ public static List CodecAndSubtitlePresets() => // Subtitles; on the video donut, SRT takes the first text subtitle track out of the video. new() { Id = "extract-subtitles", NameKey = "preset.extract_subtitles", TargetFormat = "srt" }, + // AI Captions: transcribes speech in video and audio to SRT subtitles using Whisper AI. + new() + { + Id = "caption-video", NameKey = "preset.caption_video", TargetFormat = "srt", + Media = { Caption = true }, + }, new() { Id = "to-srt", Name = "SRT", TargetFormat = "srt" }, new() { Id = "to-vtt", Name = "WebVTT", TargetFormat = "vtt" }, new() { Id = "to-ass", Name = "ASS", TargetFormat = "ass" }, @@ -668,7 +674,7 @@ public static List CatalogProfiles() => { Id = "video", NameKey = "profile.video", Extensions = ExtensionsOf(FormatCategory.Video), - PresetIds = ["to-mp4", "mp4-720", "mp4-hevc", "to-webm", "to-mov", "to-gif", "to-mp3", "extract-subtitles", "to-mkv", "to-avi"], + PresetIds = ["to-mp4", "mp4-720", "mp4-hevc", "to-webm", "to-mov", "to-gif", "to-mp3", "extract-subtitles", "caption-video", "to-mkv", "to-avi"], // Every frame as pictures is on no front (a long video gives thousands of files): behind a flip, a drop that // misses its slice doesn't start it. Live / Motion Photos of a clip are there too, and "Cut a clip" first // (in place of FLAC, which stays in the library). @@ -679,7 +685,7 @@ public static List CatalogProfiles() => { Id = "audio", NameKey = "profile.audio", Extensions = ExtensionsOf(FormatCategory.Audio), - PresetIds = ["to-mp3", "mp3-128", "to-m4a", "to-wav", "to-flac", "to-ogg", "to-opus", "to-aac"], + PresetIds = ["to-mp3", "mp3-128", "to-m4a", "to-wav", "to-flac", "to-ogg", "to-opus", "to-aac", "caption-video"], BackPresetIds = ["to-alac", "compress-audio", "merge-audio", "remove-metadata", "zip-all"], }, new() diff --git a/src/Filee.Core/Presets/Preset.cs b/src/Filee.Core/Presets/Preset.cs index 34dabca1a..f71ce2b19 100644 --- a/src/Filee.Core/Presets/Preset.cs +++ b/src/Filee.Core/Presets/Preset.cs @@ -180,6 +180,16 @@ public bool MakesLivePhotoOf(string sourceFormat) => MediaOptions.LivePhotoTargets(Media.LivePhoto).Contains(TargetFormat) && Formats.FormatRegistry.FindById(sourceFormat)?.Category == Formats.FormatCategory.Video; + /// + /// True if converting a file transcribes speech into captions + /// (): the file is a video or audio. + /// + public bool CaptionsVideo(string sourceFormat) => + Media.Caption && TargetFormat == "srt" && Formats.FormatRegistry.FindById(sourceFormat)?.Category is Formats.FormatCategory.Video or Formats.FormatCategory.Audio; + + + + /// /// True if converting a file recognizes its text first (): /// the file is a PDF or a picture and the target one of . @@ -956,6 +966,17 @@ public sealed class MediaOptions /// public bool ExtractMotionPhoto { get; set; } + /// + /// Transcribes speech using Whisper AI to an SRT subtitle file. + /// + public bool Caption { get; set; } + + /// + /// Spoken language code for transcription (e.g. "auto", "en", "hi", "es", "fr", "de", "ja", "zh"). + /// Passed to whisper.cpp (-l). Defaults to "auto". + /// + public string SpokenLanguage { get; set; } = "auto"; + /// /// Video → JPG (or HEIC for an iPhone): a Live / Motion Photo of a short clip instead of one still frame. Only for /// video sources and a target in (). diff --git a/src/Filee.Core/Presets/WhisperLanguages.cs b/src/Filee.Core/Presets/WhisperLanguages.cs new file mode 100644 index 000000000..99191e8a1 --- /dev/null +++ b/src/Filee.Core/Presets/WhisperLanguages.cs @@ -0,0 +1,131 @@ +// The spoken languages recognized by OpenAI Whisper (whisper.cpp). +// Code is the ISO-639 / whisper language identifier; Name is the English language name. + +using System.Globalization; + +namespace Filee.Core.Presets; + +/// A spoken language recognized by Whisper AI. +/// Language code passed to whisper.cpp (-l <code>). +/// The English language name from whisper.cpp. +public sealed record WhisperLanguage(string Code, string Name) +{ + /// Title-cased display name (e.g. "English", "Hindi", "French"). + public string DisplayName => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(Name); +} + +/// The spoken languages supported by Whisper AI. +public static class WhisperLanguages +{ + /// Auto-detect the spoken language from audio (whisper.cpp default). + public const string Auto = "auto"; + + /// All languages known to whisper.cpp. + public static readonly IReadOnlyList All = + [ + new("en", "english"), + new("zh", "chinese"), + new("de", "german"), + new("es", "spanish"), + new("ru", "russian"), + new("ko", "korean"), + new("fr", "french"), + new("ja", "japanese"), + new("pt", "portuguese"), + new("tr", "turkish"), + new("pl", "polish"), + new("ca", "catalan"), + new("nl", "dutch"), + new("ar", "arabic"), + new("sv", "swedish"), + new("it", "italian"), + new("id", "indonesian"), + new("hi", "hindi"), + new("fi", "finnish"), + new("vi", "vietnamese"), + new("he", "hebrew"), + new("uk", "ukrainian"), + new("el", "greek"), + new("ms", "malay"), + new("cs", "czech"), + new("ro", "romanian"), + new("da", "danish"), + new("hu", "hungarian"), + new("ta", "tamil"), + new("no", "norwegian"), + new("th", "thai"), + new("ur", "urdu"), + new("hr", "croatian"), + new("bg", "bulgarian"), + new("lt", "lithuanian"), + new("la", "latin"), + new("mi", "maori"), + new("ml", "malayalam"), + new("cy", "welsh"), + new("sk", "slovak"), + new("te", "telugu"), + new("fa", "persian"), + new("lv", "latvian"), + new("bn", "bengali"), + new("sr", "serbian"), + new("az", "azerbaijani"), + new("sl", "slovenian"), + new("kn", "kannada"), + new("et", "estonian"), + new("mk", "macedonian"), + new("br", "breton"), + new("eu", "basque"), + new("is", "icelandic"), + new("hy", "armenian"), + new("ne", "nepali"), + new("mn", "mongolian"), + new("bs", "bosnian"), + new("kk", "kazakh"), + new("sq", "albanian"), + new("sw", "swahili"), + new("gl", "galician"), + new("mr", "marathi"), + new("pa", "punjabi"), + new("si", "sinhala"), + new("km", "khmer"), + new("sn", "shona"), + new("yo", "yoruba"), + new("so", "somali"), + new("af", "afrikaans"), + new("oc", "occitan"), + new("ka", "georgian"), + new("be", "belarusian"), + new("tg", "tajik"), + new("sd", "sindhi"), + new("gu", "gujarati"), + new("am", "amharic"), + new("yi", "yiddish"), + new("lo", "lao"), + new("uz", "uzbek"), + new("fo", "faroese"), + new("ht", "haitian creole"), + new("ps", "pashto"), + new("tk", "turkmen"), + new("nn", "nynorsk"), + new("mt", "maltese"), + new("sa", "sanskrit"), + new("lb", "luxembourgish"), + new("my", "myanmar"), + new("bo", "tibetan"), + new("tl", "tagalog"), + new("mg", "malagasy"), + new("as", "assamese"), + new("tt", "tatar"), + new("haw", "hawaiian"), + new("ln", "lingala"), + new("ha", "hausa"), + new("ba", "bashkir"), + new("jw", "javanese"), + new("su", "sundanese"), + new("yue", "cantonese"), + ]; + + /// Finds a language by its code (case-insensitive). + public static WhisperLanguage? Find(string code) => + All.FirstOrDefault(l => string.Equals(l.Code, code, StringComparison.OrdinalIgnoreCase)); +} diff --git a/src/Filee.Core/Settings/AppSettings.cs b/src/Filee.Core/Settings/AppSettings.cs index b173a92fd..ace3a3aa5 100644 --- a/src/Filee.Core/Settings/AppSettings.cs +++ b/src/Filee.Core/Settings/AppSettings.cs @@ -123,7 +123,7 @@ public static bool IsFlipKeyAllowed(string key, IEnumerable gest public sealed class AppSettings { /// Current schema version. Increase when the format changes and add a migration. - public const int CurrentSchemaVersion = 56; + public const int CurrentSchemaVersion = 57; public int SchemaVersion { get; set; } = CurrentSchemaVersion; diff --git a/src/Filee.Core/Settings/UserDataStore.cs b/src/Filee.Core/Settings/UserDataStore.cs index 6e4a5d668..592f93da2 100644 --- a/src/Filee.Core/Settings/UserDataStore.cs +++ b/src/Filee.Core/Settings/UserDataStore.cs @@ -435,6 +435,7 @@ public static void Apply(AppSettings settings) if (settings.SchemaVersion < 21) InsertBefore(settings.EnginePriority, "wic", settings.EnginePriority.Contains("ffmpeg") ? "ffmpeg" : "pandoc"); + if (settings.Triggers.Count == 0) settings.Triggers = TriggerGesture.Defaults(); @@ -937,6 +938,32 @@ public static bool ApplyToLibrary(int from, List presets, List presets, List profiles) + { + var changed = false; + var caption = BuiltInData.CreatePresets().First(p => p.Id == "caption-video"); + if (presets.FirstOrDefault(p => p.Id == caption.Id) is { } existing) + { + if (!existing.Media.Caption && existing.TargetFormat == caption.TargetFormat) + { + existing.Media.Caption = true; + changed = true; + } + } + else + { + var at = presets.FindLastIndex(p => p.Id is "extract-audio" or "to-mp3"); + presets.Insert(at < 0 ? presets.Count : at + 1, caption); + changed = true; + } + changed |= Offer(presets, profiles, "video", caption.Id, "extract-subtitles"); + changed |= Offer(presets, profiles, "audio", caption.Id, "to-aac"); return changed; } diff --git a/src/Filee.Engines/EngineRegistry.cs b/src/Filee.Engines/EngineRegistry.cs index 5212f3c27..b45c29830 100644 --- a/src/Filee.Engines/EngineRegistry.cs +++ b/src/Filee.Engines/EngineRegistry.cs @@ -61,6 +61,7 @@ public static IReadOnlyList CreateAll(EngineEnvironment env, ILogger new DataConverter(), new SubtitleConverter(), new WicImageConverter(), + new WhisperConverter(loggers?.CreateLogger()), new FfmpegConverter(loggers?.CreateLogger()), new CadConverter(), new ModelConverter(), diff --git a/src/Filee.Engines/Infrastructure/EngineDownloads.cs b/src/Filee.Engines/Infrastructure/EngineDownloads.cs index d1f334b1d..343a7ea90 100644 --- a/src/Filee.Engines/Infrastructure/EngineDownloads.cs +++ b/src/Filee.Engines/Infrastructure/EngineDownloads.cs @@ -45,6 +45,7 @@ public static class EngineDownloads new("ffmpeg", ["ffmpeg"], 272_000_000, ["ffmpeg"]), new("calibre", ["calibre"], 663_000_000, ["calibre"]), new("tesseract", ["tesseract"], 168_000_000, ["tesseract"]), + .. (OperatingSystem.IsWindows() ? [new EnginePackage("whisper", ["whisper", "whisper-model"], 156_880_105, ["whisper"])] : Array.Empty()), ]; /// Total download size of a package in bytes. diff --git a/src/Filee.Engines/Infrastructure/EngineEnvironment.cs b/src/Filee.Engines/Infrastructure/EngineEnvironment.cs index de1a9e4f8..baf5eaa2e 100644 --- a/src/Filee.Engines/Infrastructure/EngineEnvironment.cs +++ b/src/Filee.Engines/Infrastructure/EngineEnvironment.cs @@ -88,6 +88,7 @@ dataDirectoryOverride is not null ["rhwp"] = [windows ? "rhwp.exe" : "rhwp"], ["7zip"] = [windows ? "7z.exe" : "7zz"], ["tesseract"] = [windows ? "tesseract.exe" : "tesseract"], + ["whisper"] = [windows ? "whisper-cli.exe" : "whisper-cli"], }; public static string ProgramName(string name) => OperatingSystem.IsWindows() ? name diff --git a/src/Filee.Engines/Infrastructure/EngineInstaller.cs b/src/Filee.Engines/Infrastructure/EngineInstaller.cs index 90b5cf01f..024a84344 100644 --- a/src/Filee.Engines/Infrastructure/EngineInstaller.cs +++ b/src/Filee.Engines/Infrastructure/EngineInstaller.cs @@ -213,6 +213,14 @@ private async Task UnpackAsync(EngineComponent component, string file, Cancellat File.Copy(file, Path.Combine(staging, component.Id + ".traineddata")); Replace(component.Id, staging); break; + case "model": + case "bin": + // Standalone AI model file (e.g. Whisper ggml-base.bin): copied directly into its component folder. + Directory.CreateDirectory(staging); + var modelFileName = Path.GetFileName(new Uri(component.Url).LocalPath); + File.Copy(file, Path.Combine(staging, modelFileName)); + Replace(component.Id, staging); + break; case "nsis": var sevenZip = Archives.SevenZip.Locate() ?? throw new InvalidOperationException($"{component.Id} needs Filee's 7-Zip, which is missing."); diff --git a/src/Filee.Engines/Infrastructure/engines.json b/src/Filee.Engines/Infrastructure/engines.json index 1c6eb5919..6400bbf27 100644 --- a/src/Filee.Engines/Infrastructure/engines.json +++ b/src/Filee.Engines/Infrastructure/engines.json @@ -77,6 +77,20 @@ "sha256": "C885FFF6998E0608BA4BB8AB51436E1C6775C2BAFC2559A19B423E18678B60C9", "size": 50175248, "kind": "nsis" + }, + "whisper": { + "version": "b5454", + "url": "https://github.com/ggml-org/whisper.cpp/releases/download/b5454/whisper-bin-x64.zip", + "sha256": "6BA69E3482D7826214F90A6A9C84CA07782AEC1E1D0C6A7C30C994FD5D816CCB", + "size": 8928640, + "kind": "zip" + }, + "whisper-model": { + "version": "base", + "url": "https://huggingface.co/ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-base.bin", + "sha256": "60ED5BC3DD14EEA856493D334349B405782DDCAF0028D4B5DF4088345FBA2EFE", + "size": 147951465, + "kind": "model" } }, "platforms": { diff --git a/src/Filee.Engines/Media/WhisperConverter.cs b/src/Filee.Engines/Media/WhisperConverter.cs new file mode 100644 index 000000000..549e65b9c --- /dev/null +++ b/src/Filee.Engines/Media/WhisperConverter.cs @@ -0,0 +1,217 @@ +// Transcribes speech in video and audio using OpenAI Whisper (whisper.cpp) into SRT subtitles. + +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +using Filee.Core.Conversion; +using Filee.Core.Formats; +using Filee.Engines.Infrastructure; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Filee.Engines.Media; + +/// +/// Speech transcription engine powered by OpenAI Whisper. +/// Transcribes speech in video and audio files into SRT subtitles. +/// +public sealed partial class WhisperConverter : IConverter +{ + private static readonly TimeSpan StallTimeout = TimeSpan.FromMinutes(5); + + private readonly ILogger _logger; + + public WhisperConverter(ILogger? logger = null) + { + _logger = logger ?? NullLogger.Instance; + } + + public string Id => "whisper"; + public string DisplayName => "Whisper"; + public int MaxParallelism => 1; + + public IReadOnlyList Edges { get; } = BuildEdges(); + + private static IReadOnlyList BuildEdges() + { + var videos = FormatRegistry.Known.Where(f => f.Category == FormatCategory.Video).Select(f => f.Id).ToList(); + var audios = FormatRegistry.Known.Where(f => f.Category == FormatCategory.Audio).Select(f => f.Id).ToList(); + return + [ + // Video -> SRT transcription directly + .. videos.Select(v => new ConversionEdge(v, "srt", Cost: 10, Caption: true)), + // Audio -> SRT transcription directly + .. audios.Select(a => new ConversionEdge(a, "srt", Cost: 10, Caption: true)), + ]; + } + + public EngineStatus GetStatus() + { + if (!OperatingSystem.IsWindows()) + return EngineStatus.Unavailable("engine.reason.unsupported_platform"); + + var (cli, model) = LocateTools(); + if (cli is null || model is null) + return EngineStatus.Unavailable("engine.reason.not_installed"); + + var modelName = Path.GetFileNameWithoutExtension(model); + return EngineStatus.Available(cli, $"Whisper ({modelName})"); + } + + public static (string? Cli, string? Model) LocateTools() + { + var cli = LocateCli(); + var model = LocateModel(cli); + return (cli, model); + } + + internal static string? LocateCli() + { + // 1. User's own copy from Settings -> Engines + if (EngineEnvironment.OwnProgram("whisper", "whisper-cli.exe") is { } own && File.Exists(own)) + return own; + + // 2. Filee bundled or downloaded engine folder + if (EngineEnvironment.BundledProgram("whisper", "whisper-cli.exe") is { } bundled && File.Exists(bundled)) + return bundled; + + // 3. System / PATH + if (EngineEnvironment.SystemProgram("whisper", "whisper-cli.exe") is { } system && File.Exists(system)) + return system; + + return null; + } + + internal static string? LocateModel(string? cliPath = null) + { + var searchDirs = new List(); + + // 1. Downloaded / bundled whisper-model folder + if (EngineEnvironment.FindBundled("whisper-model") is { } wmDir && Directory.Exists(wmDir)) + searchDirs.Add(wmDir); + + // 2. Downloaded / bundled whisper folder + if (EngineEnvironment.FindBundled("whisper") is { } wDir && Directory.Exists(wDir)) + searchDirs.Add(wDir); + + // 3. User's own copy folder + if (EngineEnvironment.OwnCopyFolder("whisper") is { } own && Directory.Exists(own)) + searchDirs.Add(own); + + // 4. Folder containing the cli executable if located + if (!string.IsNullOrEmpty(cliPath) && Path.GetDirectoryName(cliPath) is { } cliDir && Directory.Exists(cliDir)) + searchDirs.Add(cliDir); + + string[] preferredModels = ["ggml-large-v3-turbo.bin", "ggml-base.bin", "ggml-small.bin", "ggml-tiny.bin"]; + + foreach (var dir in searchDirs.Distinct()) + { + foreach (var pref in preferredModels) + { + var candidate = Path.Combine(dir, pref); + if (File.Exists(candidate)) + return candidate; + } + + var anyBin = Directory.GetFiles(dir, "*.bin").FirstOrDefault(); + if (anyBin is not null) + return anyBin; + } + + return null; + } + + public async Task> ConvertAsync(ConversionStep step, IProgress? progress, CancellationToken cancellationToken) + { + if (step.To != "srt") + throw new InvalidOperationException($"Whisper converter only produces SRT subtitles (requested '{step.To}')."); + + var (whisperCli, modelPath) = LocateTools(); + if (whisperCli is null || modelPath is null) + throw new InvalidOperationException("Whisper engine or model was not found. Please download Whisper in Settings \u2192 Engines."); + + var ffmpegTools = FfmpegConverter.Locate(); + if (ffmpegTools is null || !File.Exists(ffmpegTools.Value.Ffmpeg)) + throw new InvalidOperationException("FFmpeg was not found. Please install FFmpeg from Settings \u2192 Engines."); + + var ffmpeg = ffmpegTools.Value.Ffmpeg; + + cancellationToken.ThrowIfCancellationRequested(); + progress?.Report(0.02); + + // 1. Extract 16kHz mono WAV for Whisper inference (works for both video and audio files) + var audioWav = Path.Combine(step.WorkDirectory, $"whisper_audio_{Guid.NewGuid():N}.wav"); + var extractArgs = new[] + { + "-hide_banner", "-nostdin", "-nostats", "-v", "error", "-y", + "-i", step.InputPath, + "-vn", "-ar", "16000", "-ac", "1", + "-progress", "pipe:1", + audioWav + }; + + var extractResult = await FfmpegRunner.RunAsync(ffmpeg, extractArgs, null, cancellationToken, StallTimeout); + if (extractResult.ExitCode != 0 || !File.Exists(audioWav)) + throw new InvalidOperationException($"FFmpeg failed to extract audio: {extractResult.StandardError}"); + + progress?.Report(0.10); + cancellationToken.ThrowIfCancellationRequested(); + + // 2. Run Whisper CLI transcription (auto-detect language or preset's SpokenLanguage, srt output) + var outPrefix = Path.Combine(step.WorkDirectory, $"whisper_out_{Guid.NewGuid():N}"); + var lang = string.IsNullOrWhiteSpace(step.Preset?.Media?.SpokenLanguage) + ? "auto" + : step.Preset.Media.SpokenLanguage.Trim(); + + var threads = Math.Clamp(Environment.ProcessorCount, 1, 8).ToString(CultureInfo.InvariantCulture); + var whisperArgs = new[] + { + "-m", modelPath, + "-f", audioWav, + "-osrt", + "-of", outPrefix, + "-l", lang, + "-t", threads, + "-sow", + "-pp" + }; + + var whisperResult = await ProcessRunner.RunAsync( + whisperCli, + whisperArgs, + Timeout.InfiniteTimeSpan, + cancellationToken, + onOutput: line => ReportWhisperProgress(line, progress), + stallTimeout: StallTimeout); + + var srtPath = outPrefix + ".srt"; + + if (!File.Exists(srtPath) || whisperResult.ExitCode != 0) + throw new InvalidOperationException($"Whisper failed to transcribe audio (exit code {whisperResult.ExitCode}): {whisperResult.StandardError}"); + + var srtContent = await File.ReadAllTextAsync(srtPath, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + + // 3. Produce SRT output + var srtOutput = step.Output.Allocate("srt"); + if (srtOutput is null) + return []; + + await File.WriteAllTextAsync(srtOutput, srtContent, Encoding.UTF8, cancellationToken); + progress?.Report(1.0); + return [srtOutput]; + } + + private static void ReportWhisperProgress(string line, IProgress? progress) + { + if (progress is not null && ProgressPercent().Match(line) is { Success: true } match + && int.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out var percent)) + { + progress.Report(0.10 + (Math.Clamp(percent, 0, 100) / 100.0 * 0.88)); + } + } + + [GeneratedRegex(@"progress\s*=\s*(\d{1,3})%")] + private static partial Regex ProgressPercent(); +} diff --git a/tests/Filee.Core.Tests/BackFaceTests.cs b/tests/Filee.Core.Tests/BackFaceTests.cs index 294fe7ce4..16f39c686 100644 --- a/tests/Filee.Core.Tests/BackFaceTests.cs +++ b/tests/Filee.Core.Tests/BackFaceTests.cs @@ -100,7 +100,7 @@ public void The_fronts_stay_as_they_were_before_back_faces() Assert.Equal(["to-pdf", "to-hwpx", "to-hwp", "to-docx", "to-odt", "to-txt", "to-html", "to-epub", "to-png", "merge-pdf"], BuiltIn("office").PresetIds); Assert.Equal(["to-pdf", "merge-pdf", "zip-all", "to-png", "to-txt"], BuiltIn("mixed").PresetIds); - Assert.Equal(["to-mp4", "mp4-720", "mp4-hevc", "to-webm", "to-mov", "to-gif", "to-mp3", "extract-subtitles", "to-mkv", "to-avi"], + Assert.Equal(["to-mp4", "mp4-720", "mp4-hevc", "to-webm", "to-mov", "to-gif", "to-mp3", "extract-subtitles", "caption-video", "to-mkv", "to-avi"], BuiltIn("video").PresetIds); } diff --git a/tests/Filee.Core.Tests/CaptionRouteTests.cs b/tests/Filee.Core.Tests/CaptionRouteTests.cs new file mode 100644 index 000000000..d46daaea6 --- /dev/null +++ b/tests/Filee.Core.Tests/CaptionRouteTests.cs @@ -0,0 +1,111 @@ +// "AI Captioning" with Whisper: Whisper's caption edges start a route only when the preset asks for them (Caption = true). +// Supports both video and audio files directly to SRT subtitles. + +using Filee.Core.Conversion; +using Filee.Core.Presets; +using Filee.Core.Settings; + +namespace Filee.Core.Tests; + +public class CaptionRouteTests +{ + private static readonly ConversionEdge[] CaptionEdges = + [ + new("mp4", "srt", Caption: true), + new("mkv", "srt", Caption: true), + new("mov", "srt", Caption: true), + new("webm", "srt", Caption: true), + new("mp3", "srt", Caption: true), + new("wav", "srt", Caption: true), + new("m4a", "srt", Caption: true), + ]; + + private static string Steps(Route? route) => string.Join(", ", route!.Steps.Select(s => $"{s.Converter.Id}:{s.From}→{s.To}")); + + private static RoutePlanner Planner() => new([ + new FakeConverter("whisper", CaptionEdges), + new FakeConverter("ffmpeg", new ConversionEdge("mp4", "mp4"), new ConversionEdge("mov", "mp4"), new ConversionEdge("mp3", "wav")), + ]); + + [Fact] + public void Caption_edges_are_never_part_of_an_ordinary_route() + { + var planner = Planner(); + + Assert.Equal("ffmpeg:mp4→mp4", Steps(planner.Plan("mp4", "mp4"))); + Assert.Equal("ffmpeg:mp3→wav", Steps(planner.Plan("mp3", "wav"))); + } + + [Fact] + public void Asking_for_caption_routes_to_whisper_for_both_video_and_audio() + { + var planner = Planner(); + + // Video -> SRT + Assert.Equal("whisper:mp4→srt", Steps(planner.Plan("mp4", "srt", caption: true))); + Assert.Equal("whisper:mov→srt", Steps(planner.Plan("mov", "srt", caption: true))); + Assert.Equal("whisper:webm→srt", Steps(planner.Plan("webm", "srt", caption: true))); + Assert.True(planner.CanConvert("mp4", "srt", caption: true)); + + // Audio -> SRT + Assert.Equal("whisper:mp3→srt", Steps(planner.Plan("mp3", "srt", caption: true))); + Assert.Equal("whisper:wav→srt", Steps(planner.Plan("wav", "srt", caption: true))); + Assert.Equal("whisper:m4a→srt", Steps(planner.Plan("m4a", "srt", caption: true))); + Assert.True(planner.CanConvert("mp3", "srt", caption: true)); + } + + [Fact] + public void Without_whisper_there_is_no_caption_route() + { + var planner = new RoutePlanner([new FakeConverter("ffmpeg", new ConversionEdge("mp4", "mp4"))]); + + Assert.Null(planner.Plan("mp4", "srt", caption: true)); + Assert.False(planner.CanConvert("mp4", "srt", caption: true)); + Assert.Null(planner.Plan("mp3", "srt", caption: true)); + Assert.False(planner.CanConvert("mp3", "srt", caption: true)); + } + + [Fact] + public void BuiltIn_caption_preset_has_caption_flag_and_targets_srt() + { + var preset = BuiltInData.CreatePresets().First(p => p.Id == "caption-video"); + + Assert.Equal("srt", preset.TargetFormat); + Assert.True(preset.Media.Caption); + Assert.True(preset.CaptionsVideo("mp4")); + Assert.True(preset.CaptionsVideo("mp3")); + Assert.False(preset.CaptionsVideo("png")); + Assert.Equal("{name}", preset.Output.FileNamePattern); + Assert.Equal("preset.caption_video", preset.NameKey); + } + + [Fact] + public void Schema_56_library_gets_caption_preset_on_both_video_and_audio_donuts() + { + var presets = BuiltInData.CreatePresets().Where(p => p.Id != "caption-video").ToList(); + var profiles = BuiltInData.CreateProfiles(); + var video = profiles.Single(p => p.Id == "video"); + video.PresetIds.Remove("caption-video"); + var audio = profiles.Single(p => p.Id == "audio"); + audio.PresetIds.Remove("caption-video"); + + Assert.True(SettingsMigrations.ApplyToLibrary(56, presets, profiles)); + Assert.False(SettingsMigrations.ApplyToLibrary(56, presets, profiles)); // once only + + Assert.Contains(presets, p => p.Id == "caption-video"); + Assert.Contains("caption-video", video.PresetIds); + Assert.Contains("caption-video", audio.PresetIds); + } + + [Fact] + public void Existing_caption_preset_without_flag_learns_caption_flag_in_migration() + { + var presets = BuiltInData.CreatePresets().ToList(); + var caption = presets.First(p => p.Id == "caption-video"); + caption.Media.Caption = false; + var profiles = BuiltInData.CreateProfiles(); + + Assert.True(SettingsMigrations.ApplyToLibrary(56, presets, profiles)); + Assert.True(caption.Media.Caption); + } +} diff --git a/tests/Filee.Engines.Tests/WhisperTests.cs b/tests/Filee.Engines.Tests/WhisperTests.cs new file mode 100644 index 000000000..7d9ad726a --- /dev/null +++ b/tests/Filee.Engines.Tests/WhisperTests.cs @@ -0,0 +1,62 @@ +using Filee.Core.Conversion; +using Filee.Engines.Media; + +namespace Filee.Engines.Tests; + +public class WhisperTests +{ + [Fact] + public void Whisper_converter_has_correct_metadata_and_caption_edges() + { + var converter = new WhisperConverter(); + + Assert.Equal("whisper", converter.Id); + Assert.Equal("Whisper", converter.DisplayName); + Assert.NotEmpty(converter.Edges); + + // Every edge must produce srt directly and require the Caption flag + Assert.All(converter.Edges, edge => + { + Assert.True(edge.Caption); + Assert.Equal("srt", edge.To); + }); + + // Supports video formats to SRT + var fromFormats = converter.Edges.Select(e => e.From).ToHashSet(); + Assert.Contains("mp4", fromFormats); + Assert.Contains("mkv", fromFormats); + Assert.Contains("mov", fromFormats); + Assert.Contains("avi", fromFormats); + Assert.Contains("webm", fromFormats); + + // Supports audio formats to SRT + Assert.Contains("mp3", fromFormats); + Assert.Contains("wav", fromFormats); + Assert.Contains("m4a", fromFormats); + Assert.Contains("flac", fromFormats); + Assert.Contains("aac", fromFormats); + Assert.Contains("ogg", fromFormats); + } + + [Fact] + public void Whisper_converter_status_reports_correctly() + { + var converter = new WhisperConverter(); + var status = converter.GetStatus(); + + Assert.NotNull(status); + if (!OperatingSystem.IsWindows()) + { + Assert.False(status.IsAvailable); + Assert.Equal("engine.reason.unsupported_platform", status.ReasonKey); + } + else if (!status.IsAvailable) + { + Assert.Equal("engine.reason.not_installed", status.ReasonKey); + } + else + { + Assert.True(status.IsAvailable); + } + } +}