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