video-editing 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +83 -0
- package/dist/assets/DejaVuSansMono.ttf +0 -0
- package/dist/chunk-63HIQBDT.js +4321 -0
- package/dist/chunk-CU3NBKB4.js +307 -0
- package/dist/cli.js +1594 -0
- package/dist/index.d.ts +3569 -0
- package/dist/index.js +300 -0
- package/dist/job-OFCSLPBV.js +25 -0
- package/dist/templates/author-edl.prompt.md +33 -0
- package/dist/templates/fix.prompt.md +23 -0
- package/dist/templates/review.prompt.md +30 -0
- package/package.json +69 -0
- package/skills/video-editing/SKILL.md +254 -0
- package/skills/video-editing/references/adversarial-review.md +44 -0
- package/skills/video-editing/references/cli-reference.md +181 -0
- package/skills/video-editing/references/editing-brief.md +44 -0
- package/skills/video-editing/references/hard-rules.md +38 -0
- package/skills/video-editing/references/scribe-collapse.md +37 -0
- package/skills/video-editing/references/timeline-v2-vocabulary.md +162 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CAPABILITIES,
|
|
4
|
+
COMPOSE_AUDIO_SRC,
|
|
5
|
+
COMPOSE_ENTRY,
|
|
6
|
+
COMPOSE_HEIGHT,
|
|
7
|
+
COMPOSE_VIDEO_SRC,
|
|
8
|
+
COMPOSE_WIDTH,
|
|
9
|
+
DEFAULT_DESILENCE,
|
|
10
|
+
PRESETS,
|
|
11
|
+
agentizePrompt,
|
|
12
|
+
artifactPaths,
|
|
13
|
+
autoGradeForClip,
|
|
14
|
+
checkCapabilities,
|
|
15
|
+
compileTimeline,
|
|
16
|
+
composeCounts,
|
|
17
|
+
composeHtml,
|
|
18
|
+
computeEnvelope,
|
|
19
|
+
configDir,
|
|
20
|
+
configEnvPath,
|
|
21
|
+
cutBoundaries,
|
|
22
|
+
decideGradeFilter,
|
|
23
|
+
desilenceEdl,
|
|
24
|
+
detectSilences,
|
|
25
|
+
diagnosticsPath,
|
|
26
|
+
duplicatedPhrase,
|
|
27
|
+
duplicatedPhrases,
|
|
28
|
+
duplicatedSpans,
|
|
29
|
+
edlPath,
|
|
30
|
+
ensureTranscribed,
|
|
31
|
+
ensureVerticalMaster,
|
|
32
|
+
findPhrase,
|
|
33
|
+
fuzzySuggestions,
|
|
34
|
+
gatherCompileCtx,
|
|
35
|
+
generateQcImages,
|
|
36
|
+
getPreset,
|
|
37
|
+
learnedEditingGuardrails,
|
|
38
|
+
loadApiKey,
|
|
39
|
+
loadTimeline,
|
|
40
|
+
loadWords,
|
|
41
|
+
loadWordsBySource,
|
|
42
|
+
normalizePhrase,
|
|
43
|
+
normalizeToken,
|
|
44
|
+
packTranscripts,
|
|
45
|
+
parseSilenceDetect,
|
|
46
|
+
parseTimeline,
|
|
47
|
+
planPath,
|
|
48
|
+
readEdl,
|
|
49
|
+
render,
|
|
50
|
+
resolvePhrase,
|
|
51
|
+
screenTakes,
|
|
52
|
+
sha256File,
|
|
53
|
+
silenceDetectArgs,
|
|
54
|
+
statusPayload,
|
|
55
|
+
straddlingGap,
|
|
56
|
+
tightenRange,
|
|
57
|
+
tightenRanges,
|
|
58
|
+
timelineFromEdl,
|
|
59
|
+
timelineHash,
|
|
60
|
+
timelineJsonPath,
|
|
61
|
+
timelineV2Schema,
|
|
62
|
+
timelineView,
|
|
63
|
+
transcribeOne,
|
|
64
|
+
transcriptFileForSource,
|
|
65
|
+
updateCaptionsBlock,
|
|
66
|
+
usesFromDoc,
|
|
67
|
+
validateTimeline,
|
|
68
|
+
verify,
|
|
69
|
+
wordStream,
|
|
70
|
+
writeAgentManifest,
|
|
71
|
+
writeBoundaries,
|
|
72
|
+
writeCompileArtifacts,
|
|
73
|
+
writeCoverage,
|
|
74
|
+
writeSilencePrompt,
|
|
75
|
+
writeTranscriptArtifacts,
|
|
76
|
+
writeVideoEditingPrompt,
|
|
77
|
+
writeWarningsFromReview,
|
|
78
|
+
writeWordDump
|
|
79
|
+
} from "./chunk-63HIQBDT.js";
|
|
80
|
+
import {
|
|
81
|
+
CommandError,
|
|
82
|
+
DEFAULT_EDIT_ROOT,
|
|
83
|
+
VIDEO_EXTS,
|
|
84
|
+
defaultJobDir,
|
|
85
|
+
displayedDimensions,
|
|
86
|
+
ffprobe,
|
|
87
|
+
initJob,
|
|
88
|
+
isoNow,
|
|
89
|
+
loadJob,
|
|
90
|
+
mediaDuration,
|
|
91
|
+
pyFixed,
|
|
92
|
+
pyFixedPad,
|
|
93
|
+
pyRound,
|
|
94
|
+
pySpacePad,
|
|
95
|
+
requireFfmpeg,
|
|
96
|
+
resolveJob,
|
|
97
|
+
rotationDegrees,
|
|
98
|
+
run,
|
|
99
|
+
runSync,
|
|
100
|
+
shown,
|
|
101
|
+
slugify,
|
|
102
|
+
sourceFromJob,
|
|
103
|
+
transcriptPath,
|
|
104
|
+
videoStream
|
|
105
|
+
} from "./chunk-CU3NBKB4.js";
|
|
106
|
+
|
|
107
|
+
// src/transcribe-batch.ts
|
|
108
|
+
import { existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
109
|
+
import { basename, extname, join } from "path";
|
|
110
|
+
import pLimit from "p-limit";
|
|
111
|
+
var VIDEO_EXTS2 = /* @__PURE__ */ new Set([
|
|
112
|
+
".mp4",
|
|
113
|
+
".MP4",
|
|
114
|
+
".mov",
|
|
115
|
+
".MOV",
|
|
116
|
+
".mkv",
|
|
117
|
+
".MKV",
|
|
118
|
+
".avi",
|
|
119
|
+
".AVI",
|
|
120
|
+
".m4v"
|
|
121
|
+
]);
|
|
122
|
+
function findVideos(videosDir) {
|
|
123
|
+
const entries = readdirSync(videosDir).map((name) => join(videosDir, name)).filter((p) => {
|
|
124
|
+
let isFile;
|
|
125
|
+
try {
|
|
126
|
+
isFile = statSync(p).isFile();
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return isFile && VIDEO_EXTS2.has(extname(p));
|
|
131
|
+
});
|
|
132
|
+
entries.sort();
|
|
133
|
+
return entries;
|
|
134
|
+
}
|
|
135
|
+
function stemOf(p) {
|
|
136
|
+
return basename(p, extname(p));
|
|
137
|
+
}
|
|
138
|
+
async function transcribeBatch(videosDir, opts = {}) {
|
|
139
|
+
const { workers = 4, language, numSpeakers, apiKey } = opts;
|
|
140
|
+
if (!existsSync(videosDir) || !statSync(videosDir).isDirectory()) {
|
|
141
|
+
throw new Error(`not a directory: ${videosDir}`);
|
|
142
|
+
}
|
|
143
|
+
const editDir = opts.editDir ?? join(videosDir, "edit");
|
|
144
|
+
mkdirSync(join(editDir, "transcripts"), { recursive: true });
|
|
145
|
+
const videos = findVideos(videosDir);
|
|
146
|
+
if (videos.length === 0) {
|
|
147
|
+
throw new Error(`no videos found in ${videosDir}`);
|
|
148
|
+
}
|
|
149
|
+
const alreadyCached = videos.filter(
|
|
150
|
+
(v) => existsSync(join(editDir, "transcripts", `${stemOf(v)}.json`))
|
|
151
|
+
);
|
|
152
|
+
const cachedSet = new Set(alreadyCached);
|
|
153
|
+
const pending = videos.filter((v) => !cachedSet.has(v));
|
|
154
|
+
console.error(
|
|
155
|
+
`found ${videos.length} videos (${alreadyCached.length} cached, ${pending.length} to transcribe)`
|
|
156
|
+
);
|
|
157
|
+
if (pending.length === 0) {
|
|
158
|
+
console.error("nothing to do");
|
|
159
|
+
return { videos, cached: alreadyCached, transcribed: [], errors: [] };
|
|
160
|
+
}
|
|
161
|
+
console.error(`transcribing ${pending.length} files with ${workers} parallel workers`);
|
|
162
|
+
const t0 = Date.now();
|
|
163
|
+
const errors = [];
|
|
164
|
+
const transcribed = [];
|
|
165
|
+
const limit = pLimit(workers);
|
|
166
|
+
await Promise.all(
|
|
167
|
+
pending.map(
|
|
168
|
+
(v) => limit(async () => {
|
|
169
|
+
try {
|
|
170
|
+
const out = await transcribeOne(v, {
|
|
171
|
+
editDir,
|
|
172
|
+
language,
|
|
173
|
+
numSpeakers,
|
|
174
|
+
apiKey,
|
|
175
|
+
verbose: false
|
|
176
|
+
});
|
|
177
|
+
transcribed.push(out);
|
|
178
|
+
console.error(` + ${stemOf(v)} -> ${basename(out)}`);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
const message = String(e instanceof Error ? e.message : e);
|
|
181
|
+
errors.push({ video: v, message });
|
|
182
|
+
console.error(` x ${stemOf(v)} FAILED: ${message}`);
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
const dt = (Date.now() - t0) / 1e3;
|
|
188
|
+
console.error(`
|
|
189
|
+
done in ${dt.toFixed(1)}s`);
|
|
190
|
+
if (errors.length) {
|
|
191
|
+
console.error(`${errors.length} failures:`);
|
|
192
|
+
for (const { video, message } of errors) {
|
|
193
|
+
console.error(` ${basename(video)}: ${message}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { videos, cached: alreadyCached, transcribed, errors };
|
|
197
|
+
}
|
|
198
|
+
export {
|
|
199
|
+
CAPABILITIES,
|
|
200
|
+
COMPOSE_AUDIO_SRC,
|
|
201
|
+
COMPOSE_ENTRY,
|
|
202
|
+
COMPOSE_HEIGHT,
|
|
203
|
+
COMPOSE_VIDEO_SRC,
|
|
204
|
+
COMPOSE_WIDTH,
|
|
205
|
+
CommandError,
|
|
206
|
+
DEFAULT_DESILENCE,
|
|
207
|
+
DEFAULT_EDIT_ROOT,
|
|
208
|
+
PRESETS,
|
|
209
|
+
VIDEO_EXTS,
|
|
210
|
+
agentizePrompt,
|
|
211
|
+
artifactPaths,
|
|
212
|
+
autoGradeForClip,
|
|
213
|
+
checkCapabilities,
|
|
214
|
+
compileTimeline,
|
|
215
|
+
composeCounts,
|
|
216
|
+
composeHtml,
|
|
217
|
+
computeEnvelope,
|
|
218
|
+
configDir,
|
|
219
|
+
configEnvPath,
|
|
220
|
+
cutBoundaries,
|
|
221
|
+
decideGradeFilter,
|
|
222
|
+
defaultJobDir,
|
|
223
|
+
desilenceEdl,
|
|
224
|
+
detectSilences,
|
|
225
|
+
diagnosticsPath,
|
|
226
|
+
displayedDimensions,
|
|
227
|
+
duplicatedPhrase,
|
|
228
|
+
duplicatedPhrases,
|
|
229
|
+
duplicatedSpans,
|
|
230
|
+
edlPath,
|
|
231
|
+
ensureTranscribed,
|
|
232
|
+
ensureVerticalMaster,
|
|
233
|
+
ffprobe,
|
|
234
|
+
findPhrase,
|
|
235
|
+
fuzzySuggestions,
|
|
236
|
+
gatherCompileCtx,
|
|
237
|
+
generateQcImages,
|
|
238
|
+
getPreset,
|
|
239
|
+
initJob,
|
|
240
|
+
isoNow,
|
|
241
|
+
learnedEditingGuardrails,
|
|
242
|
+
loadApiKey,
|
|
243
|
+
loadJob,
|
|
244
|
+
loadTimeline,
|
|
245
|
+
loadWords,
|
|
246
|
+
loadWordsBySource,
|
|
247
|
+
mediaDuration,
|
|
248
|
+
normalizePhrase,
|
|
249
|
+
normalizeToken,
|
|
250
|
+
packTranscripts,
|
|
251
|
+
parseSilenceDetect,
|
|
252
|
+
parseTimeline,
|
|
253
|
+
planPath,
|
|
254
|
+
pyFixed,
|
|
255
|
+
pyFixedPad,
|
|
256
|
+
pyRound,
|
|
257
|
+
pySpacePad,
|
|
258
|
+
readEdl,
|
|
259
|
+
render,
|
|
260
|
+
requireFfmpeg,
|
|
261
|
+
resolveJob,
|
|
262
|
+
resolvePhrase,
|
|
263
|
+
rotationDegrees,
|
|
264
|
+
run,
|
|
265
|
+
runSync,
|
|
266
|
+
screenTakes,
|
|
267
|
+
sha256File,
|
|
268
|
+
shown,
|
|
269
|
+
silenceDetectArgs,
|
|
270
|
+
slugify,
|
|
271
|
+
sourceFromJob,
|
|
272
|
+
statusPayload,
|
|
273
|
+
straddlingGap,
|
|
274
|
+
tightenRange,
|
|
275
|
+
tightenRanges,
|
|
276
|
+
timelineFromEdl,
|
|
277
|
+
timelineHash,
|
|
278
|
+
timelineJsonPath,
|
|
279
|
+
timelineV2Schema,
|
|
280
|
+
timelineView,
|
|
281
|
+
transcribeBatch,
|
|
282
|
+
transcribeOne,
|
|
283
|
+
transcriptFileForSource,
|
|
284
|
+
transcriptPath,
|
|
285
|
+
updateCaptionsBlock,
|
|
286
|
+
usesFromDoc,
|
|
287
|
+
validateTimeline,
|
|
288
|
+
verify,
|
|
289
|
+
videoStream,
|
|
290
|
+
wordStream,
|
|
291
|
+
writeAgentManifest,
|
|
292
|
+
writeBoundaries,
|
|
293
|
+
writeCompileArtifacts,
|
|
294
|
+
writeCoverage,
|
|
295
|
+
writeSilencePrompt,
|
|
296
|
+
writeTranscriptArtifacts,
|
|
297
|
+
writeVideoEditingPrompt,
|
|
298
|
+
writeWarningsFromReview,
|
|
299
|
+
writeWordDump
|
|
300
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_EDIT_ROOT,
|
|
4
|
+
VIDEO_EXTS,
|
|
5
|
+
defaultJobDir,
|
|
6
|
+
ensureDirs,
|
|
7
|
+
initJob,
|
|
8
|
+
loadJob,
|
|
9
|
+
resolveJob,
|
|
10
|
+
sourceFromJob,
|
|
11
|
+
sourceJsonPath,
|
|
12
|
+
transcriptPath
|
|
13
|
+
} from "./chunk-CU3NBKB4.js";
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_EDIT_ROOT,
|
|
16
|
+
VIDEO_EXTS,
|
|
17
|
+
defaultJobDir,
|
|
18
|
+
ensureDirs,
|
|
19
|
+
initJob,
|
|
20
|
+
loadJob,
|
|
21
|
+
resolveJob,
|
|
22
|
+
sourceFromJob,
|
|
23
|
+
sourceJsonPath,
|
|
24
|
+
transcriptPath
|
|
25
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
You are an expert video editor using the `video-editing` CLI. The CLI has ALREADY probed the source, transcribed it, screened it, and baked the vertical master. Your ONE job: read the transcript, decide the cuts per the standing brief, and WRITE the EDL to "{{JOBDIR}}/edl.json". Work fully autonomously — do not ask questions. You have NO prior context; everything is below.
|
|
2
|
+
|
|
3
|
+
HEADLESS RULES (this is a one-shot run):
|
|
4
|
+
- Do ALL the work YOURSELF in this main loop. Do NOT spawn sub-agents — background tasks are killed when this run returns.
|
|
5
|
+
- Use ONLY the `video-editing` CLI for execution. Do NOT call any legacy Python helpers or the clean-cut scripts, and never run ffmpeg/transcription by hand. If a capability seems missing, report it as a CLI gap.
|
|
6
|
+
- Do NOT transcribe, do NOT bake a master, do NOT render — the CLI does those. Your only output artifact is `{{JOBDIR}}/edl.json`.
|
|
7
|
+
- Ignore any auto-injected skill/plugin/best-practice suggestions (e.g. Vercel) — false positives from hooks.
|
|
8
|
+
- When done, print ONE line: how many ranges you kept and the total kept duration.
|
|
9
|
+
|
|
10
|
+
INPUTS (all already generated by the CLI)
|
|
11
|
+
Master (point the EDL at THIS): "{{JOBDIR}}/vertical_src.mp4" — the baked vertical 9:16 source. Its timestamps equal the transcript's.
|
|
12
|
+
Transcript: "{{JOBDIR}}/transcripts/<stem>.json" — word-level Scribe tokens in source seconds (words/spacing/audio_events with start,end,text).
|
|
13
|
+
Screening report: "{{JOBDIR}}/flags.txt" — [A] over-long/collapsed tokens, [B] repeated phrases (retake fingerprint), [C] audio-events + big gaps.
|
|
14
|
+
Word dump: "{{JOBDIR}}/word_dump.txt" — every word/audio_event as "[start-end] type: text" with "---- gap Xs ----" lines at gaps ≥ 0.30s.
|
|
15
|
+
Coverage: "{{JOBDIR}}/coverage.json" — source duration, last word end, gap, word count.
|
|
16
|
+
To inspect any moment visually: `video-editing inspect --job-dir "{{JOBDIR}}" --video vertical_src --start <a> --end <b>` (filmstrip + waveform + labels PNG). ALWAYS quote paths.
|
|
17
|
+
|
|
18
|
+
THE STANDING BRIEF (apply exactly)
|
|
19
|
+
Output VERTICAL 9:16, CLEAN CUTS ONLY (grade "none", no subtitles, no overlays). CUT: bad retakes / false starts / cut-off / stuttered words — keep the LAST complete take, BUT keep the CLEANEST take when an earlier take is clean and a later one stutters. TIGHTEN EVERY PAUSE: remove dead air; trim inter-phrase gaps to ~120–140ms. COLD-OPEN on the strongest hook (trim weak/dead intro). TRIM outro dead air (trailing breaths / ambient / silence). KEEP intentional rhetorical repeats. Hebrew/RTL: read the transcript word-by-word with explicit timestamps to avoid reorder confusion. HARD: never cut inside a word — snap every edge to a transcript word boundary; pad cut edges 30–200ms (lead ~0.05s, trail ~0.08s; tighter ~0.03–0.05s at retake joins, staying inside the silence); the renderer adds 30ms audio fades so tight pads are safe.
|
|
20
|
+
|
|
21
|
+
STEPS
|
|
22
|
+
1. SCREEN first (Scribe hides retakes in over-long/dropped tokens): read "{{JOBDIR}}/flags.txt" — resolve every [A], judge every [B] (retake vs intentional). Confirm coverage from "{{JOBDIR}}/coverage.json" (last word ends within ~2s of media duration).
|
|
23
|
+
2. READ the full word-level transcript via "{{JOBDIR}}/word_dump.txt". Then THINK HARD and list, with EXACT timestamps: every bad retake/false-start/stutter (and which take to KEEP), every silence/dead-air (head/inter-phrase/outro), the cold-open point (strongest hook), and the intentional repeats to KEEP. When transcript text and audio seem to disagree, run `video-editing inspect` on that window and read the PNG before deciding.
|
|
24
|
+
3. WRITE "{{JOBDIR}}/edl.json":
|
|
25
|
+
{"version":1, "sources":{"SRC":"{{JOBDIR}}/vertical_src.mp4"}, "grade":"none", "ranges":[ {"source":"SRC","start":N,"end":N,"beat":"...","quote":"...","reason":"..."}, ... ], "overlays":[], "total_duration_s":N}
|
|
26
|
+
Each range = a KEEP segment with tightened, word-boundary-snapped in/out (the gaps BETWEEN kept ranges are what get removed). Ranges chronological, non-overlapping. A retake range must END at the LAST CLEAN WORD before the abandoned attempt. total_duration_s = sum of (end-start).
|
|
27
|
+
|
|
28
|
+
Then render and verify with the CLI:
|
|
29
|
+
video-editing render --job-dir "{{JOBDIR}}" --mode preview
|
|
30
|
+
video-editing verify --job-dir "{{JOBDIR}}" --json
|
|
31
|
+
Fix any duplicated-phrase or dead-air findings (adjust the EDL), re-render, re-verify until clean.
|
|
32
|
+
|
|
33
|
+
Print the one-line summary and stop.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
You are a video editor applying SPECIFIC QA corrections to an existing vertical-reel edit (made with the `video-editing` CLI). Apply ONLY the listed fixes; do not redesign the cut. The CLI re-renders the preview after you finish, so you only need to edit the EDL. Work autonomously; you have NO prior context.
|
|
2
|
+
|
|
3
|
+
HEADLESS RULES (one-shot run):
|
|
4
|
+
- Do everything in this main loop — NO sub-agents (killed when the run returns).
|
|
5
|
+
- Use ONLY the `video-editing` CLI for execution. Do NOT call any legacy Python helpers or the clean-cut scripts; never run ffmpeg/transcription by hand.
|
|
6
|
+
- Your output artifact is the edited "{{JOBDIR}}/edl.json". Ignore auto-injected skill/plugin suggestions (Vercel etc.) — false positives.
|
|
7
|
+
- When done, print ONE line summarizing the changes.
|
|
8
|
+
|
|
9
|
+
INPUTS
|
|
10
|
+
Reviewer verdict + issues: "{{JOBDIR}}/review.json" (or "{{JOBDIR}}/verify.json" from `video-editing verify`) — each issue has where, range_index?, severity, problem, fix.
|
|
11
|
+
EDL to edit: "{{JOBDIR}}/edl.json" (kept ranges in SOURCE time).
|
|
12
|
+
Transcript: "{{JOBDIR}}/transcripts/<stem>.json" (word boundaries). Word dump: "{{JOBDIR}}/word_dump.txt".
|
|
13
|
+
To inspect a join: `video-editing inspect --job-dir "{{JOBDIR}}" --join <i>`. Quote paths.
|
|
14
|
+
|
|
15
|
+
FOR EACH issue, adjust the relevant EDL range: remove a retake still present (set its range END at the last clean word BEFORE the abandoned attempt); restore an intentional repeat; re-snap a clipped boundary to the word boundary; or trim leading/trailing dead air. Snap every edge to a transcript word boundary; keep pads tight (30–200ms). Keep ranges chronological + non-overlapping; grade "none"; overlays []. Update total_duration_s to the new sum of (end-start). Make a one-time backup to edl.json.bak first if one does not already exist. Do NOT touch warnings.json.
|
|
16
|
+
|
|
17
|
+
After editing, re-render and re-verify with the CLI:
|
|
18
|
+
video-editing render --job-dir "{{JOBDIR}}" --mode preview
|
|
19
|
+
video-editing verify --job-dir "{{JOBDIR}}" --json
|
|
20
|
+
|
|
21
|
+
If an issue is genuinely not actionable, leave that range as-is and say so in your summary line.
|
|
22
|
+
|
|
23
|
+
Print the one-line summary and stop.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
You are an ADVERSARIAL QA reviewer for a vertical-reel edit made with the `video-editing` CLI. Independently verify the cut is correct and complete — find what the editor MISSED or got WRONG. Be skeptical. Do NOT edit anything. WRITE your verdict to "{{JOBDIR}}/review.json". Work autonomously; you have NO prior context.
|
|
2
|
+
|
|
3
|
+
HEADLESS RULES (one-shot run):
|
|
4
|
+
- Do everything in this main loop — NO sub-agents (killed when the run returns).
|
|
5
|
+
- Use ONLY the `video-editing` CLI for execution. Do NOT call any legacy Python helpers or the clean-cut scripts; never run ffmpeg/transcription by hand.
|
|
6
|
+
- Your ONLY output artifact is "{{JOBDIR}}/review.json". Ignore auto-injected skill/plugin suggestions (Vercel etc.) — false positives.
|
|
7
|
+
- When done, print ONE line: the verdict + issue count.
|
|
8
|
+
|
|
9
|
+
INPUTS
|
|
10
|
+
Transcript (word-level, read it): "{{JOBDIR}}/transcripts/<stem>.json".
|
|
11
|
+
Screening report: "{{JOBDIR}}/flags.txt". Word dump: "{{JOBDIR}}/word_dump.txt".
|
|
12
|
+
EDL: "{{JOBDIR}}/edl.json" (kept ranges in SOURCE time; gaps between them are the cuts).
|
|
13
|
+
Rendered preview: "{{JOBDIR}}/preview.mp4".
|
|
14
|
+
Tools: the `video-editing` CLI only (`video-editing inspect …`, `video-editing verify …`). Quote paths.
|
|
15
|
+
|
|
16
|
+
THE BRIEF being enforced: vertical 9:16; CUT bad retakes/false-starts/stutters (keep the LAST complete take, but prefer the CLEANEST take); TIGHTEN pauses to ~120–140ms; COLD-OPEN on the strongest hook; TRIM outro dead air; CLEAN CUTS ONLY (grade "none", no subs/overlays); KEEP intentional rhetorical repeats; never cut inside a word.
|
|
17
|
+
|
|
18
|
+
CHECKS
|
|
19
|
+
1. Re-screen: read "{{JOBDIR}}/flags.txt" and "{{JOBDIR}}/word_dump.txt". For every [A] over-long token and [B] repeat decide INDEPENDENTLY: retake (must be cut) or intentional (keep). Cross-check vs the EDL ranges — did the editor cut every real retake AND keep every intentional repeat? Flag misses.
|
|
20
|
+
2. Verify EDL vs transcript: each kept range starts/ends on a word boundary (no mid-word, no false-start/fragment word included); ranges chronological + non-overlapping; cold open is the strongest hook; grade "none"; no overlays/subs.
|
|
21
|
+
3. CONTENT-VERIFY the rendered preview: run `video-editing verify --job-dir "{{JOBDIR}}" --json`. It re-transcribes the preview at every join (healing disabled) and reports duplicated phrases across joins (= a retake left in) and over-long gaps (= dead air not tightened). Treat any error finding as a real defect. To eyeball a join, run `video-editing inspect --job-dir "{{JOBDIR}}" --join <i>` and READ the PNG (clipped word, leftover false-start syllable, audio pop, broken frame?).
|
|
22
|
+
4. Coverage: preview STARTS on the hook (no leading dead air) and ENDS on the CTA (no trailing footsteps/ambient/silence)?
|
|
23
|
+
|
|
24
|
+
WRITE "{{JOBDIR}}/review.json" (use a temp file + rename) as:
|
|
25
|
+
{"verdict":"clean"|"needs-fixes",
|
|
26
|
+
"issues":[ {"where":"<timestamp/range>","range_index":<edl ranges[] index if applicable>,"severity":"error"|"warning"|"info","problem":"<what>","fix":"<the concrete EDL change>"} ],
|
|
27
|
+
"confidence":"<one line>"}
|
|
28
|
+
A real retake left in / an intentional repeat wrongly cut / a clipped word at a join / leading-or-trailing dead air ⇒ "needs-fixes" (severity "error"). Minor taste calls are NOT issues; if none, verdict "clean" with issues [].
|
|
29
|
+
|
|
30
|
+
Print the one-line summary and stop.
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "video-editing",
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "Standalone TypeScript library + CLI for editing talking-head reels: transcribe, screen, cut (EDL), render vertical 9:16, QC, content-verify.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"author": "Itay Elgazar",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ItayElgazar/clipready.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/ItayElgazar/clipready#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"video-editing",
|
|
15
|
+
"video",
|
|
16
|
+
"cli",
|
|
17
|
+
"agents",
|
|
18
|
+
"ai",
|
|
19
|
+
"talking-head",
|
|
20
|
+
"reels",
|
|
21
|
+
"shorts",
|
|
22
|
+
"captions",
|
|
23
|
+
"transcription",
|
|
24
|
+
"ffmpeg",
|
|
25
|
+
"edl",
|
|
26
|
+
"clipready"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"video-editing": "dist/cli.js"
|
|
33
|
+
},
|
|
34
|
+
"main": "./dist/index.js",
|
|
35
|
+
"module": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/index.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"skills",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"dev": "tsup --watch",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest",
|
|
54
|
+
"prepublishOnly": "pnpm build"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@napi-rs/canvas": "^0.1.65",
|
|
58
|
+
"commander": "^12.1.0",
|
|
59
|
+
"p-limit": "^6.1.0",
|
|
60
|
+
"zod": "^3"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^22.10.0",
|
|
64
|
+
"tsup": "^8.3.5",
|
|
65
|
+
"tsx": "^4.19.2",
|
|
66
|
+
"typescript": "^5.7.2",
|
|
67
|
+
"vitest": "^2.1.8"
|
|
68
|
+
}
|
|
69
|
+
}
|