كيف تحرّر فيديو باستخدام Claude أو Codex أو أي وكيل ذكاء اصطناعي

أعطِ الوكيل تفريغًا نصيًا يحدد وقت بداية كل كلمة ونهايتها بدقة. يختار الوكيل الجمل التي يريد إبقاءها، ثم يقصّ سكربت قصير الفيديو وفق هذه الأوقات عبر ffmpeg. توفر KolWrite API هذا التفريغ لملفات الصوت والفيديو بـ 100 لغة، ويمكنك استخدام الكلمات نفسها لإنشاء ترجمات للنسخة النهائية.

احصل على مفتاح API بدء سريع اللغات المدعومة

آخر تحديث: · Markdown

ما يستطيعه الوكيل وما لا يستطيعه

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

إذا كان الوكيل قادرًا على تشغيل الأوامر مثل Claude Code وCodex CLI ووكيل Cursor، فبإمكانه استدعاء الواجهة وتشغيل ffmpeg بنفسه. أما نافذة الدردشة العادية التي لا تنفّذ الشيفرة، فيقتصر دورها على كتابة هذه السكربتات لتشغّلها أنت.

الموجّه الذي تلصقه في وكيلك

يلزم هذا الموجّه الوكيل بالتعامل مع أرقام المقاطع بدل كتابة الأوقات بنفسه، حتى لا يختلق توقيتات غير صحيحة. ويتضمن أيضًا رابطًا لتعليمات الواجهة المخصصة لقراءة الآلة.

Edit talk.mp4 down to the parts worth keeping, using the KolWrite API for the transcript.
Read https://console.kolwrite.com/agent-instructions.md first. The key is in KOLWRITE_API_KEY.
1. Extract the audio with ffmpeg and transcribe it with model "oracle", output_format "json", word_timestamps true.
2. Print the segments as numbered lines: S<number> [start-end] text.
3. Choose the segments to keep (remove false starts, filler, repeated takes and tangents) and write
   their numbers to keep.json as {"keep": [0, 2, 4]}. Do not invent timestamps; use segment numbers only.
4. Cut the video from those segments' word times with ffmpeg and write edited.mp4.
5. Make edited.srt for the edited video and attach it as a subtitle track.
Show me the list of removed segments before you cut anything.

لتثبيت الواجهة في كل جلسة، أضف القاعدة المشروحة في دليل الأداة إلى ملف AGENTS.md أو CLAUDE.md.

الخطوة 1: استخرج الصوت وفرّغه

يقبل الرفع الموقَّع ملفات يصل حجمها إلى 100 MiB، لذلك يُفضل استخراج الصوت أولًا إذا كان الفيديو طويلًا. فالساعة الواحدة من الصوت أحادي القناة بمعدل 48 كيلوبت/ث لا تتجاوز نحو 21 ميغابايت.

ffmpeg -i talk.mp4 -vn -ac 1 -ar 16000 -b:a 48k talk.mp3

ترفع هذه الدالة الصغيرة ملفًا محليًا، وتبدأ المهمة، ثم تنتظر النتيجة:

kolwrite.py

# kolwrite.py — three small functions around the KolWrite REST API
import os, time, requests

API = "https://app.kolwrite.com"
HEADERS = {"x-api-key": os.environ["KOLWRITE_API_KEY"]}


def upload(path):
    """Upload a local file (up to 100 MiB) and return a URL the API can read."""
    name = os.path.basename(path)
    form = requests.post(f"{API}/upload", headers=HEADERS, json={"filename": name}, timeout=30)
    form.raise_for_status()
    form = form.json()
    with open(path, "rb") as media:
        # No x-api-key on this request: it goes to the storage address.
        sent = requests.post(form["uploadUrl"], data=form["uploadFields"],
                             files={"file": (name, media)}, timeout=600)
    sent.raise_for_status()
    return form["fileUrl"]


def transcribe(audio_url, timeout_s=3600, **options):
    """Start a job, wait for it, return the result: a dict for json, a string for srt and txt."""
    body = {"audio_url": audio_url, "model": "oracle", **options}
    job = requests.post(f"{API}/transcribe", headers=HEADERS, json=body, timeout=30)
    job.raise_for_status()
    job_id = job.json()["jobId"]
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        time.sleep(5)
        reply = requests.get(f"{API}/transcribe/{job_id}", headers=HEADERS, timeout=30)
        reply.raise_for_status()
        reply = reply.json()
        if reply["status"] == "COMPLETED":
            return reply["result"]
        if reply["status"] == "FAILED":
            raise RuntimeError(reply.get("error", "transcription failed"))
    raise TimeoutError(f"job {job_id} did not finish in {timeout_s} seconds")

يطلب السكربت edit.py أدناه النتيجة بصيغة JSON مع توقيت الكلمات (وهما الإعدادان الافتراضيان، وكتبناهما هنا للتوضيح). تعود النتيجة في مصفوفة segments[]، ويضم كل مقطع الحقول start وend وtext وwords[]، إضافة إلى speaker عند طلبه:

مثال مختصر للنتيجة:

{
  "jobId": "34c63d45-4cb5-473b-bc60-25c175e69c57",
  "status": "COMPLETED",
  "result": {
    "language": "en",
    "segments": [
      {
        "start": 1.0,
        "end": 3.75,
        "text": "Hello everyone and welcome to the show.",
        "speaker": "Speaker 1",
        "words": [
          { "start": 1.0, "end": 1.35, "word": "Hello" },
          { "start": 1.4, "end": 1.75, "word": "everyone" }
        ]
      }
    ]
  }
}

الخطوة 2: دع الوكيل يختار ما يُبقيه

اطبع التفريغ في أسطر مرقّمة ليقرأه الوكيل بهذه الصيغة. هذا أوفر بكثير في استهلاك الرموز من إرسال ملف JSON الخام بكل كلماته وتوقيتاته.

view.py

# view.py — print the transcript the way an agent can read it
import json, sys

result = json.load(open(sys.argv[1]))
for number, segment in enumerate(result["segments"]):
    who = f'{segment["speaker"]}: ' if segment.get("speaker") else ""
    print(f'S{number} [{segment["start"]:.1f}-{segment["end"]:.1f}] {who}{segment["text"]}')

يردّ الوكيل بأرقام المقاطع فقط:

keep.json

{"keep": [0, 2, 4]}

اطلب منه أن يوضح المقاطع التي حذفها وسبب حذفها قبل تنفيذ القصّ. صحيح أن إعادة القصّ سهلة إذا وقع خطأ، لكن مراجعة قائمة نصية أسرع بكثير من مشاهدة الفيديو كاملًا بعد التعديل.

الخطوة 3: اقصّ بـ ffmpeg اعتمادًا على أوقات الكلمات

يأخذ السكربت أرقام المقاطع ويستخرج منها وقت بداية أول كلمة ونهاية آخر كلمة في كل مقطع. بعد ذلك يضيف هامشًا زمنيًا صغيرًا بأجزاء من الثانية، ويدمج المقاطع المتتالية، ثم يعيد ترميز الفيديو في عملية واحدة لضمان دقة القصّ على مستوى الإطار. وفي الوقت نفسه، يحسب التوقيت الجديد للكلمات ليتطابق مع الفيديو بعد قصّه تمهيدًا لإنشاء الترجمة.

cut.py

# cut.py — ranges from segment numbers, one ffmpeg pass, word times for the new timeline
import subprocess

PAD = 0.08  # seconds of room kept around every range


def ranges_from_ids(segments, keep_ids, pad=PAD):
    """Turn the segment numbers the agent chose into [start, end] ranges in seconds."""
    ranges = []
    for i in sorted(set(keep_ids)):
        segment = segments[i]
        words = segment.get("words") or []
        start = words[0]["start"] if words else segment["start"]
        end = words[-1]["end"] if words else segment["end"]
        start, end = max(0.0, start - pad), end + pad
        if ranges and start <= ranges[-1][1]:
            ranges[-1][1] = max(ranges[-1][1], end)
        else:
            ranges.append([start, end])
    return ranges


def cut(video, ranges, out):
    """Re-encode the kept ranges into one file (frame accurate)."""
    parts = []
    for i, (start, end) in enumerate(ranges):
        parts.append(f"[0:v]trim=start={start:.3f}:end={end:.3f},setpts=PTS-STARTPTS[v{i}]")
        parts.append(f"[0:a]atrim=start={start:.3f}:end={end:.3f},asetpts=PTS-STARTPTS[a{i}]")
    joined = "".join(f"[v{i}][a{i}]" for i in range(len(ranges)))
    parts.append(f"{joined}concat=n={len(ranges)}:v=1:a=1[v][a]")
    subprocess.run(["ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", video,
                    "-filter_complex", ";".join(parts), "-map", "[v]", "-map", "[a]", out], check=True)


def remap(words, ranges):
    """Move word times onto the edited timeline, so subtitles match the cut video."""
    moved, offset = [], 0.0
    for start, end in ranges:
        for word in words:
            if word["start"] >= start and word["end"] <= end:
                moved.append({**word, "start": word["start"] - start + offset,
                              "end": word["end"] - start + offset})
        offset += end - start
    return moved

edit.py

# edit.py — the whole pipeline: transcript in, edited video and subtitles out
import json
from kolwrite import upload, transcribe
from cut import ranges_from_ids, cut, remap
from srt_tools import cues_from_words, write_srt

result = transcribe(upload("talk.mp3"), output_format="json", word_timestamps=True)
with open("talk.json", "w", encoding="utf-8") as out:
    json.dump(result, out, ensure_ascii=False)
# ... the agent reads talk.json (python view.py talk.json) and writes keep.json ...

keep = json.load(open("keep.json"))["keep"]
ranges = ranges_from_ids(result["segments"], keep)
cut("talk.mp4", ranges, "edited.mp4")

words = [w for segment in result["segments"] for w in segment.get("words", [])]
write_srt(cues_from_words(remap(words, ranges)), "edited.srt")

نسخ البث مباشرة دون إعادة ترميز (-c copy) أسرع، لكنه لا يقصّ إلا عند الإطارات المفتاحية، ما قد يسبب فرقًا يصل إلى بضع ثوانٍ عن الموضع المطلوب. إذا كانت دقة موضع القصّ مهمة، فأعد الترميز.

الخطوة 4: ترجمات للفيديو المحرَّر

أنشأ السكربت edit.py ملف edited.srt بالاعتماد على أوقات الكلمات بعد تعديلها. ويمكنك الاطلاع على طريقة تقسيم الأسطر في دليل SRT.

srt_tools.py

# srt_tools.py — neat subtitle cues from word timings
def srt_time(seconds):
    ms = round(seconds * 1000)
    h, ms = divmod(ms, 3_600_000)
    m, ms = divmod(ms, 60_000)
    s, ms = divmod(ms, 1000)
    return f"{h:02}:{m:02}:{s:02},{ms:03}"


def cues_from_words(words, max_chars=42, max_seconds=6.0, max_gap=0.8):
    """Group timed words into subtitle cues: (start, end, text)."""
    cues, line = [], []
    for word in words:
        text = word["word"].strip()
        if not text:
            continue
        if line:
            length = sum(len(w["word"]) + 1 for w in line) + len(text)
            if (length > max_chars
                    or word["end"] - line[0]["start"] > max_seconds
                    or word["start"] - line[-1]["end"] > max_gap):
                cues.append(line)
                line = []
        line.append({"start": word["start"], "end": word["end"], "word": text})
    if line:
        cues.append(line)
    return [(c[0]["start"], c[-1]["end"], " ".join(w["word"] for w in c)) for c in cues]


def cues_from_result(result):
    """Cues for a whole JSON result; a segment without word times becomes one cue."""
    cues = []
    for segment in result["segments"]:
        if segment.get("words"):
            cues += cues_from_words(segment["words"])
        elif segment["text"].strip():
            cues.append((segment["start"], segment["end"], segment["text"].strip()))
    return cues


def write_srt(cues, path):
    with open(path, "w", encoding="utf-8") as out:
        for number, (start, end, text) in enumerate(cues, 1):
            out.write(f"{number}\n{srt_time(start)} --> {srt_time(end)}\n{text}\n\n")

ادمج الترجمات في الصورة مباشرة، أو أرفقها كمسار يستطيع المشغّل إيقافه:

ffmpeg -i edited.mp4 -vf "subtitles=edited.srt" -c:a copy burned.mp4
ffmpeg -i edited.mp4 -i edited.srt -c copy -c:s mov_text -metadata:s:s:0 language=eng soft.mp4

تعديلات أخرى من أوقات الكلمات نفسها

  • تقليص فترات الصمت: أبقِ مقاطع الكلام المتصل واحذف التوقفات التي تتجاوز حدًا زمنيًا معينًا (راجع دليل توقيت الكلمات).
  • استخراج مقطع قصير: ابحث عن عبارة محددة بين الكلمات واقصّ الفترة الزمنية الخاصة بها.
  • إزالة كلمات الحشو: دع الوكيل يحدد المقاطع المقتصرة على الحشو، أو ابحث عن هذه الكلمات مباشرة في قائمة الكلمات.
  • الفصول واللحظات البارزة: يختار الوكيل أرقام المقاطع المناسبة، ويحوّلها السكربت إلى أوقات زمنية (دليل البودكاست ويوتيوب).
  • حذف كلام متحدث معين: فعّل الخيار include_diarization واحتفظ بمقاطع المتحدث المطلوب فقط.

التكلفة

المهمةالنموذجالتكلفة
مقطع مدته 10 دقائق، تفريغ لمرة واحدةoracle0.24 دولار
ساعة فيديو، صوت واضحbase0.48 دولار
ساعة فيديو، جودة إنتاجيةoracle1.44 دولار
ساعة، صوت صعبoracle_max3.60 دولار

تُحسب التكلفة بالثانية دون حد أدنى. لذلك إذا أردت إعادة تفريغ مقطع واحد بنموذج أقوى، اقصص صوت هذا المقطع أولًا ولن تدفع إلا تكلفة مدته. تفاصيل الأسعار في /llms/pricing.md.

قيود يجب الانتباه إليها

  • توقيت الكلمات دقيق بما يكفي للقصّ بين الكلمات، لكنه قد ينحرف قليلًا عند بداية الكلمة أو نهايتها، ولهذا يضيف cut.py هامشًا صغيرًا لتفادي قطع الحروف. استمع دائمًا إلى مواضع القصّ قبل نشر الفيديو.
  • لا يخلو التفريغ الآلي من الأخطاء، لذا راجع الأسماء والأرقام قبل النشر. ولا تعلن KolWrite عن نسب دقة محددة لكل لغة في هذه الواجهة.
  • احتفظ بملف الفيديو الأصلي، لأن عملية القصّ تعيد ترميز الفيديو، وقد تحتاج للعودة إلى الأصل لتعديل القصّ لاحقًا.
  • إذا كثرت المقاطع المقتطعة جدًا، سيصبح أمر المرشّح في ffmpeg أطول من اللازم. عندما تتجاوز بضع مئات من المقاطع، نفّذ القصّ على دفعات.

أسئلة وأجوبة

هل يستطيع Claude تحرير فيديو؟

نعم، عندما يعمل في بيئة تتيح له تشغيل الأوامر. عندئذٍ يستدعي KolWrite API للحصول على التفريغ مع توقيت الكلمات، ويختار الجمل المطلوبة، ثم يشغّل ffmpeg لقصّ الفيديو. ينطبق هذا على Claude Code وCodex CLI ووكلاء Cursor، بينما تكتفي نافذة الدردشة العادية بكتابة السكربتات لك.

كيف أقصّ فيديو اعتمادًا على تفريغه النصي؟

فرّغ الملف مع تفعيل توقيت الكلمات، وحدد المقاطع التي تريد الإبقاء عليها، ثم خذ وقت بداية أول كلمة ونهاية آخر كلمة في كل مقطع، واقصّ هذه الفترات باستخدام مرشّحي trim وconcat في ffmpeg كما يوضح السكربت cut.py أعلاه.

هل أحتاج إلى إضافة أو SDK؟

لا. الواجهة HTTPS وJSON؛ ويستدعيها الوكيل بـ curl أو Python. لا توجد وقت الكتابة حزمة SDK ولا خادم MCP منشوران.

هل يعمل هذا مع العربية والعبرية ولغات أخرى؟

نعم في ما يخص التفريغ، إذ تدعم الواجهة 100 لغة بالتعرّف التلقائي أو بتمرير رمز اللغة في language. أما توقيت الكلمات فيعتمد على النماذج الموضحة في دليل توقيت الكلمات. يُفضل أن تجرب أولًا على عينة من تسجيلاتك، ويمكنك مراجعة صفحة اللغات.

كيف أمنع الوكيل من اختلاق الأوقات؟

لا تطلب منه كتابة أي أوقات زمنية. أعطه المقاطع مرقّمة واطلب منه أرقام المقاطع فقط، ثم دع الشيفرة تحسب الأوقات الدقيقة من توقيت الكلمات.