Communitygithub.com

peditx/persian-sub-tizone-skill

ویدیو/صدا را به زیرنویس SRT فارسی تبدیل می‌کند: استخراج صدا با ffmpeg → تبدیل گفتار به متن با 9Router → هم‌ترازی زمانی بلوک‌های هم‌پوشان → خروجی SRT صیقلی‌شده. جدید در ۲۰۲۶-۰۹-۲۶.

persian-sub-tizone-skill란 무엇인가요?

این اسکیل یک خط لوله کامل برای تولید خودکار زیرنویس SRT فارسی از ویدیو یا فایل صوتی است. ابتدا با ffmpeg صدای ویدیو استخراج می‌شود، سپس با استفاده از API 9Router (مدل‌های gemini/groq) به متن کامل تبدیل می‌شود. برای دقت زمانی بالا، صدا به بلوک‌های ۶ ثانیه‌ای با هم‌پوشانی ۴ ثانیه تقسیم می‌شود و هر بلوک جداگانه ترنکرایب می‌شود. سپس با `difflib.SequenceMatcher` کلمات استخراج‌شده از متن کامل با کلمات هر بلوک هم‌تراز می‌شوند تا زمان‌بندی دقیق هر کلمه به‌دست آید. در نهایت زیرنویس‌ها با قوانین هوشمند (حداکثر ۸۴ کاراکتر در هر خط، حداکثر ۷ ثانیه نمایش، ادغام خطوط کوتاه) به فایل SRT تبدیل می‌شوند. این اسکیل از resume خودکار، fallback در صورت محدودیت نرخ (429)، و تست داخلی پشتیبانی می‌کند. خروجی مستقیماً در نرم‌افزارهایی مثل Premiere Pro قابل استفاده است.

지원 대상✓Claude Code~Codex CLI~Cursor✓Gemini CLI
npx skills add peditx/persian-sub-tizone-skill

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Persian Sub Tizone — ساخت زیرنویس SRT فارسی

خط لوله: ffmpeg (استخراج صدا) → 9Router /v1/audio/transcriptions (ترنکرایب) → هم‌ترازی زمان با بلوک‌های هم‌پوشان → خروجی .srt.

روشِ سریع — همین را اجرا کن

یک فرمان، کل کار را می‌کند (استخراج صدا → متن کاملِ یک‌باره → بلوک‌های هم‌پوشان با کش → هم‌ترازی → نوشتن .srt):

python3 pipeline/stt.py "$VIDEO"                       # → کنارِ ویدیو: video.srt
python3 pipeline/stt.py "$VIDEO" -m groq/whisper-large-v3-turbo   # جایگزینِ مدل

خروجی: فایل video.srt کنار فایل اصلی — مستقیم در Premiere قابل Import است (File → Import یا درگ به تایم‌لاین).

رفتاری که باید بدانی:

  • کار در پوشهٔ <video>.pst/ کنارِ خود فایل می‌ماند؛ اجرای دوباره از همان‌جا ادامه می‌دهد (بلوک‌های تمام‌شده دوباره صدا زده نمی‌شوند، فقط بلوکِ نیمه‌کاره تکرار می‌شود).
  • ۴۲۹ (quota): پیام را می‌خواند و همان‌قدر می‌خوابد (۳ بار)، بعد اگر باز هم ۴۲۹ بود به gemini/gemini-2.5-flash-lite (سهمیهٔ جدا) فالبک می‌زند. فاصلهٔ ۳–۵ ثانیه بین فراخوانی‌ها و timeout هر کدام ۶۰ ثانیه هم داخل اسکریپت است.
  • مدل پیش‌فرض gemini/gemini-2.5-flash است (بهترین متن فارسی).
  • بدون کلید خاموش نمی‌شود: NINEROUTER_KEY نیست می‌گوید و می‌ایستد — کلید را از کاربر بگیر، حدس نزن.

چک‌ها (بدون کلید و بدون شبکه):

python3 pipeline/stt.py --selftest    # chunking + multipart + پارسِ 429
python3 pipeline/test_e2e.py          # کل خط لوله روی سرور STTِ جعلیِ محلی

پیش‌نیاز (یک‌بار)

export NINEROUTER_URL="https://9router.peditx.ir"   # یا هاست 9Router کاربر
export NINEROUTER_KEY="sk-..."
curl -s "$NINEROUTER_URL/api/health"                 # باید {"ok":true} بدهد

اگر key نبود از کاربر بگیر؛ حدس نزن.


جزئیات خط لوله (فقط برای عیب‌یابی/توسعه)

۱) استخراج صدا

DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$VIDEO")
ffmpeg -y -i "$VIDEO" -vn -ac 1 -ar 16000 -b:a 64k /tmp/audio.mp3

۲) ترنکرایب — بلوک‌های هم‌پوشان ۶ ثانیه‌ای (hop=4s)

# هر کلمه در ≥ یک بلوک قرار می‌گیرد
ffmpeg -i audio.mp3 -ss <t> -t 6 -c copy ch/NN_t.mp3
# هر بلوک: POST /v1/audio/transcriptions  model=gemini/gemini-2.5-flash  response_format=json
  • ۴۲۹ را جدی بگیر: پیام را بخوان، reset after Xm را همان‌قدر بخواب، دوباره بزن.
  • پاسخ‌های خراب/ناقص را با try/except رد کن و حداکثر ۳ بار retry بزن.
  • خروجی‌ها را در chunks.json ذخیره کن تا resume کار کند.

۳) هم‌ترازی زمان (کلید کیفیت)

متن کامل را یک‌بار از روی کل ویدیو بگیر، بعد کلمات را روی بلوک‌ها با difflib.SequenceMatcher هم‌تراز کن و به هر کلمه یک زمان interpolate کن:

import difflib, re
W = lambda s: re.findall(r"[^\s]+", s)
norm = lambda w: re.sub(r"[^\w؀-ۿ]", "", w.lower())
gw = W(full_text)
# برای هر بلوک: بهترین جایگاه start در gw را با SequenceMatcher پیدا کن
# word j ← time = start + 6 * i/(n-1) ؛ خلأها را interpolate کن

اگر ratio < 0.45 بود بلوک را رد کن و از زمان‌های segments همان بلوک به‌عنوان fallback استفاده کن.

۴) ساختن SRT

MAX_CHARS, MAX_DUR = 84, 7.0
SOFT_CHARS, SOFT_DUR = 72, 6.0
# شکستن: اول پایان جمله (. ؟ !) با طول ≥30 → اولویت؛ وگنه کاما/طول/مدت
# اگر cue زیر 22 کاراکتر بود با cue قبلی ادغامش کن
def ts(x): return f"{int(x//3600):02d}:{int(x%3600//60):02d}:{int(x%60):02d},{int(round((x-int(x))*1000)):03d}"
# شکستن خط: تعادل دو خط در ۴۶ کاراکتر (حداکثر ۲ خط)

پیاده‌سازی: pipeline/mk_srt.py

python3 pipeline/mk_srt.py -t g.json -c chunks.json --media "$VIDEO" -o "${VIDEO%.*}.srt"
python3 pipeline/mk_srt.py --selftest

۵) صیقلی‌سازی املای فارسی (اختیاری)

{"model":"claude-sonnet-4-6","temperature":0.1,
 "messages":[{"role":"system","content":"ویراستار فارسی. خروجی فقط JSON معتبر."},
   {"role":"user","content":"فقط املای فارسی را اصلاح کن؛ معنی/تعداد کلمات/ترتیب را عوض نکن. خروجی: [{\"i\":..,\"t\":\"..\"}]"}]}

خروجی باید دقیقاً همان تعداد cue را برگرداند؛ وگرنه assert کن و به متن خام برگرد. نرمال‌سازی امن: ي → ی و ك → ک.

۶) تأیید نهایی

  • یک قاب از ویدیو در چند نقطه بخوان (fps=1/6) و متن زیرنویس روی تصویر را با cueها چک کن.
  • چند بخش کوتاه جدا (ffmpeg -ss) را STT بزن و با زمان cue تطبیق بده؛ اگر جابه‌جایی > ۲ ثانیه بود زمان‌ها را اصلاح کن.

نکات عملی

  • ffmpeg/ffprobe همیشه هستند؛ whisper محلی نصب نیست.
  • اسکریپت را با python3 - <<'PY' اجرا کن؛ برای payloadهای بزرگ -d @file.json بده نه argv.
  • زمان هر فراخوانی را محدود کن (-m 60)؛ timeout محیط حدود ۱۷۵ ثانیه است → فراخوانی‌ها را قطعه‌قطعه کن.
  • نام فایل نهایی: کنار ویدیو با پسوند .srt.

فایل‌های خروجی قابل استفاده در Premiere:

فایلکاربرد
video.srtزیرنویس — مستقیم File → Import یا درگ به تایم‌لاین
video.mp4.pst/chunks.jsonکش STT (برای fix دستی بعدی)
video.mp4.pst/full.jsonمتن کامل مرجع

관련 스킬