# עריכה

> עריכת פודקאסטים וסרטונים ארוכים מ-TypeScript: הידוק פרק בקריאה אחת, זיהוי שקט, סנכרון הקלטות, תכנון חיתוכים ורינדור של רשימת עריכה.

Source: https://nodaro.ai/he/docs/developers/sdk/editing

**`client.edit`** מרכז את כלי העריכה לפודקאסטים ולסרטונים ארוכים. הוא מהדק פרק שלם בקריאה אחת, מוצא שקט ומודד את הפרשי הזמן בין כמה הקלטות. הוא גם מתכנן חיתוך מתוך תמלול ומרנדר **רשימת עריכה**, או EDL, לקובץ וידאו או אודיו מוגמר. רוב המתודות מתחילות משימה ומחזירות את המזהה שלה, ואת הסטטוס של המשימה בודקים מדי כמה שניות עם [`client.jobs.getStatus()`](https://nodaro.ai/docs/developers/sdk/jobs-and-executions). המתודה `estimateTightenEpisode()` נותנת אומדן מחיר להרצה, והמתודה `remapTranscript()` רצה מקומית, בלי בקשה לשרת.

## מתודות
| מתודה | נקודת קצה | מה היא עושה |
| --- | --- | --- |
| [`edit.tightenEpisode(input, options?)`](#edittighten-episode) | `POST /v1/tighten-episode` | הידוק של הקלטה אחת לפרק מוגמר, בקריאה אחת |
| [`edit.estimateTightenEpisode(input)`](#edittighten-episode) | `POST /v1/tighten-episode/estimate` | אומדן מחיר להרצה של הידוק פרק. לא נוצר כלום ולא נגבים קרדיטים |
| [`edit.continueUseCase(input, options?)`](#editcontinueusecaseinput-options) | `POST /v1/<type>/continue`, היום `/v1/tighten-episode/continue` | הרצת המשך של הידוק פרק, כמשימה חדשה |
| [`edit.silenceDetect(input)`](#editsilencedetectinput) | `POST /v1/silence-detect` | איתור הטווחים השקטים במקור אודיו או וידאו |
| [`edit.audioSync(input)`](#editaudiosyncinput) | `POST /v1/audio-sync` | מדידת ההיסט בזמן בין 2 ל-6 הקלטות |
| [`edit.editPlan(input)`](#editeditplaninput) | `POST /v1/edit-plan` | תכנון של חיתוך מהודק, קליפים קצרים, פרקים או טריילר מתוך תמלול |
| [`edit.applyEdl(input)`](#editapplyedlinput) | `POST /v1/apply-edl` | רינדור EDL לקובץ וידאו או אודיו |
| [`edit.remapTranscript(edl, transcript)`](#editremaptranscriptedl-transcript) | אין, מקומית | העברת התזמונים של תמלול לציר הזמן הערוך |

## עריכת פודקאסט מקוד
לפרק שלם, המתודה [`tightenEpisode()`](#edittighten-episode) מריצה את כל השלבים שלמטה בקריאה אחת. כשצריך שליטה בכל שלב, הריצו את השלבים אחד אחד.

### תמלול ההקלטה
הריצו את [`client.audio.transcribe()`](https://nodaro.ai/docs/developers/sdk/voices-and-audio#audiotranscribeinput) עם מנוע שמחזיר תזמון לכל מילה. השדה `output_data.json` של המשימה הוא התמלול שהתוכנית צריכה.

### איתור השקט
הריצו את `silenceDetect()` על ההקלטה הראשית. ב-`output_data.json` של המשימה נמצאים הטווחים השקטים.

### תכנון החיתוך
הריצו את `editPlan()` במצב `tighten` עם התמלול, המקורות והשקט. קבלו את התוכנית עם `unwrapEditPlanOutput()`.

### רינדור התוכנית
העבירו את התוכנית ל-`applyEdl()`. בפלט של המשימה שהסתיימה נמצאים הסרטון או האודיו הערוכים.

Workflow: אותה שרשרת עריכה, כרכיבים: תמלול ואיתור השקט, תכנון של חיתוך מהודק יותר, ואז רינדור.

- העלאת וידאו → תמלול
- העלאת וידאו → זיהוי שקט
- תמלול → תוכנית עריכה
- זיהוי שקט → תוכנית עריכה
- תוכנית עריכה → החלת רשימת עריכה

```ts

async function outputOf(jobId: string): Promise<any> {
for (;;) {
const { data } = await client.jobs.getStatus(jobId)
if (data.status === "completed") return data.output_data
if (data.status === "failed" || data.status === "cancelled") throw new Error(data.error_message ?? data.status)
await new Promise((resolve) => setTimeout(resolve, 3_000))
}
}

// 1. Transcribe with word timings
const tr = await client.audio.transcribe({ audioUrl: masterUrl, provider: "elevenlabs-stt" })
const transcript = (await outputOf(tr.jobId)).json

// 2. Find the silence
const sd = await client.edit.silenceDetect({ audioUrl: masterUrl, thresholdDb: -35 })
const silence = (await outputOf(sd.jobId)).json

// 3. Plan a tighter cut
const plan = await client.edit.editPlan({
mode: "tighten",
planTier: "standard",
transcript,
sources: [{ id: "ep", url: masterUrl, kind: "video", role: "master-audio" }],
silence,
})
const edl = unwrapEditPlanOutput(await outputOf(plan.jobId))

// 4. Render the plan
const render = await client.edit.applyEdl({ edl, output: "video", quality: "final" })
const { videoUrl } = await outputOf(render.jobId)
```

אותם שלבים קיימים גם כרכיבים: [תמלול](https://nodaro.ai/docs/nodes/audio/transcribe), [זיהוי שקט](https://nodaro.ai/docs/nodes/audio/silence-detect), [תוכנית עריכה](https://nodaro.ai/docs/nodes/video/edit-plan) ו[החלת רשימת עריכה](https://nodaro.ai/docs/nodes/video/apply-edl).

## client.edit
### edit.tightenEpisode(input, options?)‎ ו-estimateTightenEpisode(input)‎
מהדקת הקלטה של פודקאסט בקריאה אחת, כמו רכיב [הידוק פרק](https://nodaro.ai/docs/nodes/video/tighten-episode). ההרצה מתמללת את ההקלטה, מתכננת את החיתוך המהודק ומרנדרת אותו. לפי ההגדרות, היא גם מוסיפה לפרק כתוביות, ממסגרת אותו ומשלבת מתחתיו מוזיקת רקע. רק השדה `mediaUrl` נדרש, ושדה שלא נשלח מקבל את ברירת המחדל של הרכיב.

```ts
tightenEpisode(input: TightenEpisodeInput, options?: { idempotencyKey?: string }): Promise<TightenEpisodeJobResult>
estimateTightenEpisode(input: TightenEpisodeInput): Promise<{ data: TightenEpisodeEstimate }>
```

<TypeTable
type={{
mediaUrl: { type: 'string', required: true, description: "ההקלטה: כתובת של וידאו או אודיו, עד 3 שעות." },
musicUrl: { type: 'string', description: "מוזיקת רקע, שיורדת בעוצמה מתחת לקול ומסתיימת עם הפרק." },
output: { type: '"video" | "audio"', description: "מה לרנדר. הקלטת אודיו תמיד יוצרת פרק אודיו." },
framing: { type: '{ aspect, layout } | null', description: "מסגור של פרק וידאו עם רכיב תצוגת דוברים. הערך של aspect הוא 16:9, 9:16, 1:1 או 4:5. הערך של layout הוא auto, single, side-by-side, stacked, grid או pip. null, או שדה חסר, פירושו בלי מסגור." },
framingSource: { type: '"auto" | "static" | "tracking"', description: "כשיש מסגור: איך הוא עוקב אחרי הדוברים. הערך \"static\", או שדה חסר, מחלק את הפריים באופן שווה. הערך \"tracking\" עוקב אחרי פנים בעזרת רכיב מסגרות דוברים, שמשלמים עליו לכל 1,000 פריימים דגומים, או חלק מהם. כשרכיב מסגרות דוברים לא זמין לכם, הערך הזה נדחה עם 403 node_not_available. הערך \"auto\" עוקב אחרי פנים כשרכיב מסגרות דוברים זמין לכם, ואחרת מחלק את הפריים באופן שווה." },
captions: { type: 'boolean', default: 'true', description: "כתוביות בפרק וידאו." },
captionStyle: { type: 'CaptionStyle', description: "המראה של הכתוביות." },
reviewBeforeRender: { type: 'boolean', description: "עצירה בתצוגה מקדימה מהירה, כדי שאדם יבדוק אותה. אחר כך מרנדרים את הפרק באיכות מלאה עם continueUseCase במצב render-final." },
planTier: { type: '"economy" | "standard" | "premium"', description: "רמת המודל של שלב התכנון." },
instructions: { type: 'string', description: "הוראות עריכה לשלב התכנון, עד 4,000 תווים." },
styleGuide: { type: 'string', description: "מדריך סגנון לשלב התכנון, עד 4,000 תווים." },
musicDuckAmount: { type: 'number', default: '75', description: "כמה המוזיקה יורדת בעוצמה מתחת לקול, מ-0 עד 100." },
}}
/>

```ts
const quote = await client.edit.estimateTightenEpisode({ mediaUrl: episodeUrl })
console.log(quote.data.totalCredits)

const { jobId } = await client.edit.tightenEpisode(
{ mediaUrl: episodeUrl, framing: { aspect: "9:16", layout: "auto" } },
{ idempotencyKey: crypto.randomUUID() },
)
```

המתודה `tightenEpisode()` מחזירה `{ jobId, executionId?, deduped? }`. אם שולחים שוב את אותה בקשה עם אותו `idempotencyKey`, התגובה היא ההרצה שכבר התחילה, עם `deduped: true`. בדקו את הסטטוס של `jobId` מדי כמה שניות. ב-`output_data` של המשימה שהסתיימה נמצא הפרק, ב-`videoUrl`, או ב-`audioUrl` בפרק אודיו. שם נמצאים גם ה-EDL של התוכנית, ב-`json`, והשדה `quality`, שערכו `"proxy"` לתצוגה מקדימה לבדיקה ו-`"final"` בכל מקרה אחר.

המתודה `estimateTightenEpisode()` מחזירה `{ totalCredits, stepsCredits, feeCredits, durationSec, estimate? }`: השלבים של ההרצה, כל אחד בתעריף שלו, ועוד עמלה קבועה לכל הרצה. הערך `estimate: true` פירושו שהמחיר של אחד השלבים הוא אומדן של קריאה ל-LLM שמחויבת לפי שימוש, ולכן הציגו את הסכום הכולל כ-`≈N`. האומדן נוטה כלפי מעלה, כי הוא מתמחר את הרינדור לפי האורך המלא של ההקלטה, לפני שהעריכה ידועה. החיוב נקבע לפי הדקות שרונדרו בפועל. השדה `durationSec` הוא `null` כשאי אפשר היה לקרוא את האורך של ההקלטה. במהדורה בלי קרדיטים, כל סכום של קרדיטים הוא 0.

שתי המתודות דוחות בקשה לפני שמשהו נוצר ולפני שנגבים קרדיטים. בשגיאת 400 הקוד הוא `validation_error`, `media_unreadable`, `media_has_no_audio`, `media_too_long` או `music_has_no_audio`. בשגיאת 403 הקוד הוא `node_not_available`. בשגיאת 503 הקוד הוא `nodaro_connection_required` או `use_case_unavailable`. המתודה `tightenEpisode()` זורקת גם `InsufficientCreditsError` כשהיתרה לא מכסה את האומדן. בהתקנה באירוח עצמי, השלבים שרצים ב-Nodaro Cloud צריכים [חיבור ל-Nodaro Cloud](https://nodaro.ai/docs/self-hosting/cloud-connect).

### edit.continueUseCase(input, options?)‎
ממשיכה הרצה של הידוק פרק כמשימה חדשה, ומחזירה `{ jobId, executionId?, deduped? }`, כמו `tightenEpisode()`.

```ts
continueUseCase(input: UseCaseContinueInput, options?: { idempotencyKey?: string }): Promise<UseCaseJobResult>
```

<TypeTable
type={{
jobId: { type: 'string', required: true, description: "ההרצה שממשיכים: המשימה ש-tightenEpisode, או הרצת המשך קודמת, החזירה." },
mode: { type: '"from-failed" | "render-final"', required: true, description: "המצב from-failed שומר את השלבים שהסתיימו ומריץ את השאר. העמלה משוריינת שוב, כי על העמלה של ההרצה שנכשלה קיבלתם החזר. המצב render-final מרנדר את הפרק באיכות מלאה אחרי תצוגה מקדימה לבדיקה, עם ההגדרות של התצוגה המקדימה ובלי עמלה." },
editedEdl: { type: 'unknown', description: "רק ב-render-final: העריכה שהבודק עשה בתוכנית, כ-EDL." },
nodeType: { type: 'UseCaseNodeType', description: "סוג הרכיב של ההרצה, \"tighten-episode\". הרשימה מיוצאת כ-USE_CASE_NODE_TYPES. הרצת ההמשך נשלחת אל POST /v1/<nodeType>/continue. הערך אף פעם לא נשלח בגוף הבקשה. עם ערך שלא נמצא ברשימה, המתודה זורקת NodaroError (invalid_node_type, 400) לפני כל בקשה." },
}}
/>

**לאיזה נתיב.** עם `nodeType`, הרצת ההמשך נשלחת ישר לנתיב של הסוג הזה. בלי `nodeType`, המתודה `continueUseCase` טוענת קודם את המשימה (`GET /v1/jobs/:id`) כדי לברר את הסוג שההרצה רשמה, ואז ממשיכה בנתיב של הסוג הזה. הקריאה הזו דורשת את ההרשאה `jobs:read` באסימון של אפליקציה מחוברת. באסימון בלי ההרשאה, המתודה פונה לנתיב של הידוק פרק, כך שאסימון שיכול היה להמשיך הרצה קודם יכול לעשות זאת גם עכשיו. העבירו `nodeType` כדי לדלג על הקריאה. ל-`TightenEpisodeJobResult` יש אותו מבנה כמו ל-`UseCaseJobResult`.

```ts
const preview = await client.edit.tightenEpisode({ mediaUrl: episodeUrl, reviewBeforeRender: true })
// ...after a person reviews the preview:
const final = await client.edit.continueUseCase({ jobId: preview.jobId, mode: "render-final" })
```

רינדור סופי שהסתיים לא חוסם רינדור סופי נוסף של אותה תצוגה מקדימה עם עריכה ששונתה. השלבים הסופיים רצים אז שוב, ומשלמים עליהם שוב, עדיין בלי העמלה. בקשה עם בדיוק אותה עריכה נדחית. כל דחייה נזרקת כשגיאה עוד לפני שנוצרת הרצה. כשהדחייה מציינת משימה אחרת, המתודה זורקת `ContinuationRefusedError`, שהיא `NodaroError` עם סטטוס 409, וה-`jobId` שלה הוא המשימה ההיא. לשגיאה הזו יש שלושה קודים. הקוד `already_running` פירושו שהרצת המשך כבר מתבצעת או הסתיימה: בדקו את המצב שלה. הקוד `continuation_superseded` פירושו שגם הרצת המשך מאוחרת יותר נכשלה: המשיכו במקום זאת את ההרצה ההיא. הקוד `render_final_already_rendered` פירושו שהתוצאה של המשימה ההיא היא זו שצריך להשתמש בה. דחיות אחרות כוללות את `not_found`, `continuation_not_found`, `continuation_not_failed` ו-`continuation_not_completed`. הקוד `continuation_settling` פירושו שצריך לנסות שוב בעוד רגע, והקוד `render_final_not_preview` פירושו שהמשימה היא לא תצוגה מקדימה לבדיקה. כשמי שמשלם לא יכול לכסות את ההרצה, המתודה זורקת `InsufficientCreditsError`.

### edit.silenceDetect(input)‎
מוצאת את הטווחים השקטים במקור אודיו או וידאו. המתודה רצה בשרת, בלי מודל AI.

```ts
silenceDetect(input: SilenceDetectInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
audioUrl: { type: 'string', required: true, description: "מקור אודיו או וידאו." },
thresholdDb: { type: 'number', default: '-35', description: "עוצמת הקול שמתחתיה אודיו נחשב לשקט, ב-dBFS, 0 או פחות." },
minSilenceMs: { type: 'number', default: '700', description: "השקט הקצר ביותר שהמתודה מדווחת עליו, באלפיות שנייה." },
padMs: { type: 'number', default: '120', description: "מרווח שנשמר סביב הדיבור, באלפיות שנייה. המרווח מקצר כל טווח שקט." },
workflowId: { type: 'string', description: "תהליך שההרצה הזו תופיע תחתיו, בהיסטוריית ההרצות שלו." },
}}
/>

```ts
const { jobId } = await client.edit.silenceDetect({ audioUrl: masterUrl, minSilenceMs: 900 })
```

השדה `output_data.json` של המשימה שהסתיימה הוא אובייקט `SilenceRanges`: `{ version, ranges: [{ startMs, endMs }], durationMs }`. העבירו את האובייקט כולו ל-`editPlan()` בפרמטר `silence`.

### edit.audioSync(input)‎
מודדת כמה רחוקים זה מזה השעונים של 2–6 הקלטות של אותה שיחה, לפי האודיו שלהן, כמו רכיב [סנכרון אודיו](https://nodaro.ai/docs/nodes/audio/audio-sync). המתודה רצה בשרת, בלי מודל AI. היא עולה 11 קרדיטים לכל מקור אחרי הראשון: 11 קרדיטים ל-2 מקורות, 33 ל-4 ו-55 ל-6.

```ts
audioSync(input: AudioSyncInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
sources: { type: 'Array<{ id: string; url: string }>', required: true, description: "2–6 הקלטות. כל id, באורך 1–200 תווים וייחודי, חוזר כ-sourceId של ההיסט שלו. השתמשו במזהים שה-EDL שלכם משתמש בהם לאותן הקלטות." },
reference: { type: 'string', description: "המזהה של המקור שכל היסט נמדד מולו. ההיסט שלו עצמו הוא 0. ברירת המחדל היא המקור הראשון." },
workflowId: { type: 'string', description: "תהליך שההרצה הזו תופיע תחתיו." },
}}
/>

```ts
const { jobId } = await client.edit.audioSync({
sources: [
{ id: "mic", url: micUrl },
{ id: "camA", url: camAUrl },
],
reference: "mic",
})
```

השדה `output_data.json` של המשימה שהסתיימה הוא `AudioSyncResult`:

```ts
{
version: number
reference: string // the source every offset is measured against
offsets: Array<{
sourceId: string
offsetMs: number              // referenceMs = sourceMs + offsetMs
confidence: number            // 0 to 1; below 0.5 a note asks you to check by ear
driftMsPerHour: number | null // measured, never corrected; null when the overlap was too short
}>
notes: string[] // low confidence, drift above 33 ms over the overlap, no shared sound
}
```

כשההקלטה הראשית היא ה-`reference`, כל `offsetMs` הוא בדיוק ה-`offsetMs` של המקור הזה ב-EDL שלכם. בקשה פגומה נדחית עם `NodaroError` שה-`code` שלה הוא `validation_error`, לפני שקרדיטים משוריינים. זה כולל בקשה עם פחות מ-2 מקורות או יותר מ-6, עם מזהה שחוזר פעמיים או עם `reference` שהוא לא אחד מהמזהים.

### edit.editPlan(input)‎
מתכננת עריכה מתוך תמלול מתוזמן, כמו רכיב [תוכנית עריכה](https://nodaro.ai/docs/nodes/video/edit-plan). המתודה כותבת אחד מארבעה סוגים של תוכניות: חיתוך מהודק יותר של ההקלטה כולה, סדרה של קליפים קצרים, פרקים או טריילר.

```ts
editPlan(input: EditPlanInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
mode: { type: '"tighten" | "clips" | "chapters" | "trailer"', required: true, description: "המצב tighten מסיר הפסקות ומילות מילוי, המצב clips חותך קליפים קצרים, המצב chapters מחלק את ההקלטה לפרקים והמצב trailer חותך טריילר." },
planTier: { type: '"economy" | "standard" | "premium"', required: true, description: "רמת המודל. הרמה קובעת את האיכות ואת המחיר." },
transcript: { type: 'Transcript', required: true, description: "התמלול המתוזמן: ה-output_data.json של משימת תמלול." },
sources: { type: 'EditPlanSource[]', required: true, description: "1–6 מקורות. כל מקור הוא ‎{ id, url, kind, role?, speakers?, offsetMs? }‎, והערך של kind הוא video או audio." },
silence: { type: 'SilenceRanges', description: "השדה output_data.json של משימת silenceDetect. העבירו את האובייקט כולו: אובייקט בלי ranges לא נלקח בחשבון." },
instructions: { type: 'string', description: "הוראות עריכה בטקסט חופשי." },
styleGuide: { type: 'string', description: "מדריך סגנון שהתוכנית צריכה לפעול לפיו." },
count: { type: 'number', description: "במצב clips: כמה קליפים לחתוך." },
targetDurationSec: { type: 'number', description: "במצב clips: אורך היעד של כל קליפ." },
targetAspect: { type: '"16:9" | "9:16" | "1:1" | "4:5"', description: "יחס הגובה־רוחב של הקליפים." },
platform: { type: 'string', description: "הפלטפורמה שהקליפים מיועדים לה." },
workflowId: { type: 'string', description: "תהליך שההרצה הזו תופיע תחתיו." },
}}
/>

```ts

const { jobId } = await client.edit.editPlan({
mode: "clips",
planTier: "standard",
transcript,
sources: [{ id: "ep", url: masterUrl, kind: "video", role: "master-audio" }],
silence,
count: 5,
targetAspect: "9:16",
})
const { data } = await client.jobs.getStatus(jobId) // poll until completed
const clips = unwrapEditPlanOutput(data.output_data) // one EDL per clip
```

קבלו את הפלט של המשימה שהסתיימה עם **`unwrapEditPlanOutput()`**. הפונקציה מחזירה `Edl` במצבים `tighten` ו-`trailer`, מערך של `Edl` במצב `clips`, ו-`ChapterSet` במצב `chapters`.

בהתקנה באירוח עצמי, המתודה הזו צריכה [חיבור ל-Nodaro Cloud](https://nodaro.ai/docs/self-hosting/cloud-connect). בלי חיבור, היא נכשלת עם `503 nodaro_connection_required`.

### edit.applyEdl(input)‎
מרנדרת רשימת עריכה לקובץ וידאו או אודיו, כמו רכיב [החלת רשימת עריכה](https://nodaro.ai/docs/nodes/video/apply-edl).

```ts
applyEdl(input: ApplyEdlInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
edl: { type: 'Edl', required: true, description: "רשימת העריכה לרינדור. המדיה נטענת מה-url של כל אחד מהמקורות שלה." },
sources: { type: 'string[]', description: "כתובות מדיה חלופיות למקורות של ה-EDL, באותו סדר." },
transcript: { type: 'Transcript', description: "תמלול להעברה לציר הזמן הערוך. התוצאה חוזרת בפלט json של המשימה." },
output: { type: '"video" | "audio"', default: '"video"', description: "מה לרנדר." },
quality: { type: '"proxy" | "final"', default: '"final"', description: "הערך proxy מרנדר תצוגה מקדימה: לכל היותר 720p, עם אודיו מונו קל יותר בחיתוך של אודיו. התצוגה המקדימה מתומחרת לפי דקות הפלט, בתעריף הנמוך שלה, והיא תמיד פרטית. הערך final מרנדר באיכות המלאה." },
crossfadeMs: { type: 'number', default: '0', description: "מעבר צולב בכל נקודת חיתוך שאין לה מעבר משלה, באלפיות שנייה. 0 פירושו חיתוכים חדים." },
clipKey: { type: 'string', description: "הקליפ מהתוכנית שהרינדור הזה חותך: edlSpanKey(clip)‎ מ-‎@nodaro/shared. הערך חוזר ב-output_data.clipKey של המשימה." },
planBasis: { type: 'string', description: "ערך התוכנית שהרינדור הזה חותך: renderReadBasis(value)‎ מ-‎@nodaro/shared, באורך 16 ספרות הקסדצימליות באותיות קטנות. שלחו אותו רק כשה-EDL שאתם שולחים הוא הערך הזה עצמו. כל פורמט אחר נדחה עם 400. הערך חוזר ב-output_data.planBasis של המשימה." },
workflowId: { type: 'string', description: "תהליך שההרצה הזו תופיע תחתיו." },
}}
/>

```ts
const { jobId } = await client.edit.applyEdl({ edl, output: "video", quality: "proxy", crossfadeMs: 80 })
```

ב-`output_data` של המשימה שהסתיימה נמצא החיתוך: השדות `videoUrl` ו-`thumbnailUrl`, או `audioUrl`. כששלחתם תמלול, נמצא שם גם התמלול שהועבר, ב-`json`. שם נמצאים גם `quality` (`"proxy"` או `"final"`), וגם `clipKey` ו-`planBasis` כששלחתם אותם. רינדור `proxy` הוא תצוגה מקדימה, והוא אף פעם לא מופיע בגלריה הציבורית. ראו את הסעיף [תצוגה מקדימה ורינדור סופי](https://nodaro.ai/docs/nodes/video/apply-edl#preview-and-render-final) בדף החלת רשימת עריכה, ואת הסעיף [קרדיטים](https://nodaro.ai/docs/nodes/video/apply-edl#credits) באותו דף, עם המחיר של כל רמת איכות.

ה-EDL נבדק לפני שקרדיטים משוריינים. כשיש מקור לא מוכר, מקטע בלי תמונה בעריכת וידאו או **יותר מ-180 דקות של פלט** ברינדור אחד, הבקשה נכשלת עם `NodaroError` שה-`code` שלה הוא `invalid_edl`. האורך נמדד אחרי המעברים הצולבים. השדה `message` מציין את הבעיה, למשל שהעריכה מרנדרת 200 דקות וצריך לפצל אותה לחלקים של 180 דקות לכל היותר.

### edit.remapTranscript(edl, transcript)‎
מעבירה תמלול לציר הזמן של עריכה, **מקומית, בלי בקשה לשרת**. המתודה משמיטה את המילים שבטווחים שנחתכו, מקצרת מילים שחוצות נקודת חיתוך ומזיזה כל תזמון למקום שלו בפלט הערוך. המתודה `applyEdl()` עושה את אותו הדבר בשרת כשמעבירים לה `transcript`.

```ts
remapTranscript(edl: Edl, transcript: Transcript): Transcript
```

<TypeTable
type={{
edl: { type: 'Edl', required: true, description: "העריכה." },
transcript: { type: 'Transcript', required: true, description: "התמלול של ההקלטה המקורית." },
}}
/>

```ts
const editedTranscript = client.edit.remapTranscript(edl, transcript)
// caption the edited video without another transcription
```

השתמשו במתודה הזו כדי להוסיף כתוביות לעריכה בלי רינדור ובלי תמלול נוסף. היא גם הבחירה המהירה יותר לתמלול גדול, כשצריך רק את התזמונים החדשים.

## שאלות נפוצות

### איך מסירים את ההפסקות מפודקאסט עם ה-SDK של Nodaro?

תמללו את ההקלטה, הריצו את client.edit.silenceDetect, ואז הריצו את client.edit.editPlan במצב tighten עם התמלול ועם טווחי השקט. רנדרו את התוכנית שמתקבלת עם client.edit.applyEdl.

### האם אפשר להדק פרק של פודקאסט בקריאה אחת?

כן. המתודה client.edit.tightenEpisode מקבלת כתובת של הקלטה אחת, וידאו או אודיו של עד 3 שעות, ומחזירה משימה. ההרצה מתמללת את ההקלטה, מתכננת את החיתוך ומרנדרת את הפרק המהודק, ויכולה גם להוסיף כתוביות, מוזיקה ומסגור.

### מה זה EDL?

EDL, או רשימת עריכה, מציינת את המקורות של עריכה ואת המקטעים שנשארים בה, לפי הסדר, עם המעברים ביניהם. המתודה client.edit.editPlan כותבת רשימה כזו, והמתודה client.edit.applyEdl מרנדרת אותה לקובץ וידאו או אודיו.

### מה האורך המרבי של רינדור EDL אחד?

רינדור EDL אחד מפיק לכל היותר 180 דקות של פלט. עריכה ארוכה יותר נדחית עם invalid_edl לפני שקרדיטים משוריינים. פצלו אותה לחלקים.

### כמה עולה המתודה client.edit.audioSync?

המתודה client.edit.audioSync עולה 11 קרדיטים לכל מקור אחרי הראשון: 11 קרדיטים ל-2 הקלטות, 33 ל-4 ו-55 ל-6.
