پروژه ساخت زیرنویس از ویدیو Media2Ass

media2ass پروژه‌ای برای ساخت زیرنویس از ویدیو و فایل‌های صوتی به‌صورت محلی است که از Faster-Whisper، FFmpeg/ffprobe، خروجی ASS، Validation و کنترل حرارتی استفاده می‌کند. هدف فقط تبدیل گفتار به متن نبود؛ Pipeline باید برای Batchهای بزرگ قابل‌ادامه، قابل‌عیب‌یابی و قابل‌اعتماد باشد، خروجی معتبر قبلی را دوباره نسازد، از SRT مناسب استفاده کند و فقط در صورت نیاز سراغ مدل گفتار برود.این پروژه از یک نیاز عملی شروع شد: می‌خواستم مجموعه بزرگی از فایل‌های ویدیویی و صوتی را به زیرنویس ASS تبدیل کنم، اما نه با اسکریپتی که برای هر اجرا به اینترنت یا Python نصب‌شده روی ویندوز وابسته باشد. مسیر توسعه مستقیم نبود؛ خطای دسترسی به مدل، مشکلات Import، Timeline Drift، Retryهای کاذب، ابهام سنسورهای حرارتی، چند Regression و در نهایت یک Media بدون Audio Stream هر کدام بخشی از معماری نهایی را شکل دادند.در این مقاله کل مسیر واقعی پروژه تا v2.2.0 FINAL STABLE حفظ و مستند شده است: زمان‌بندی توسعه، معماری نهایی، Benchmark چهار مدل Faster-Whisper، دلیل انتخاب medium، Thermal Guard، منطق SRT-first، Run واقعی 552 فایل، Validation و محدودیت‌های پذیرفته‌شده. اعداد Benchmark و نتایج Run همان Evidence ثبت‌شده پروژه هستند و برای زیباتر شدن مقاله تغییر داده نشده‌اند.

media2ass چیست؟

media2ass یک Pipeline اختصاصی برای تبدیل Media به زیرنویس ASS است. این ابزار ابتدا فایل‌ها را کشف می‌کند، خروجی معتبر قبلی را تشخیص می‌دهد، SRT موجود را بررسی می‌کند، وجود Audio Stream را با ffprobe می‌سنجد و فقط زمانی که لازم باشد Audio را برای Faster-Whisper پردازش می‌کند. خروجی نیز صرفاً نوشته نمی‌شود؛ Timeline، Overlap، طول Line، Text Integrity و وضعیت Run بررسی و ثبت می‌شوند.

پروژه ساخت زیرنویس از ویدیو Media2Ass داریوش حقیقی
پروژه ساخت زیرنویس از ویدیو Media2Ass داریوش حقیقی

به همین دلیل media2ass را نباید فقط یک «اسکریپت Speech-to-Text» در نظر گرفت. بخش مهم پروژه مدیریت Failure، Resume، Validation، Thermal Safety و حفظ قابلیت بازسازی Run است. همین لایه‌های اطراف مدل گفتار بودند که در تست‌های واقعی بیشترین خطاهای مهم را آشکار کردند.

پیش‌نیازهای کار

قبل از ورود به روند واقعی پروژه، بهتر است اجزایی را که در این Pipeline نقش دارند بشناسیم. media2ass فقط یک اسکریپت تبدیل صدا به متن نیست؛ چند ابزار مختلف کنار هم قرار می‌گیرند تا Media شناسایی شود، Audio بررسی و استخراج شود، گفتار با مدل محلی Transcribe شود، خروجی به ASS تبدیل و Validate شود و در پایان وضعیت Run همراه با اطلاعات حرارتی و Diagnostic ذخیره شود.

نسخه‌ای که در این مقاله مستند شده روی Windows و با PowerShell اجرا شده است. Production نهایی روی سیستم دارای پردازنده AMD Ryzen 5 7500F و کارت NVIDIA GeForce RTX 5060 Ti 16GB با CUDA تست شد. بنابراین اعداد Benchmark و دما مربوط به همین محیط هستند و نباید بدون تست به سخت‌افزار دیگری تعمیم داده شوند.

برای استفاده از Source نهایی، باید Project Root، Python Runtime محلی، Packageهای Python، مدل Faster-Whisper و ابزارهای FFmpeg در دسترس باشند. در این پروژه این اجزا به شکلی نگهداری شدند که Normal Production پس از Setup اولیه به دانلود Model یا System Python وابسته نباشد.

ابزارهای پروژه

هر کدام از ابزارهای زیر یک وظیفه مشخص در Pipeline دارند. شناخت این نقش‌ها کمک می‌کند وقتی خطایی رخ می‌دهد، سریع‌تر مشخص شود مشکل در Media، Audio Extraction، مدل گفتار، GPU Runtime یا لایه Monitoring است.

ابزارکاربرد در پروژهنکته
Pythonاجرای هسته media2ass و کتابخانه‌های AIدر نسخه نهایی Runtime محلی پروژه استفاده می‌شود
Faster-Whisperتبدیل گفتار انگلیسی به متن همراه Timestampمدل پیش‌فرض Production برابر medium است
CTranslate2Runtime بهینه اجرای مدل Faster-Whisperدر تست نهایی همراه GPU/CUDA استفاده شد
FFmpegخواندن Media و استخراج Audio مناسب برای Transcriptionخطای Audio Extraction از خطای Whisper جدا ثبت می‌شود
ffprobeبررسی Streamهای Media قبل از پردازشبرای تشخیص فایل بدون Audio در v2.2.0 حیاتی شد
PowerShellEntry Point، Self-test و کنترل اجرای پروژهمسیر ورودی هنگام Run به اسکریپت داده می‌شود
LibreHardwareMonitorخواندن سنسورهای CPUTctl/Tdie سنسور Control پروژه است
nvidia-smi / NVMLGPU Temperature، Load، VRAM و Powerمنبع اصلی GPU Telemetry در سیستم تست

وجود HWiNFO64 برای اجرای خود media2ass الزامی نیست. در این پروژه از HWiNFO برای Cross-check دستی سنسورها استفاده شد، در حالی که Monitoring خودکار CPU از LibreHardwareMonitor و GPU از NVML انجام می‌شود.

Faster-Whisper چیست؟

Faster-Whisper پیاده‌سازی مدل Whisper بر پایه CTranslate2 است. در media2ass از آن به‌عنوان موتور محلی Speech-to-Text استفاده شد تا گفتار همراه Timestamp به متن تبدیل شود. مدل‌های پروژه به‌صورت Local نگهداری شدند تا Production پس از Setup اولیه برای دریافت Model به اینترنت وابسته نباشد.

نکته مهم این است که FFmpeg و ffprobe در این پروژه بخشی از معماری خود media2ass هستند؛ Faster-Whisper در پیاده‌سازی رسمی می‌تواند Audio را از طریق PyAV Decode کند و الزام ذاتی به نصب System FFmpeg ندارد. در media2ass استفاده از FFmpeg/ffprobe تصمیم معماری برای کنترل Media، استخراج Audio و تشخیص Streamها بود.

مدل‌های Whisper

Faster-Whisper می‌تواند با Modelهای مختلف اجرا شود. در این پروژه چهار مدل tiny، small، medium و large-v3 به‌صورت Local وارد و Verify شدند تا انتخاب مدل بر اساس Benchmark واقعی انجام شود، نه صرفاً بزرگ‌تر بودن Model.

مدل کوچک‌تر معمولاً سریع‌تر و کم‌مصرف‌تر است، اما ممکن است روی نام‌های خاص و اصطلاحات فنی اشتباه بیشتری داشته باشد. مدل بزرگ‌تر هم الزاماً در هر Dataset بهتر نیست؛ همین پروژه نشان داد large-v3 در محتوای تست‌شده افزایش کیفیت قابل اثباتی نسبت به medium نداشت و در یکی از Benchmarkهای اولیه حتی یک Repetition Loop ایجاد کرد.

به همین دلیل در ادامه مقاله قبل از رسیدن به Run Production، Benchmark مدل‌ها را جدا بررسی می‌کنم و توضیح می‌دهم چرا در نهایت medium به‌عنوان Default انتخاب شد.

فرمت ASS چیست؟

ASS یا Advanced SubStation Alpha یک فرمت زیرنویس متنی است که علاوه بر زمان‌بندی Dialogue، امکان کنترل دقیق‌تر Style، Position و Formatting را نسبت به فرمت ساده‌تری مانند SRT فراهم می‌کند. در این پروژه هدف اصلی تولید ASS استاندارد و قابل استفاده در Pipelineهای بعدی ویدیو بود.

برای همین فقط ساخته‌شدن فایل کافی نبود. Timeline، ترتیب Cueها، Overlap، طول Line، Text Integrity و قرار نگرفتن Cue خارج از Media Timeline نیز بررسی شدند. یکی از مهم‌ترین درس‌های پروژه همین بود که یک Transcript خوب با Post-processing اشتباه می‌تواند به Subtitle بد تبدیل شود.

هدف پروژه

نسخه قدیمی جریان کاری من بر پایه اسکریپتی شبیه #Subtitle-VGA.py بود. در 21 اوت 2026، حدود ساعت 12:31، آن مسیر هنگام دسترسی به مدل با خطای Hugging Face Authentication متوقف شد. همان نقطه باعث شد مسئله را فقط به‌عنوان «رفع یک Error» نبینم و به‌جای Patch کردن اسکریپت قدیمی، ساختار پروژه را از نو طراحی کنم.

Requirementهای اصلی به‌تدریج به این شکل تثبیت شدند: Runtime باید داخل خود پروژه باشد، بعد از Setup اولیه اجرای Production به اینترنت وابسته نباشد، مسیر ورودی هنگام اجرا قابل تعیین باشد، FFmpeg و مدل‌ها مسیر مشخص داشته باشند، خروجی کنار Media ساخته شود، Runهای قبلی قابل Resume باشند و هر Run یک Diagnostic Bundle قابل بررسی بسازد.

Root نهایی پروژه روی سیستم توسعه این مسیر بود:

D:\AI\media2ass

این مسیر برای خواننده الزام نیست؛ اگر ساختار مشابهی پیاده می‌کنید می‌توانید Root را تغییر دهید، اما تمام Pathهای وابسته باید با هم سازگار بمانند.

معماری media2ass

در نسخه نهایی، تصمیم اصلی این بود که Whisper اولین انتخاب نباشد. Pipeline ابتدا بررسی می‌کند آیا خروجی معتبر قبلی وجود دارد یا نه، سپس به دنبال SRT مناسب می‌گردد و فقط در صورت نیاز Audio را برای Faster-Whisper آماده می‌کند.

Input path
  ↓
Discover media recursively
  ↓
Valid existing .media2ass.ass?
  ├─ YES → SKIP CONVERTED
  └─ NO
       ↓
Matching SRT?
  ├─ valid → SRT → ASS
  ├─ invalid → Whisper fallback
  └─ absent
       ↓
ffprobe audio preflight
  ├─ no audio → NO AUDIO / non-failure
  └─ audio exists
       ↓
Thermal safe?
       ↓
Faster-Whisper medium
       ↓
Timing-preserving segmentation
       ↓
ASS write + validation
       ↓
Diagnostics + RESULTS-HISTORY.md + tar.xz

این ترتیب چند مزیت عملی داشت. فایل معتبر دوباره Transcribe نمی‌شود، SRT قابل‌اعتماد بدون هزینه AI استفاده می‌شود، Media بدون Audio به‌اشتباه Failure محسوب نمی‌شود و هر مرحله قبل از رفتن به مرحله سنگین‌تر داده کافی برای عیب‌یابی تولید می‌کند.

فایل‌های اصلی

فایلنقشوضعیت
run.ps1Entry Point اصلی و اجرای Python داخلیActive
run.batWrapper ساده برای اجراActive
media2ass.pyهسته Discovery، SRT، Whisper، ASS و ValidationActive
config.jsonتنظیمات Canonical پروژهActive
SELF-TEST.ps1Self-test محیط و APIActive
DJH-CPU-Telemetry-Bridge.ps1خواندن سنسور CPU با LibreHardwareMonitorActive
RESULTS-HISTORY.mdتاریخچه Append-only اجراها و ValidationهاActive

محیط اجرا

این پروژه روی Windows و سیستم DJH PC توسعه و تست شد. سخت‌افزار ثبت‌شده برای تست‌های اصلی شامل AMD Ryzen 5 7500F و NVIDIA GeForce RTX 5060 Ti 16GB بود. این مشخصات روی اعداد Performance و Thermal اثر دارند و نباید نتایج Benchmark را بدون توجه به سخت‌افزار به سیستم دیگری تعمیم داد.

در Runtime نهایی، Python از داخل خود پروژه اجرا می‌شود و Normal Production نباید به System Python وابسته باشد. FFmpeg و FFprobe نیز در این پروژه به‌صورت ثابت از مسیر زیر خوانده می‌شوند:

C:\D\ffmpeg\ffmpeg.exe
C:\D\ffmpeg\ffprobe.exe

نسخه‌های Package که در Setup اولیه ثبت شده بودند شامل faster-whisper 1.2.1، ctranslate2 4.8.1، tokenizers 0.23.1، onnxruntime 1.29.0 و av 18.1.0 بودند. برای CPU Telemetry از LibreHardwareMonitor و برای GPU از nvidia-smi/NVML استفاده شد.

این پروژه برای Normal Run به اینترنت وابسته نیست. دانلود Packageها و Modelها در مرحله Setup می‌تواند Network بخواهد، اما Production باید از Modelهای Local استفاده کند.

Runtime مستقل

اولین تصمیم معماری این بود که مشکل Authentication مدل را با Retry یا Credential جدید دور نزنم. چون هدف نهایی Offline Production بود، وابستگی Runtime به Hugging Face در هر اجرا خودش یک Failure Point محسوب می‌شد. راه‌حل این شد که Python، Packageها و Modelها داخل Project Tree نگهداری شوند.

حدود ساعت 13:00 روز 21 اوت، Setup اولیه Project-local Runtime کامل شد. در ادامه، مدل‌های موجود نیز بین حدود 13:24 تا 13:27 Import و با SHA-256 بررسی شدند. اولین Run موفق Standalone نسخه v1.2.1 در ساعت 13:06 روی دو Media ثبت شد.

ساختار Restore‌شونده به شکل زیر است:

D:\AI\media2ass\
├─ python\
├─ packages\
├─ models\
├─ tools\
├─ media2ass.py
├─ run.ps1
├─ run.bat
├─ config.json
└─ RESULTS-HISTORY.md

Runtimeهای حجیم مثل Python، Packageها و Modelها در Knowledge Archive نهایی Duplicate نشدند؛ برای Restore واقعی باید آنها را جداگانه نگه داشت.

Benchmark مدل‌ها

نکته: این Benchmark یک رتبه‌بندی عمومی برای همه مدل‌های Whisper نیست؛ نتیجه به Dataset، سخت‌افزار، تنظیمات و نسخه‌های همین پروژه وابسته است و باید در همان Context تفسیر شود.

بعد از اینکه نسخه Standalone کار کرد، سؤال بعدی این بود که کدام مدل برای محتوای آموزشی انگلیسی انتخاب شود. چهار مدل tiny، small، medium و large-v3 روی فایل‌های واقعی مقایسه شدند. اولین Benchmark فقط انتخاب مدل را حل نکرد؛ یک Regression جدی در Segmentation را هم آشکار کرد.

Benchmark اول

در تست اولیه 7 فایل، زمان خام Transcription چنین ثبت شد:

مدلزمان کلبرداشت اولیه
tiny55.50sبسیار سریع، خطای واژگانی بیشتر
small103.77sتعادل سرعت و کیفیت
medium172.89sکیفیت پایدارتر
large-v3233.50sکندتر و یک Repetition Loop مشاهده شد

در همان تست، tiny خطاهایی مانند تشخیص نادرست نام‌ها و اصطلاحات داشت. small و medium از نظر Word-level نزدیک بودند، اما medium در واژه‌های تخصصی پایدارتر دیده شد. در یکی از فایل‌ها نیز large-v3 وارد حلقه تکرار شد و یک عبارت 8 کلمه‌ای 24 بار تکرار شد. این موضوع به‌تنهایی ثابت نمی‌کرد large-v3 همیشه مشکل دارد، اما کافی بود که آن را بدون Retest به‌عنوان Default انتخاب نکنم.

خطای Timeline

مهم‌ترین کشف Benchmark اول مربوط به مدل Whisper نبود؛ مشکل در Post-processing خود پروژه بود. تقریباً 99 درصد Cueها به حدود یک ثانیه نزدیک می‌شدند. Config اولیه min_duration = 1.0 و min_gap = 0.08 داشت و الگوریتم برای رسیدن به Minimum، Timestampهای بعدی را به جلو Push می‌کرد.

نتیجه یک Drift تجمعی بود. برای یک فایل واحد، آخرین Timestamp خروجی چهار مدل به این شکل ثبت شد:

tiny       978.73 sec
small      868.13 sec
medium     912.97 sec
large-v3   812.97 sec

اختلاف 165.76 ثانیه برای یک Media واحد قابل قبول نبود. در فایل دیگری Spread تا 314.40 ثانیه رسید. این Evidence نشان داد مشکل «اختلاف طبیعی مدل‌ها» نیست، بلکه خود Segmentation Timeline را تغییر می‌دهد.

چرا CPS هم گمراه‌کننده شد؟

در همان نسخه، سقف متن هر Cue برابر 84 کاراکتر و Duration اجباری تقریباً یک ثانیه بود. بنابراین Max CPS مدل‌ها به شکل مصنوعی روی عدد 84 می‌افتاد:

84 characters / 1 second = 84 CPS

این معیار دیگر کیفیت واقعی Subtitle را اندازه نمی‌گرفت؛ فقط محدودیت الگوریتم را منعکس می‌کرد. بنابراین تصمیم گرفتم هیچ انتخاب Production را بر اساس آن Benchmark قفل نکنم.

Fix اصلی

قاعده نهایی Segmentation این شد:

preserve_source_timestamps = true
never_shift_next_cue = true

یعنی برای بهتر کردن خوانایی می‌توان Cue را در فضای آزاد Merge یا Extend کرد، اما هیچ Cue حق ندارد Cue بعدی را به جلو هل بدهد. این تصمیم یکی از مهم‌ترین Invariantهای نسخه نهایی است، چون Regression آن مستقیماً Sync زیرنویس را خراب می‌کند.

Retest مدل‌ها

پس از اصلاح Timeline و Line Wrapping، Benchmark دیگری روی 4 فایل انجام شد. این بار 16 خروجی ASS ساخته شد و هر چهار مدل تقریباً در یک نقطه Timeline را تمام کردند. برای نمونه، در فایل چهارم پایان Timeline بین مدل‌ها فقط حدود 0.11 ثانیه اختلاف داشت:

tiny       972.04s
small      971.96s
medium     972.07s
large-v3   972.07s

همچنین Overlap برابر صفر شد و حداکثر طول Line دقیقاً در 42 کاراکتر نگه داشته شد.

کیفیت واژگان

در Dataset مربوط به Claude، مدل medium در تشخیص نام اصلی موضوع پایدارتر بود. شمارش ثبت‌شده برای واژه Claude در آن تست:

مدلClaude صحیحخطاهای شاخص
tiny43Cloud در 64 مورد
small81Clod در 21 مورد
medium100Cloud در 8 مورد
large-v327Cloud و Clod بیشتر

این معیار به‌تنهایی Accuracy کامل مدل نیست، اما چون محتوای تست مرتب درباره Claude صحبت می‌کرد، برای همین Dataset شاخص معناداری بود. در Word Sequence، مدل‌های small، medium و large-v3 اغلب حدود 95 تا 98 درصد به هم نزدیک بودند.

Performance سالم

مدلزمان 4 فایلVRAM Max
tiny77.93s~956 MB
small136.86s~1490 MB
medium230.09s~2383 MB
large-v3281.20s~3796 MB

large-v3 حدود 22 درصد کندتر از medium بود، VRAM بیشتری مصرف کرد و در این Dataset افزایش کیفیت قابل اثباتی نداد. به همین دلیل medium به Default Production تبدیل شد و small گزینه مناسب‌تر برای Fast Mode باقی ماند.

Retry اضافی

Retest سالم یک مشکل جدید را هم نشان داد: هر 16 Transcription یک Retry انجام می‌دادند و Trigger تقریباً همیشه word_timing بود. حتی در مواردی که نتیجه اول انتخاب می‌شد، هزینه Transcription دوم پرداخت شده بود.

این رفتار یک Regression Performance بود، نه Failure کیفیت. مثال ثبت‌شده نشان می‌داد زمان یک فایل با مدل small از حدود 7.6 ثانیه در تست قبلی به 15.77 ثانیه رسیده است؛ تقریباً دو برابر، چون عملاً دوبار Transcribe می‌شد.

Fix این بود که word_timing به‌عنوان Trigger عمومی Retry حذف شود:

"retry_on_word_timing": false

Retry در نسخه Production فقط برای Anomalyهای واقعی مانند Repetition، Overlap، Timing anomaly شدید، Text Integrity یا Cue خارج از Media Timeline باقی ماند. بررسی Runهای Production بعدی نشان داد Retry غیرضروری دیگر روی هر فایل تکرار نمی‌شود.

کنترل دما

چون پروژه برای Batchهای چندساعته طراحی شده بود، فقط ثبت Temperature کافی نبود؛ Thermal Monitoring باید واقعاً روی جریان کار اثر می‌گذاشت. برای GPU منبع اصلی NVML از طریق nvidia-smi شد و برای CPU از LibreHardwareMonitor استفاده کردم.

ابهام سنسور CPU

در توسعه اولیه، Labelهایی مثل CPU Package و CPU Max بیش از حد مبهم بودند. روی Ryzen 5 7500F سه مقدار مهم دیده می‌شدند: CPU Tctl/Tdie، CPU Die Average و CPU CCD1 Tdie. مشکل این بود که CCD1 می‌توانست Spike محلی بالاتری نشان دهد و اگر آن را معادل Control Temperature می‌گرفتم، Pauseهای کاذب ایجاد می‌شد.

Policy نهایی نقش سنسورها را جدا کرد:

CPU Tctl/Tdie    [CONTROL]
CPU Die Average  [INFO]
CPU CCD1 Tdie    [LOCAL]
GPU Core         [CONTROL]

بنابراین CCD1 برای Diagnostic ثبت می‌شود، ولی به‌تنهایی Trigger اصلی Pause نیست.

Policy نهایی

Pause heavy work : 85°C
Resume            : <=70°C
Poll              : 5s
Stable samples    : 3

این Hysteresis عمداً بزرگ انتخاب شد تا سیستم بعد از رسیدن به Limit واقعاً فرصت Cooling داشته باشد و در محدوده 84/83 درجه مرتب Pause/Resume نکند.

Run با Policy قدیمی

در v2.1.0 هنوز Policy قدیمی 80°C / 75°C فعال بود و CCD/local sensor نیز در Pause logic نقش داشت. در Production Run سه Thermal Pause ثبت شد و مجموع Wait حدود 90 ثانیه بود. Peak کنترل CPU تقریباً 82.125°C و GPU Peak برابر 61°C ثبت شد. این تجربه یکی از شواهدی بود که باعث شد Sensor Roles و Thresholdها شفاف‌تر شوند.

منطق SRT-first

یکی از بهینه‌سازی‌های مهم Production این بود که فایل SRT معتبر از Whisper ارزشمندتر است؛ چون Transcript آماده دارد، زمان پردازش را کم می‌کند و از یک Encode/Transcription غیرضروری جلوگیری می‌کند.

در Run واقعی 552 فایل، 94 SRT دقیقاً با Media متناظر Match شدند. از این تعداد 88 فایل مستقیم به ASS تبدیل شدند و 6 SRT به دلیل Overlap نامعتبر تشخیص داده شدند. هیچ Fuzzy ambiguity ثبت نشد.

برای 6 SRT نامعتبر، رفتار پروژه Safe بود: SRT حذف یا Force نشد، بلکه Failure آن ثبت شد و همان Media با Whisper پردازش شد. هر 6 Fallback در Run Production موفق شدند.

این بخش یک تفاوت مهم بین Match صحیح و Subtitle صحیح را نشان داد. فایل ممکن است از نظر نام دقیقاً متعلق به Media باشد، اما Timing داخلی آن هنوز نامعتبر باشد. بنابراین Matching به‌تنهایی برای Trust کردن Subtitle کافی نیست.

تست 552 فایل

مهم‌ترین Validation پروژه، اجرای Production روی 552 Media بود. نسخه v2.1.0 از حدود ساعت 17:40 تا 20:16 روز 21 اوت اجرا شد. نتیجه آن نشان داد معماری کلی درست است، اما یک Edge Case باقی مانده است.

وضعیتتعدادتفسیر
SRT → ASS88تبدیل مستقیم
Whisper → ASS459Transcription محلی
Skip Converted4ASS معتبر قبلی
SRT invalid → Whisper6Fallback موفق
Failed1Edge Case صوت

در عمل 551 فایل از 552 فایل به نتیجه قابل استفاده رسیدند. تنها Failure مربوط به فایل 7 Netstat Commands Explained For Network Analysis.mp4 بود. Error ثبت‌شده:

Output file does not contain any stream
Error opening output file ...audio-0016.wav
Error opening output files: Invalid argument

در ابتدا این پیام داخل Stage مربوط به Whisper دیده می‌شد، اما Diagnostic Bundle نشان داد Whisper اصلاً شروع نشده بود؛ خطا در Audio Extraction رخ داده بود. این تفاوت برای Root Cause مهم بود.

فایل بدون صدا

بررسی Diagnostic نشان داد Media مشکل‌دار SRT نداشت و FFmpeg هنگام ساخت WAV هیچ Audio Stream پیدا نمی‌کرد. بنابراین Symptom «Whisper failed» نبود؛ Root Cause واقعی این بود که فایل ورودی Audio Stream نداشت.

اینجا دو وضعیت باید از هم جدا می‌شدند:

NO AUDIO
→ Media اصلاً Audio Stream ندارد
→ Non-failure / skip

AUDIO EXTRACT FAILED
→ ffprobe Audio را دیده، اما FFmpeg نتوانسته آن را استخراج کند
→ Failure واقعی

در v2.2.0 قبل از Extract یک ffprobe preflight اضافه شد و گزینه زیر در Config تثبیت شد:

"probe_before_extract": true
"no_audio_is_failure": false

این تغییر یک Workaround نبود؛ Classification Pipeline اصلاح شد تا Error واقعی از وضعیت طبیعی Media بدون Audio جدا شود.

Validation نهایی

Run نهایی v2.2.0 با ID 20260821-203437 در ساعت 20:34:45 به Summary نهایی رسید. چون 551 خروجی معتبر قبلاً در Run بزرگ ساخته شده بودند، هدف این Run تبدیل دوباره نبود؛ هدف تأیید Resume/Skip Logic و Edge Case بدون Audio بود.

TOTAL             : 552
SKIP CONVERTED    : 551
NO AUDIO          : 1
AUDIO EXTRACT FAIL: 0
FAILED            : 0
THERMAL PAUSES    : 0
THERMAL WAIT      : 0.0 sec
LIMIT REACHED     : NO

MEDIA2ASS RESULT: PASS

در این Validation، Peak سنسور کنترل CPU حدود 56°C، CCD1 حدود 54.25°C و GPU Core برابر 43°C ثبت شد. این Run سبک‌تر بود چون تقریباً همه فایل‌ها Skip شدند؛ بنابراین این دماها را نباید با Run کامل Transcription به‌عنوان Benchmark مستقیم مقایسه کرد.

نتیجه Dataset در آخرین وضعیت قابل اثبات این است: 551 Media دارای ASS معتبر هستند، یک Media Audio Stream ندارد و Failure حل‌نشده‌ای باقی نمانده است.

کیفیت زیرنویس

بعد از PASS شدن Pipeline، فقط به Exit Code اکتفا نکردم. یک بسته واقعی از Subtitleها بررسی شد تا ساختار ASS، Timing و محدودیت Line Length مستقل از Run Summary کنترل شود.

Audit فایل‌محور روی 93 ASS با مجموع 12,010 Cue این نتیجه را داد:

معیارنتیجهوضعیت
Overlap0PASS
Duration نامعتبر0PASS
File غیر Monotonic0PASS
Line بیش از 42 کاراکتر0PASS
Cue بالای 20 CPS2,560Known Limitation
Max CPS40.48Non-blocking

همچنین 6 فایل SRT منبع با مجموع 1,662 Cue بررسی شدند و همان 6 Overlap منبعی که Pipeline گزارش کرده بود در Audit نیز دیده شد. این تطابق، رفتار Fallback را تأیید کرد.

چرا CPS را Force نکردم؟

21.32 درصد Cueهای نمونه بالاتر از 20 CPS بودند، اما تجربه Regression قبلی نشان داده بود که «اصلاح» خوانایی با جابه‌جایی Timestamp می‌تواند Sync کل فایل را خراب کند. بنابراین CPS در نسخه نهایی یک Warning/Accepted Limitation است، نه شرطی که Timeline را دستکاری کند. هر Optimization آینده باید بدون Shift کردن Cue بعدی انجام شود.

Config نهایی

بخش‌های زیر هسته تنظیمات Production v2.2.0 را نشان می‌دهند. این‌ها Extract مرتبط از Config واقعی هستند، نه یک Config فرضی.

Whisper و Retry

{
  "models": {
    "active": "medium"
  },
  "whisper": {
    "language": "en",
    "device": "auto",
    "compute_type": "auto",
    "beam_size": 5,
    "vad_filter": true,
    "condition_on_previous_text": true,
    "word_timestamps": true,
    "temperature": 0.0
  },
  "retry_guard": {
    "enabled": true,
    "retry_on_word_timing": false,
    "repetition_ngram_words": 8,
    "repetition_threshold": 5,
    "timing_retry_max_cps": 60.0
  }
}

Segmentation

{
  "segmentation": {
    "max_lines": 2,
    "max_chars_per_line": 42,
    "max_chars_per_cue": 84,
    "target_cps": 17.0,
    "warn_cps": 20.0,
    "preferred_min_duration": 1.0,
    "max_duration": 6.0,
    "merge_high_cps": true,
    "extend_short_cues": true,
    "balance_lines": true,
    "preserve_source_timestamps": true,
    "never_shift_next_cue": true
  }
}

Thermal Guard

{
  "thermal_guard": {
    "enabled": true,
    "max_temperature_c": 85.0,
    "resume_temperature_c": 70.0,
    "poll_seconds": 5.0,
    "stable_samples_required": 3,
    "watch_cpu_package": true,
    "watch_cpu_max": false,
    "watch_gpu": true,
    "cpu_control_role": "CPU Tctl/Tdie",
    "cpu_ccd1_role": "local_diagnostic",
    "gpu_control_role": "GPU Core / NVML"
  }
}

Diagnostic

{
  "diagnostics": {
    "enabled": true,
    "bundle_enabled": true,
    "format": "tar.xz",
    "xz_preset": 9,
    "xz_extreme": true,
    "zip_fallback": false,
    "results_history_file": "RESULTS-HISTORY.md"
  }
}

روش اجرا

اگر بخواهم فقط روش درست و نهایی را به خواننده بدهم، بدون اینکه مجبور شود Trial & Error پروژه را تکرار کند، مسیر به شکل زیر است.

  1. Project Root، Embedded Python، Packageها، Model محلی و FFmpeg/FFprobe را آماده کنید.
  2. config.json را با Pathهای سیستم خودتان تطبیق دهید؛ مخصوصاً FFmpeg، Model و Runtime.
  3. Self-test را اجرا کنید و قبل از Production مطمئن شوید Runtime و Telemetry قابل دسترس هستند.
  4. برای یک Dataset کوچک Run واقعی بگیرید.
  5. ASSهای خروجی و Diagnostic Bundle را بررسی کنید.
  6. سپس مسیر اصلی Media را به Entry Point بدهید.

دستور اصلی روی سیستم توسعه من:

D:\AI\media2ass\run.ps1 "D:\Path\To\Media"

یا Wrapper:

D:\AI\media2ass\run.bat "D:\Path\To\Media"

Self-test:

D:\AI\media2ass\SELF-TEST.ps1

و تست CPU Telemetry:

D:\AI\media2ass\TEST-CPU-TELEMETRY.ps1

این Pathها مربوط به سیستم من هستند و در سیستم دیگر باید متناسب با محل نصب Project تغییر داده شوند. Dedicated command مستقلی با نام Dry Run یا Recovery Mode در نسخه نهایی قابل اثبات نبود؛ Recovery عملی پروژه عمدتاً با Skip کردن خروجی‌های معتبر و اجرای مجدد همان Input انجام می‌شود.

خطاهای مهم

Hugging Face 401

مشاهده: جریان قدیمی هنگام دسترسی به Repository مدل شکست خورد. تصمیم: به‌جای وابسته نگه داشتن Production به Credential/Network، مدل‌ها به Project-local storage منتقل شدند. وضعیت: Historical و حل‌شده در معماری جدید.

Import benchmark

خطا: ModuleNotFoundError: media2ass. علت: نحوه Load ماژول در Benchmark. Fix: Benchmark به‌شکل Project-aware/standalone اصلاح شد. وضعیت: حل‌شده.

CPU=N/A

مشاهده: GPU Telemetry در دسترس بود اما CPU مقدار معتبری نمی‌داد. Fix: استفاده مستقیم از LibreHardwareMonitor DLL و Mapping سنسور Ryzen. وضعیت: حل‌شده.

find_smi regression

خطا: AttributeError: module 'media2ass' has no attribute 'find_smi'. Fix: API مشترک Telemetry بازگردانده و Self-test برای جلوگیری از Regression اضافه شد. وضعیت: حل‌شده.

Timeline drift

مشاهده: پایان Timeline یک Media بین مدل‌ها تا چند دقیقه اختلاف داشت. Root Cause: Push کردن Timestampهای بعدی برای enforce کردن Minimum Duration. Fix: حفظ Source Timestamp و ممنوعیت Shift کردن Cue بعدی. وضعیت: حل‌شده و در Retest تأیید شد.

Retry کاذب

مشاهده: 16 از 16 Transcription Benchmark دوبار اجرا شدند. Root Cause: Trigger بیش از حد حساس word_timing. Fix: غیرفعال شدن این Trigger و حفظ Retry برای Anomalyهای واقعی. وضعیت: حل‌شده در Production.

SRT overlap

مشاهده: شش SRT درست Match شدند اما Timing آنها Overlap داشت. Fix: SRT نامعتبر Trust نشد و Media به Whisper Fallback کرد. وضعیت: Handle شده؛ Source SRTها خودشان Fix نشده‌اند.

No Audio

خطا: Output file does not contain any stream. Root Cause: Media فاقد Audio Stream بود. Fix: ffprobe preflight و دسته‌بندی مستقل NO AUDIO. Retest: Run نهایی 552 فایل با 0 Failure. وضعیت: حل‌شده و Validated.

وضعیت Issueها

این جدول فقط Summary آخرین وضعیت است؛ جزئیات تشخیص و Fix در بخش‌های قبلی آمده‌اند.

مشکلاقدام نهاییوضعیت
Online model/auth dependencyLocal/offline model runtimeResolved
Benchmark importProject-aware loadingResolved
CPU telemetry unavailableLibreHardwareMonitor bridgeResolved
Timeline driftNever shift next cueValidated
Line بالای 42Balanced strict wrappingValidated
large-v3 repetitionRetry guard + medium defaultMitigated
False word_timing retryTrigger disabledResolved
SRT overlapWhisper fallbackHandled
No-audio mediaffprobe preflightValidated
CPS بالای 20Warning only; no timeline pushAccepted Limitation

Timeline پروژه

برای رویدادهایی که Timestamp قابل اتکا داشتند، ترتیب توسعه به این شکل ثبت شده است:

زمانرویدادنتیجه
21 Aug ~12:31خطای Authentication در جریان قدیمیTrigger بازطراحی
~13:00Setup Runtime داخلیCompleted
13:06اولین Run موفق v1.2.1 روی 2 MediaPASS
13:24–13:27Import مدل‌های Local + SHA256PASS
13:34 onwardBenchmark چهار مدلRegression کشف شد
14:06–16:29Telemetry و Mapping سنسورهاStabilized
16:31–16:42Benchmark اصلاح‌شدهPASS
17:13شروع v2.0 ProductionIssueهای جدید دیده شد
17:40–20:16v2.1 روی 552 Media551 resolved / 1 fail
بعد از 20:16تشخیص Media بدون AudioRoot Cause confirmed
20:34:45Validation نهایی v2.2PASS / 0 Failure
~21:22Audit فایل‌های SubtitlePASS

وضعیت پروژه

تنها محدودیت پذیرفته‌شده، وجود Cueهای با CPS بالاتر از 20 در بخشی از خروجی‌هاست. این مورد به‌عمد با دستکاری Timeline «حل» نشده، چون Sync و Text Integrity اولویت بالاتری دارند.

آخرین نسخه قابل اثبات v2.2.0 FINAL STABLE است. Run نهایی روی مجموعه 552 Media با 551 خروجی معتبر، یک Media بدون Audio و صفر Failure به پایان رسید. Audit مستقل 93 فایل ASS نیز صفر Overlap، صفر Timing نامعتبر و صفر Line بالاتر از 42 کاراکتر را نشان داد.

مهم‌ترین Lesson فنی پروژه این بود که کیفیت Subtitle فقط به مدل Speech-to-Text وابسته نیست. یک Post-processing اشتباه می‌تواند حتی Transcript خوب را با Timeline Drift خراب کند. همچنین بزرگ‌ترین مدل الزاماً بهترین انتخاب Production نیست؛ در Dataset واقعی من medium نسبت به large-v3 هزینه کمتر و پایداری واژگانی بهتری نشان داد.

مسئله اولیه، ساخت زیرنویس ASS برای تعداد زیاد Media با Runtime مستقل و قابل اتکا بود. معماری نهایی از Embedded Python، Modelهای Local Faster-Whisper، FFmpeg/ffprobe، SRT-first selection، Validation، Thermal Guard و Diagnostic Bundle استفاده می‌کند.

  • Last Known Version: v2.2.0 FINAL STABLE
  • Last Run ID: 20260821-203437
  • Last Run Result: PASS
  • Total Media: 552
  • Valid ASS: 551
  • No Audio: 1
  • Unresolved Failure: 0
  • Quality Audit: 93 ASS / 12,010 Cue / PASS
  • Accepted Limitation: CPS spikes above 20
  • Production Blocker: None known

یک Evidence Gap باقی مانده است: Raw Diagnostic Bundle نهایی v2.2.0 داخل Archive مستندات فعلی موجود نبود، هرچند مسیر Bundle و Summary Console آن ثبت شده است. برای وضعیت فعلی پروژه این Gap مانع تعیین Baseline نشده، چون Source نهایی، Run Summary، Diagnostic کامل v2.1، Benchmarkها و Audit فایل‌محور در دسترس بوده‌اند.

سوالات متداول

media2ass دقیقاً چه کاری انجام می‌دهد؟

media2ass فایل‌های Media را بررسی می‌کند و در صورت نیاز با SRT موجود یا Faster-Whisper برای آنها زیرنویس ASS می‌سازد. خروجی سپس از نظر Timing، Overlap، طول Line و Text Integrity بررسی می‌شود.

برای اجرای media2ass اینترنت لازم است؟

Normal Production نسخه نهایی با Python Runtime و Modelهای Local طراحی شده و پس از آماده‌شدن Dependencyها به دانلود Model در هر Run وابسته نیست. Setup اولیه یا تهیه Modelها می‌تواند به Network نیاز داشته باشد.

آیا داشتن کارت گرافیک NVIDIA اجباری است؟

نسخه Production مستندشده روی NVIDIA CUDA تست و Validate شده است. اجرای CPU-only در Final Production این پروژه به‌صورت مستقل Benchmark و تأیید نشده، بنابراین برای همان رفتار و Performance ثبت‌شده باید محیط GPU/CUDA را مبنا گرفت.

چرا مدل medium پیش‌فرض شد؟

در Benchmark سالم، medium روی Dataset آموزشی تست‌شده از نظر واژه‌های تخصصی پایدار بود و large-v3 افزایش کیفیت قابل اثباتی ارائه نکرد. large-v3 همچنین حدود 22٪ زمان بیشتری مصرف کرد.

آیا مدل small ارزش استفاده دارد؟

بله؛ small در تست‌ها بهترین گزینه Speed/Quality بود و برای حالت سریع انتخاب منطقی‌تری است. با این حال Default Production برای بیشترین پایداری متن روی Dataset تست‌شده medium باقی ماند.

اگر کنار ویدیو SRT وجود داشته باشد چه می‌شود؟

Pipeline ابتدا SRT متناظر را پیدا و Validate می‌کند. اگر SRT معتبر باشد مستقیم به ASS تبدیل می‌شود؛ اگر Timing آن مشکل داشته باشد، Media به Whisper Fallback می‌کند.

اگر ASS از قبل ساخته شده باشد دوباره تبدیل می‌شود؟

خیر، اگر خروجی موجود Validation را پاس کند با وضعیت SKIP CONVERTED رد می‌شود. Run نهایی این رفتار را برای 551 فایل موجود تأیید کرد.

اگر فایل ویدیویی Audio نداشته باشد چه می‌شود؟

از v2.2.0، ffprobe قبل از Audio Extraction وجود Stream صوتی را بررسی می‌کند. فایل بدون Audio با وضعیت NO AUDIO ثبت می‌شود و Failure محسوب نمی‌شود.

تفاوت No Audio با Audio Extract Failed چیست؟

NO AUDIO یعنی Media اساساً Stream صوتی ندارد. AUDIO EXTRACT FAILED یعنی Audio وجود داشته اما FFmpeg نتوانسته آن را استخراج کند؛ این دو وضعیت در نسخه نهایی جدا گزارش می‌شوند.

Thermal Guard چگونه از سیستم محافظت می‌کند؟

در Policy نهایی، رسیدن سنسور کنترل CPU یا GPU به 85°C باعث توقف شروع Heavy Work بعدی می‌شود و Resume پس از خنک‌شدن تا 70°C یا پایین‌تر انجام می‌گیرد. CCD1 فقط برای Diagnostic ثبت می‌شود و Trigger اصلی Pause نیست.

چرا CPU CCD1 معیار اصلی توقف نیست؟

CCD1 می‌تواند Spike موضعی بالاتری از دمای کنترلی CPU نشان دهد. در این پروژه Tctl/Tdie سنسور Control است و CCD1 فقط به‌عنوان Local Diagnostic نگهداری می‌شود.

چرا Cueهای بالای 20 CPS هنوز باقی مانده‌اند؟

چون نسخه‌های اولیه برای کاهش CPS، Timestampها را جابه‌جا کردند و باعث Timeline Drift جدی شدند. در نسخه نهایی Sync و Text Integrity اولویت دارند و High-CPS به‌عنوان Warning غیرمسدودکننده ثبت می‌شود.

چطور مطمئن شویم زیرنویس خروجی خراب نشده است؟

Validation نهایی فقط Exit Code را بررسی نمی‌کند؛ Overlap، Duration نامعتبر، ترتیب Timeline، طول Line و Cueهای خارج از Media نیز کنترل می‌شوند. Audit واقعی 93 فایل ASS و 12,010 Cue، صفر Overlap و صفر Timing نامعتبر ثبت کرد.

برای اجرای دوباره یک Batch بزرگ باید از کجا شروع کرد؟

همان Input Root را دوباره به run.ps1 بدهید. Skip Logic خروجی‌های معتبر قبلی را کنار می‌گذارد و فقط فایل‌های جدید، نامعتبر یا تعیین‌تکلیف‌نشده نیاز به پردازش خواهند داشت.

نتیجه‌گیری

ساخت media2ass در نهایت بیشتر از یک پروژه Speech-to-Text شد. مسئله اصلی به مجموعه‌ای از موضوعات Runtime، Model Management، Timing، Validation، Thermal Monitoring، Resume Logic و Diagnostic تبدیل شد. مهم‌ترین نتیجه برای من این بود که Reliability از یک «مدل قوی‌تر» یا یک Run موفق به‌تنهایی به دست نمی‌آید؛ باید بتوان هر تصمیم را با Log، Retest و Validation دنبال کرد.

پروژه ساخت زیرنویس از ویدیو Media2Ass داریوش حقیقی
پروژه ساخت زیرنویس از ویدیو Media2Ass داریوش حقیقی

نسخه v2.2.0 آخرین Baseline معتبر این پروژه است. در آن، Runtime مستقل و Offline شده، مدل medium بر اساس Benchmark واقعی انتخاب شده، Timeline Drift رفع و Retest شده، SRT-first و Skip Logic در Production جواب داده، Thermal Sensor Roles اصلاح شده و Edge Case بدون Audio نیز در آخرین Run به‌صورت درست طبقه‌بندی شده است.

اگر توسعه در آینده ادامه پیدا کند، منطقی‌ترین مسیر این است که v2.2.0 دست‌نخورده به‌عنوان Recovery Baseline حفظ شود و هر تغییر جدید با Regression Test جداگانه روی Timing، Text Integrity، Skip Logic و Thermal Guard انجام شود. تنها بهبود پیشنهادی فعلی، بررسی روش‌های امن برای کاهش Cueهای High-CPS بدون جابه‌جایی Timestampهای بعدی است؛ این مورد هنوز Requirement قطعی پروژه نیست.

داریوش حقیقی
نویسنده و توسعه‌دهنده

داریوش حقیقی

بیش از 20 سال تجربه در حوزه فناوری اطلاعات، طراحی سایت، سئو تکنیکال، مدیریت سرورهای لینوکس و ویندوز، توسعه وردپرس، برنامه‌نویسی، اتوماسیون و هوش مصنوعی.در djh.ir تلاش می‌کنم تجربیات واقعی پروژه‌های اجرایی، آموزش‌های کاربردی و راهکارهای عملی را با زبانی ساده و قابل استفاده منتشر کنم.

20+ سال تجربه
100+ پروژه اجرایی
1000+ ساعت آموزش

نظر و سوالتون رو اینجا بنویسید...

تماس در تلگرام