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.
Files changed (3) hide show
  1. package/README.md +15 -1
  2. package/dist/cli.js +82 -4
  3. 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: "batchedits run <files, folders or links\u2026> --style <name|id> [--out ./edited] [--hooks <file>] [--ctas <file>] [--yes]",
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> 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\nExample: batchedits run ./raw --style "Shorts" --out ./edited --yes',
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.7.0" : "dev";
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.7.0",
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",