batchedits 1.7.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 +15 -1
- package/dist/cli.js +82 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,11 +23,13 @@ Needs Node.js 18.17+ and a [BatchEdits](https://batchedits.com) account (7-day f
|
|
|
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
|
@@ -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",
|
|
@@ -756,6 +790,37 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
756
790
|
return results.some((r) => r.error) ? EXIT.PARTIAL : EXIT.OK;
|
|
757
791
|
}
|
|
758
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
|
+
},
|
|
759
824
|
status: {
|
|
760
825
|
summary: "Show video status (or --watch until done)",
|
|
761
826
|
usage: "batchedits status [video ids\u2026] [--watch] [--interval 10]",
|
|
@@ -790,18 +855,28 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
790
855
|
},
|
|
791
856
|
run: {
|
|
792
857
|
summary: "Upload, edit, wait and download in one go",
|
|
793
|
-
usage:
|
|
794
|
-
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',
|
|
795
860
|
async run(ctx) {
|
|
796
861
|
const { api, positionals, flags } = ctx;
|
|
797
862
|
const inputs = expandInputs(positionals);
|
|
798
863
|
const style = await resolveStyle(api, flags.style);
|
|
799
864
|
const hooks = readLinesFile(flags.hooks, style, inputs.length, "hook");
|
|
800
865
|
const ctas = readLinesFile(flags.ctas, style, inputs.length, "cta");
|
|
866
|
+
const fixes = parsePairs(flags.fix, "--fix");
|
|
801
867
|
const clips = (style.actions ?? []).some((a) => a.name === "ai_clips");
|
|
802
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" : ""}.`);
|
|
803
869
|
const ids = [];
|
|
804
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
|
+
}
|
|
805
880
|
for (const [i, videoId] of ids.entries()) await api.call("process_video", { videoId, styleId: style.id, ...perVideoText(hooks, ctas, i) });
|
|
806
881
|
info(`Editing ${ids.length} video${ids.length === 1 ? "" : "s"} with "${style.name}"\u2026`);
|
|
807
882
|
const group = await watch(api, ids, Number(flags.interval) || 10);
|
|
@@ -818,7 +893,7 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
818
893
|
};
|
|
819
894
|
|
|
820
895
|
// src/cli.ts
|
|
821
|
-
var VERSION = true ? "1.
|
|
896
|
+
var VERSION = true ? "1.8.0" : "dev";
|
|
822
897
|
var FLAG_OPTIONS = {
|
|
823
898
|
help: { type: "boolean", short: "h" },
|
|
824
899
|
version: { type: "boolean", short: "v" },
|
|
@@ -846,7 +921,10 @@ var FLAG_OPTIONS = {
|
|
|
846
921
|
ctas: { type: "string" },
|
|
847
922
|
set: { type: "string", multiple: true },
|
|
848
923
|
remove: { type: "string", multiple: true },
|
|
849
|
-
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 }
|
|
850
928
|
};
|
|
851
929
|
function mainHelp() {
|
|
852
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",
|