batchedits 1.6.0 → 1.8.0
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/README.md +16 -2
- package/dist/cli.js +87 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,15 +19,17 @@ Needs Node.js 18.17+ and a [BatchEdits](https://batchedits.com) account (7-day f
|
|
|
19
19
|
| `login` / `logout` / `whoami` | Sign in through your browser; check the connection |
|
|
20
20
|
| `styles` | List your styles |
|
|
21
21
|
| `actions` | List editing actions and their settings |
|
|
22
|
-
| `style create <name> [--captions] [--emojis] [--silence] [--repeats] [--crop 9:16] [--zoom] [--clips [n]] [--hook] [--hook-clip <id or words>] [--broll] [--cta [text]] [--sfx] [--set action.setting=value…]` | Create a style from flags |
|
|
22
|
+
| `style create <name> [--captions] [--emojis] [--silence] [--repeats] [--fillers] [--crop 9:16] [--zoom] [--clips [n]] [--hook] [--hook-clip <id or words>] [--broll] [--cta [text]] [--sfx] [--set action.setting=value…]` | Create a style from flags |
|
|
23
23
|
| `hooks [words]` | Find intro clips for a video hook |
|
|
24
24
|
| `style edit <name> [same flags] [--set …] [--remove <action>…] [--name <new>]` | Change a saved style's edits and settings |
|
|
25
25
|
| `style copy <link\|file\|video id> [--name <name>]` | Create a style copying a reference video's captions, zooms and crop |
|
|
26
|
+
| `captions <video id>` | Show a video's caption text word by word, to check names and spelling |
|
|
27
|
+
| `captions fix <video id> --replace "wrong=right"… [--word "<id>=text"…]` | Fix caption words before editing (no credit) |
|
|
26
28
|
| `upload <files, folders or links…>` | Upload up to 50 videos (YouTube or video-file links are downloaded by BatchEdits), print their ids |
|
|
27
29
|
| `process <ids…> --style <name> [--yes]` | Edit videos (1 credit each, 1 per AI clip) |
|
|
28
30
|
| `status [ids…] [--watch]` | Show status, or wait until done |
|
|
29
31
|
| `download <ids…> [--out dir]` | Save finished videos (and their AI clips) |
|
|
30
|
-
| `run <files, folders or links…> --style <name> [--out dir] [--hooks file] [--ctas file] [--yes]` | All of the above in one go |
|
|
32
|
+
| `run <files, folders or links…> --style <name> [--out dir] [--hooks file] [--ctas file] [--fix "wrong=right"…] [--yes]` | All of the above in one go |
|
|
31
33
|
|
|
32
34
|
Every command has `--help` with an example, and `--json` for machine-readable output.
|
|
33
35
|
|
|
@@ -35,6 +37,18 @@ Every command has `--help` with an example, and `--json` for machine-readable ou
|
|
|
35
37
|
|
|
36
38
|
`--set action.setting=value` sets anything `batchedits actions` lists. Short action names: `captions`, `silence`, `crop`, `zoom`, `clips`, `repeats`, `hook`.
|
|
37
39
|
|
|
40
|
+
### Fix caption words
|
|
41
|
+
|
|
42
|
+
Captions are transcribed automatically; fix names or brand spellings before the edit (no extra credit):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
batchedits captions 3f2a… # check the words
|
|
46
|
+
batchedits captions fix 3f2a… --replace "BatchEdit=BatchEdits"
|
|
47
|
+
batchedits run ./raw --style "Shorts" --fix "BatchEdit=BatchEdits" --yes # every video in one go
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Fixes apply before processing only; a finished video keeps its captions.
|
|
51
|
+
|
|
38
52
|
### Hooks
|
|
39
53
|
|
|
40
54
|
`--hook-clip` plays a short intro clip (a video hook) before every video. `--hook-clip ai` lets AI pick a different matching clip for each video. To use one clip for all of them, find it with `batchedits hooks <words>`, then pass its id, or just the words for the best match. With `--hook` too, the text hook is drawn on the clip:
|
package/dist/cli.js
CHANGED
|
@@ -398,7 +398,7 @@ var ACTION_ALIASES = {
|
|
|
398
398
|
var SET_HELP = [
|
|
399
399
|
" --set <action.setting=value> Any setting from `batchedits actions` (repeatable). Adds the edit if missing.",
|
|
400
400
|
" captions.position=top|center|bottom captions.fontColor=#FFFFFF captions.highlightColor=#FF6B00",
|
|
401
|
-
" captions.fontSize=64 silence.silentThreshold=0.06 zoom.zoomPercent=125 clips.maxSeconds=60",
|
|
401
|
+
" captions.fontSize=64 silence.silentThreshold=0.06 silence.fillerWords=true zoom.zoomPercent=125 clips.maxSeconds=60",
|
|
402
402
|
" hook.position=top|center|bottom hook.y=0.3 hook.duration=auto|3 hook.source=ai|manual hook.animation=fade|pop|none hook.backgroundColor=#FF6B00",
|
|
403
403
|
' cta.text="Link in bio" cta.duration=auto|5 cta.position=center|top|bottom cta.animation=pop|fade|none',
|
|
404
404
|
" broll.seconds=auto|3 broll.source=mine_first|mine|stock",
|
|
@@ -415,7 +415,7 @@ function upsert(actions, name, params) {
|
|
|
415
415
|
function applyFlagActions(actions, flags) {
|
|
416
416
|
let out = actions;
|
|
417
417
|
if (typeof flags.crop === "string") out = upsert(out, "crop_video", { aspectRatio: flags.crop });
|
|
418
|
-
if (flags.silence) out = upsert(out, "remove_silence");
|
|
418
|
+
if (flags.silence || flags.fillers) out = upsert(out, "remove_silence", flags.fillers ? { fillerWords: true } : void 0);
|
|
419
419
|
if (flags.repeats) out = upsert(out, "remove_repeated_sentences");
|
|
420
420
|
if (flags.captions || flags.emojis) out = upsert(out, "add_captions", flags.emojis ? { emojis: true } : void 0);
|
|
421
421
|
if (flags.zoom) out = upsert(out, "punch_zoom");
|
|
@@ -523,6 +523,40 @@ async function downloadAll(api, videos, out) {
|
|
|
523
523
|
}
|
|
524
524
|
return { saved, notReady: res.notReady ?? [] };
|
|
525
525
|
}
|
|
526
|
+
function parsePairs(value, flag) {
|
|
527
|
+
const list2 = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
528
|
+
return list2.map((raw) => {
|
|
529
|
+
const at = String(raw).indexOf("=");
|
|
530
|
+
const find = at < 0 ? "" : String(raw).slice(0, at).trim();
|
|
531
|
+
const replace = at < 0 ? "" : String(raw).slice(at + 1).trim();
|
|
532
|
+
if (!find || !replace) throw new CliError(`${flag} "${raw}" needs the form "wrong=right".`, EXIT.USAGE, `Example: ${flag} "BatchEdit=BatchEdits"`);
|
|
533
|
+
return { find, replace };
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
function parseWords(value) {
|
|
537
|
+
return parsePairs(value, "--word").map((p) => ({ wordId: p.find, text: p.replace }));
|
|
538
|
+
}
|
|
539
|
+
async function waitForCaptions(api, videoId, timeoutSec = 900) {
|
|
540
|
+
const until = Date.now() + timeoutSec * 1e3;
|
|
541
|
+
let said = false;
|
|
542
|
+
for (; ; ) {
|
|
543
|
+
const r = await api.call("get_captions", { videoId });
|
|
544
|
+
if (r.status === "ready") return r;
|
|
545
|
+
if (r.status === "failed") throw new CliError(r.message, EXIT.ERROR);
|
|
546
|
+
if (Date.now() > until) throw new CliError(`Captions for ${videoId} weren't ready after ${Math.round(timeoutSec / 60)} minutes.`, EXIT.ERROR, `Try again later: batchedits captions ${videoId}`);
|
|
547
|
+
if (!said) info(r.message);
|
|
548
|
+
said = true;
|
|
549
|
+
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async function fixCaptions(api, videoId, replacements, words = []) {
|
|
553
|
+
await waitForCaptions(api, videoId);
|
|
554
|
+
return api.call("edit_captions", {
|
|
555
|
+
videoId,
|
|
556
|
+
...replacements.length ? { replacements } : {},
|
|
557
|
+
...words.length ? { words } : {}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
526
560
|
var commands = {
|
|
527
561
|
login: {
|
|
528
562
|
summary: "Sign in through your browser",
|
|
@@ -596,13 +630,14 @@ var commands = {
|
|
|
596
630
|
},
|
|
597
631
|
"style create": {
|
|
598
632
|
summary: "Create a style from simple flags",
|
|
599
|
-
usage: "batchedits style create <name> [--captions] [--emojis] [--silence] [--crop 9:16] [--zoom] [--clips [n]] [--repeats] [--hook] [--broll] [--cta [text]] [--sfx] [--set action.setting=value\u2026]",
|
|
633
|
+
usage: "batchedits style create <name> [--captions] [--emojis] [--silence] [--crop 9:16] [--zoom] [--clips [n]] [--repeats] [--fillers] [--hook] [--broll] [--cta [text]] [--sfx] [--set action.setting=value\u2026]",
|
|
600
634
|
help: [
|
|
601
635
|
"Creates a reusable style. Order of edits: crop, silence, repeats, captions, zoom, AI clips.",
|
|
602
636
|
" --captions Auto captions (word-by-word highlight)",
|
|
603
637
|
" --emojis Add AI-picked emojis to the captions",
|
|
604
638
|
" --silence Remove silences and dead air",
|
|
605
639
|
" --repeats Remove repeated takes of the same sentence",
|
|
640
|
+
' --fillers Also cut filler words (um, uh, filler "like" / "you know"); turns on --silence',
|
|
606
641
|
" --crop <ratio> Crop every video, e.g. 9:16, 1:1, 4:5",
|
|
607
642
|
" --zoom Zoom in on key moments",
|
|
608
643
|
" --clips [n] Cut long videos into n short clips (default 10; 1 credit per clip)",
|
|
@@ -755,6 +790,37 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
755
790
|
return results.some((r) => r.error) ? EXIT.PARTIAL : EXIT.OK;
|
|
756
791
|
}
|
|
757
792
|
},
|
|
793
|
+
captions: {
|
|
794
|
+
summary: "Show a video's caption text, to check spelling",
|
|
795
|
+
usage: "batchedits captions <video id>",
|
|
796
|
+
help: `Prints the video's captions word by word as text(id), with times. Transcription starts if it hasn't run, and this waits for it (about a minute).
|
|
797
|
+
Fix words with: batchedits captions fix <video id> --replace "wrong=right"
|
|
798
|
+
Example: batchedits captions 3f2a\u2026`,
|
|
799
|
+
async run({ api, positionals }) {
|
|
800
|
+
const videoId = positionals[0];
|
|
801
|
+
if (!videoId) throw new CliError("Give the video id.", EXIT.USAGE, "Find it with: batchedits status");
|
|
802
|
+
const r = await waitForCaptions(api, videoId);
|
|
803
|
+
if (isJson()) printJson({ videoId, words: r.words ?? [] });
|
|
804
|
+
else say(r.message);
|
|
805
|
+
return EXIT.OK;
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
"captions fix": {
|
|
809
|
+
summary: "Fix caption words before editing (no credit)",
|
|
810
|
+
usage: 'batchedits captions fix <video id> --replace "wrong=right"\u2026 [--word "<id>=text"\u2026]',
|
|
811
|
+
help: 'Corrects caption text before the video is edited, like Edit captions in the editor. Uses no credit: the fixes go into the next edit.\n --replace "wrong=right" Replace everywhere (ignores case, keeps punctuation). Repeatable. A phrase needs the same number of words as its replacement.\n --word "<id>=text" Change one word, by the id `batchedits captions` shows. Repeatable.\nOnly before processing: a finished video can\'t have its captions fixed.\nExample: batchedits captions fix 3f2a\u2026 --replace "BatchEdit=BatchEdits" --replace "mike=Mike"',
|
|
812
|
+
async run({ api, positionals, flags }) {
|
|
813
|
+
const videoId = positionals[0];
|
|
814
|
+
if (!videoId) throw new CliError("Give the video id.", EXIT.USAGE, "Find it with: batchedits status");
|
|
815
|
+
const replacements = parsePairs(flags.replace, "--replace");
|
|
816
|
+
const words = parseWords(flags.word);
|
|
817
|
+
if (!replacements.length && !words.length) throw new CliError("Give --replace or --word.", EXIT.USAGE, 'Example: batchedits captions fix 3f2a\u2026 --replace "BatchEdit=BatchEdits"');
|
|
818
|
+
const r = await fixCaptions(api, videoId, replacements, words);
|
|
819
|
+
if (isJson()) printJson({ videoId, changed: r.changed, message: r.message });
|
|
820
|
+
else say(r.message);
|
|
821
|
+
return EXIT.OK;
|
|
822
|
+
}
|
|
823
|
+
},
|
|
758
824
|
status: {
|
|
759
825
|
summary: "Show video status (or --watch until done)",
|
|
760
826
|
usage: "batchedits status [video ids\u2026] [--watch] [--interval 10]",
|
|
@@ -789,18 +855,28 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
789
855
|
},
|
|
790
856
|
run: {
|
|
791
857
|
summary: "Upload, edit, wait and download in one go",
|
|
792
|
-
usage:
|
|
793
|
-
help: 'Uploads up to 50 videos (files, folders, or YouTube and video-file links), edits them with the style, waits, and downloads the results.\n --hooks <file>
|
|
858
|
+
usage: 'batchedits run <files, folders or links\u2026> --style <name|id> [--out ./edited] [--hooks <file>] [--ctas <file>] [--fix "wrong=right"\u2026] [--yes]',
|
|
859
|
+
help: 'Uploads up to 50 videos (files, folders, or YouTube and video-file links), edits them with the style, waits, and downloads the results.\n --hooks <file> One hook per line, in upload order (a folder uploads its files A-Z), for styles with a hook\n --ctas <file> One call to action per line, same order, for styles with a cta\n --fix "wrong=right" Fix a caption word in every video before editing (waits for transcription first). Repeatable, no extra credit.\nExample: batchedits run ./raw --style "Shorts" --out ./edited --fix "BatchEdit=BatchEdits" --yes',
|
|
794
860
|
async run(ctx) {
|
|
795
861
|
const { api, positionals, flags } = ctx;
|
|
796
862
|
const inputs = expandInputs(positionals);
|
|
797
863
|
const style = await resolveStyle(api, flags.style);
|
|
798
864
|
const hooks = readLinesFile(flags.hooks, style, inputs.length, "hook");
|
|
799
865
|
const ctas = readLinesFile(flags.ctas, style, inputs.length, "cta");
|
|
866
|
+
const fixes = parsePairs(flags.fix, "--fix");
|
|
800
867
|
const clips = (style.actions ?? []).some((a) => a.name === "ai_clips");
|
|
801
868
|
await confirm(flags, `Edit ${inputs.length} video${inputs.length === 1 ? "" : "s"} with "${style.name}"? This uses ${inputs.length} credit${inputs.length === 1 ? "" : "s"}${clips ? ", plus 1 per AI clip" : ""}.`);
|
|
802
869
|
const ids = [];
|
|
803
870
|
for (let i = 0; i < inputs.length; i++) ids.push((await uploadInput(api, inputs[i], i, inputs.length)).id);
|
|
871
|
+
if (fixes.length) {
|
|
872
|
+
for (const videoId of ids) {
|
|
873
|
+
try {
|
|
874
|
+
info((await fixCaptions(api, videoId, fixes)).message);
|
|
875
|
+
} catch (err) {
|
|
876
|
+
info(`Captions of ${videoId} unchanged: ${err?.message ?? err}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
804
880
|
for (const [i, videoId] of ids.entries()) await api.call("process_video", { videoId, styleId: style.id, ...perVideoText(hooks, ctas, i) });
|
|
805
881
|
info(`Editing ${ids.length} video${ids.length === 1 ? "" : "s"} with "${style.name}"\u2026`);
|
|
806
882
|
const group = await watch(api, ids, Number(flags.interval) || 10);
|
|
@@ -817,7 +893,7 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
817
893
|
};
|
|
818
894
|
|
|
819
895
|
// src/cli.ts
|
|
820
|
-
var VERSION = true ? "1.
|
|
896
|
+
var VERSION = true ? "1.8.0" : "dev";
|
|
821
897
|
var FLAG_OPTIONS = {
|
|
822
898
|
help: { type: "boolean", short: "h" },
|
|
823
899
|
version: { type: "boolean", short: "v" },
|
|
@@ -832,6 +908,7 @@ var FLAG_OPTIONS = {
|
|
|
832
908
|
emojis: { type: "boolean" },
|
|
833
909
|
silence: { type: "boolean" },
|
|
834
910
|
repeats: { type: "boolean" },
|
|
911
|
+
fillers: { type: "boolean" },
|
|
835
912
|
crop: { type: "string" },
|
|
836
913
|
zoom: { type: "boolean" },
|
|
837
914
|
clips: { type: "string" },
|
|
@@ -844,7 +921,10 @@ var FLAG_OPTIONS = {
|
|
|
844
921
|
ctas: { type: "string" },
|
|
845
922
|
set: { type: "string", multiple: true },
|
|
846
923
|
remove: { type: "string", multiple: true },
|
|
847
|
-
name: { type: "string" }
|
|
924
|
+
name: { type: "string" },
|
|
925
|
+
replace: { type: "string", multiple: true },
|
|
926
|
+
word: { type: "string", multiple: true },
|
|
927
|
+
fix: { type: "string", multiple: true }
|
|
848
928
|
};
|
|
849
929
|
function mainHelp() {
|
|
850
930
|
const names = Object.keys(commands);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "batchedits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "BatchEdits CLI, SDK and React widgets: edit up to 50 videos at once from your terminal or AI agent (Claude Code, OpenClaw, Hermes)",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|