انتقل للمحتوى
كل دراسات الحالة
أداة فيديو مفتوحة المصدرمدة القراءة: 8 دقايق

Qatf: من فيديوهات طويلة لمقاطع رأسية، مع دعم النص العربي من اليمين للشمال

أداة مفتوحة المصدر (Apache-2.0) بتحوّل الفيديو الطويل لمقاطع 9:16 عليها captions كلمة بكلمة. موديل لغوي هو اللي بيختار اللحظات؛ لكن التفريغ وضبط حدود القص والـ rendering بيفضلوا deterministic، والـ captions بالعربي من اليمين للشمال جزء أساسي من الأداة. أنا اللي عملتها وبتابع صيانتها؛ وهي نموذج أولي شغال.

الكود (Apache-2.0)منشئ المشروع والـ maintainer · مستقل، مفتوح المصدر · أغسطس 2026 – لحد دلوقتي
مراحل في الـ pipeline
5
presets للموديلات
8
أشكال للـ captions كلمة بكلمة
4
رخصة مفتوحة المصدر
Apache-2.0

المشكلة

إيه المشكلة اللي كنا بنحلها؟

تحويل محاضرة طويلة لمقاطع Reels و Shorts و TikTok يدويًا بياخد وقت، والأدوات اللي بتعمل ده أوتوماتيك مصممة أساسًا للإنجليزي. مع العربي، أداة التفريغ ممكن تكتب المصطلحات التقنية غلط، والـ captions من اليمين للشمال ممكن تطلع بترتيب كلمات غلط.

واختيار المقاطع بموديل لغوي بيعمل مشكلة تانية: الموديلات بتقدر تختار أجزاء تلفت الانتباه، لكن توقيتها مش دقيق، فالمقاطع بتبدأ أو بتخلص في نص كلمة.

القيود

إيه القيود اللي أثّرت على التصميم؟

  • لازم دعم العربي وعرض الـ captions من اليمين للشمال يبقى بنفس جودة دعم الإنجليزي. والتحقق يكون بفحص frames من الفيديو الناتج فعلًا.
  • الموديل مسموح له يختار الأجزاء ويقترح توقيتها، لكن الكود هو اللي بيحدد مكان القص النهائي على حدود كلمة حقيقية حسب توقيتات التفريغ.
  • تغيير الـ provider بتاع الموديل، سواء hosted أو local، لازم ما يحتاجش أي تغيير في الكود، وما يأثرش على دقة القص.
  • أمر واحد للتشغيل على لابتوب أو على جهاز فيه GPU؛ الـ GPU بيسرّع الشغل بس مش شرط.
  • كل إعداد افتراضي لازم يكون وراه قياس متسجّل، وده يشمل الاختيارات اللي اتجربت واترفضت.

الـ architecture

النظام متصمم إزاي؟

خمس مراحل بتعتمد على job model واحد. Whisper بيحوّل الكلام لكلمات بتوقيتاتها، واستدعاء واحد للموديل بيختار الأجزاء، وكل حاجة تخص مكان القص deterministic. ونفس الـ pipeline بيشتغل ورا CLI، و service بـ FastAPI، وواجهة ويب بـ React.

  1. المدخلات

    • رفع ملف

      رفع multipart، والحد الافتراضي 2 GB.

    • مسار على السيرفر

      محصور جوه media root، فمفيش request يقدر يحدد ملف عشوائي.

    • لينك YouTube

      بيتنزّل بـ yt-dlp، والـ host لازم يطابق allowlist بالضبط.

  2. الـ pipeline

    • الصوت

      فصل الصوت بـ ffmpeg (demux)، و denoising اختياري.

    • التفريغ

      faster-whisper large-v3 على الـ GPU لو ينفع يتستخدم، أو الـ caption track بتاع الفيديو نفسه.

    • الاختيار

      الاستدعاء الوحيد للموديل: أنهي أجزاء، بصيغة MM:SS.

    • الضبط على الكلمات

      بداية المقطع ونهايته بيتظبطوا على توقيتات الكلمات؛ وده deterministic.

    • الـ render

      ضبط الكادر على 9:16، و captions كلمة بكلمة مدموجة في الفيديو (burned-in)، بـ H.265 أو H.264.

  3. الواجهات

    • CLI

      مرحلة التخطيط بس، وبعدها تعديل plan.json، وبعدين render للخطة دي بالضبط.

    • HTTP API

      مبني على FastAPI؛ كل طلب start بيرجّع 202 و job id، والـ client بيعمل polling.

    • واجهة الويب

      React و Vite، بالإنجليزي والعربي مع layout معكوس (mirrored).

  4. الحالة

    • SQLite (WAL)

      سجلات الـ jobs، وتصحيحات الكلمات، و cache للتفريغ الـ key بتاعه مكوّن من الموديل واللغة وقائمة المصطلحات.

  • الـ imports ماشية في اتجاه واحد: core ← llm ← pipeline ← jobs ← api. وفيه smoke test بيقرا كود الـ core package، ويفشل لو لقى أي import عكس الاتجاه المحدد.
  • المراحل 1 و 4 و 5 عمرها ما بتستدعي أي موديل، فتغيير الـ provider ما يقدرش يغيّر مكان القص ولا شكل الـ render بتاع المقطع.

المميزات

المنتج بيعمل إيه؟

إدخال الفيديو

  • رفع ملف (الحد الافتراضي 2 GB)، أو تحديد ملف موجود أصلًا على السيرفر، أو لصق لينك YouTube.
  • المقبول بس youtube.com و m.youtube.com و music.youtube.com و youtu.be؛ وأي حاجة غير كده بتترفض.

من الكلام لكلمات

  • Whisper من tiny لحد large-v3، على الـ GPU لو فيه واحد ينفع يتستخدم، أو بالإجبار على CUDA أو CPU. والجهاز اللي بيتحدد بالاسم عمره ما بيتحوّل لغيره في صمت.
  • اللغة بتتحدد يدويًا أو الأداة بتتعرف عليها تلقائيًا؛ ولو المصدر لينك YouTube، الـ caption track بتاع الفيديو نفسه ممكن ياخد مكان Whisper لو فيه توقيت لكل كلمة.
  • للعربي: قائمة مصطلحات تكتبوا بيها الأسماء والمصطلحات بالشكل المطلوب، و denoising للصوت اللي فيه ضوضاء، و fix-ups بتصحّح إملاء الكلمة في كل مكان.
  • بعد كده ممكن تصحيح أي كلمة باليد، أو قبول اقتراحات الموديل واحد واحد. التعديل بيغيّر نص الـ captions بس، ونقطة القص بتفضل زي ما هي.

اختيار اللحظات

  • تحديد عدد المقاطع (5 افتراضيًا) والطول المسموح لكل مقطع (من 30 لـ 75 ثانية؛ و 52 ثانية هي القيمة الافتراضية المتحفّظة للمقاطع القصيرة).
  • المقطع اللي طوله بره المدى المطلوب بيفضل موجود وعليه علامة، وعمره ما بيتشال في صمت.
  • أي provider، والـ thinking شغال أو مقفول. وممكن الشغل يقف قبل الـ render عشان تتعدّل البدايات والنهايات والعناوين والترتيب؛ ونقط القص المتعدّلة بترجع تتظبط على الكلمات.

الـ captions

  • أربعة أشكال للـ captions (box و pill و marker و youtube)، وفي كل شكل بيتم تمييز الكلمة وقت نطقها، مع دعم اتجاه العربي من اليمين للشمال، ودعم الإنجليزي كمان.
  • بديل (fallback) بيعرض سطر واحد في المرة، أو من غير captions خالص؛ والخط وعدد الكلمات في السطر ممكن يتظبطوا.
  • الـ captions بتظهر عند حوالي 70% من ارتفاع الكادر، محسوبة من فوق، فوق المكان اللي TikTok و Shorts بيحطوا فيه الأزرار بتاعتهم.

الفيديو

  • قص جزء 9:16، أو وضع الـ blur: الكادر كله فوق خلفية معمول لها blur، أو تتبّع أكبر وجه.
  • دقة 1080p أو 1440p أو 4K أو أي مقاس، أو دقة المصدر نفسها؛ و H.265 بالـ tag بتاع أجهزة Apple، أو H.264.
  • الـ encode preset، والجودة (CRF 20 افتراضيًا)، وإخراج 10-bit لما المصدر يكون 10-bit.

وهو شغال، وبعد ما يخلص

  • نسبة التقدّم لحظة بلحظة للتحميل والـ render، متقاسة من ffmpeg مش متخمّنة، ومعاها زرار إلغاء.
  • تحميل المقاطع واحد واحد أو كلهم مرة واحدة؛ وإعادة الـ render بإعدادات جديدة من غير تفريغ تاني.
  • فيه health check بيعرض حالة ffmpeg، والـ provider الشغال، وحالة إعداد بيانات الدخول ليه، وهل فيه GPU متاح للتفريغ ولا لأ، قبل ما الـ job يبدأ.
  • الـ provider والموديل والـ thinking ممكن يتغيروا من صفحة الإعدادات (Settings) من غير restart.

القرارات الهندسية

اخترنا إيه، وليه؟

  1. الموديل بيقترح اللحظة، والكود بيضبط مكان القص

    المرحلة 3 بتقرا التفريغ في أجزاء مدتها حوالي 12 ثانية، وبتقترح البداية والنهاية بصيغة MM:SS. المرحلة 4 بتضبط بداية المقطع ونهايته على توقيتات الكلمات اللي طلّعها Whisper. اختيار اللحظة جاي من الموديل، لكن مكان القص النهائي جاي من الصوت؛ عشان كده تغيير الـ provider ممكن يغيّر المقطع المختار، لكنه مش هيخلّيه يبدأ في نص كلمة.

  2. التحقق من الـ captions بالعربي على frames طالعة من الـ render

    سلوك في الـ bidi بتاع libass كان بيلخبط ترتيب الكلمات العربية كل ما الكلمة اللي عليها الـ highlight يتغير الـ style بتاعها، والاختبارات الأولية كلها عدّت. الحل إن كل كلمة بقى ليها caption cue خاص بيها. وفيه render check بيمشّي الـ highlight على طول السطر، ويعمل render لـ frame واحد عند كل موضع، ويتأكد إنه بيتحرك من اليمين للشمال؛ وأشكال الـ captions نفسها اتختارت من renders حقيقية لفيديو عربي.

  3. تعديل النص عمره ما يحرّك نقطة قص

    محرر التفريغ بيغيّر الكلمات بس، وأي حدود بتتعدّل باليد بترجع تتظبط على توقيتات الكلمات. عشان كده تصحيح إملائي بعد الـ render بيعيد مرحلة الـ render بس.

  4. كل إعداد افتراضي وراه رقم

    الـ crop هو الـ framing الافتراضي عشان بيحتفظ بحوالي تلات أضعاف الـ pixels بتاعة الـ subject مقارنة بالـ blur، وكان الـ render بيه أسرع 1.8× في الاختبار المتسجّل؛ والـ encode preset والـ codec وعوامل ضبط التفريغ، كل واحد فيهم وراه قياس، والاختيارات اللي اتجربت واترفضت مكتوبة هي كمان.

  5. اختبارات بتكشف العطل فعلًا

    كل render fixture بيعمل render لحالة ضابطة (control) مقصود إنها تفشل، وبيتحقق من فشلها عشان نتأكد إن الفحص قادر يكشف المشكلة، والـ load test بتاع الـ API بيعمل assert مش مجرد report. والـ load test ده كشف إن الـ health check كان بيشغّل process لـ ffmpeg مع كل request، ودي حاجة مفيش اختبار sequential كان يقدر يشوفها.

المفاضلات

كسبنا إيه، وإيه اللي اتنازلنا عنه؟

الموديل بيختار، والكود بيقص

الضبط على الكلمات ممكن يزوّد كام ثانية في المقطع، فطلب 60 ثانية ممكن يطلّع 63 ثانية. عشان كده القيمة الافتراضية 52 ثانية، بهامش متحفّظ للمقاطع القصيرة؛ وهي اختيار في المنتج، مش حد من YouTube Shorts.

تتبّع أكبر وجه

لسه مفيش موديل بيحدد مين اللي بيتكلم (active speaker)، فلقطة فيها شخصين ممكن الكادر يتظبط فيها على اللي بيسمع، وثوابت التتبّع قيم مبدئية ما اتقاستش.

job queue جوه نفس الـ process، على SQLite

أمر واحد للتشغيل، بس الـ jobs بتضيع مع أي restart، والإلغاء ما بيتنفذش غير بين الخطوات.

مفيش authentication مدمج

الـ API محتاج طبقة حماية وتحكم في الدخول قبل ما يتاح من خارج الجهاز.

الـ GPU محجوز افتراضيًا في Docker

تفريغ سريع على جهاز فيه GPU؛ ومن غيره، ملف الـ compose محتاج تعديل بسيط، و large-v3 بيشتغل ببطء على الـ CPU.

النتايج

وصلنا لإيه؟

التحقق من الأول للآخر

فيديو عربي مدته 12 دقيقة، 4K ProRes، متصوّر جوه عربية ماشية، عدّى على المراحل الخمسة كلها مع provider حقيقي، وطلّع 8 مقاطع رأسية عليها captions بالعربي مدموجة في الفيديو — مرة عن طريق الـ CLI على GPU، وفي تجربة منفصلة عن طريق الـ HTTP API.

التفريغ العربي: من 23 غلطة لـ 3

على التسجيل ده، عدد أخطاء التفريغ اللي اتتبعناها نزل من 23 لـ 3 بقائمة مصطلحات فيها 30 مصطلح، و denoising، و fix-ups، زي ما هو متسجّل في ملاحظات الجودة في الـ repository.

مختبَر من غير GPU

الـ CI بيشغّل الـ suites بتاعة قاعدة البيانات والـ pipeline والـ provider والـ API على Python 3.10 و 3.12، و load test بـ 24 thread، واختبارات الـ frontend والـ type check، و render check حقيقي بـ ffmpeg، والـ Docker build.

مفتوح للمساهمين

رخصة Apache-2.0 مع DCO sign-off، ودليل للمساهمة مترتّب حسب القيمة مقابل المجهود. وأول مساهمة من بره، وهي إعادة تصميم كل الشاشات مع واجهة عربية، اتعملها merge.

دوري

  • عملت Qatf وبتابع صيانته: الـ pipeline، وطبقة الـ provider، ونظام الـ jobs، والـ HTTP API، والـ CLI، وأول نسخة من واجهة الويب، والتوثيق — 117 commit من أصل 119.
  • عملت القياسات اللي ورا الإعدادات الافتراضية: عوامل ضبط التفريغ على العربي، وأوضاع الـ framing، والـ codecs والـ encode presets، والـ API تحت ضغط متزامن (concurrent load).
  • راجعت وعملت merge لتصميم الواجهة الحالي، بما فيه الواجهة العربية والـ layout من اليمين للشمال؛ والتصميم ده مساهمة من ‎@Dabash99 (PR #4).

حدود الشغل

  • نموذج أولي شغال، مش production: الـ API مفيهوش أي authentication، والـ jobs بتضيع مع أي restart، والإلغاء ما يقدرش يقطع call لـ ffmpeg أو Whisper شغال بالفعل.
  • مفيش غير OpenRouter اللي اتشغّل على الـ endpoint الحقيقي بتاعه؛ الـ providers التانيين مختبَرين على الـ requests اللي Qatf بيبعتها، مش على ردود حقيقية.
  • دقة توقيت الكلمات في Whisper على العربي ما اتقاستش، مع إن نقط القص والـ highlight بتاع الـ captions الاتنين معتمدين عليها.
  • فحص الـ captions من اليمين للشمال بيشتغل في البيئة اللي الخط بيكون متاح فيها؛ وعلى الـ CI runner الحالي الخط مش متاح، فالـ CI بيعمل skip للفحص ده بدل ما يفشل.
  • لسه مفيش loudness normalisation، ولا قص لفترات السكوت، ولا اكتشاف لتغيير المشهد؛ ومفيش أي ادعاء بأرقام استخدام أو انتشار.
PythonFastAPIfaster-whisperffmpeglibassSQLiteReactTypeScriptDocker

دراسة الحالة اللي بعدها

Check-In (Hayyak)

الـ backend بتاع تطبيق لضيوف الفنادق (كان اسمه Hayyak في الأول، وبعدين بقى Check-In): الضيف بيربط حجزه اللي على OPERA بالتطبيق، ويضيف ضيوف مرافقين، ويسجّل وصوله (check-in)، ويمدّد إقامته، ويسجّل مغادرته (check-out) مع الدفع من جوه التطبيق، والنظام بيدعم أكتر من فندق. قُدت شغل الـ backend، وكنت المساهم الأساسي فيه وصاحب الـ repository.

اقرأها