batchedits 1.2.0 → 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.
Files changed (3) hide show
  1. package/README.md +12 -3
  2. package/dist/cli.js +33 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -19,19 +19,28 @@ 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]] [--set action.setting=value…]` | Create a style from flags |
22
+ | `style create <name> [--captions] [--emojis] [--silence] [--repeats] [--crop 9:16] [--zoom] [--clips [n]] [--hook] [--set action.setting=value…]` | Create a style from flags |
23
23
  | `style edit <name> [same flags] [--set …] [--remove <action>…] [--name <new>]` | Change a saved style's edits and settings |
24
24
  | `upload <files or folders…>` | Upload up to 50 videos, print their ids |
25
25
  | `process <ids…> --style <name> [--yes]` | Edit videos (1 credit each, 1 per AI clip) |
26
26
  | `status [ids…] [--watch]` | Show status, or wait until done |
27
27
  | `download <ids…> [--out dir]` | Save finished videos (and their AI clips) |
28
- | `run <files or folders…> --style <name> [--out dir] [--yes]` | All of the above in one go |
28
+ | `run <files or folders…> --style <name> [--out dir] [--hooks file] [--yes]` | All of the above in one go |
29
29
 
30
30
  Every command has `--help` with an example, and `--json` for machine-readable output.
31
31
 
32
32
  ### Settings
33
33
 
34
- `--set action.setting=value` sets anything `batchedits actions` lists. Short action names: `captions`, `silence`, `crop`, `zoom`, `clips`, `repeats`.
34
+ `--set action.setting=value` sets anything `batchedits actions` lists. Short action names: `captions`, `silence`, `crop`, `zoom`, `clips`, `repeats`, `hook`.
35
+
36
+ ### Hooks
37
+
38
+ `--hook` adds a text hook at the start of every video; AI writes each line. To use your own, put one hook per line in a file and pass it with `--hooks` (line 1 → first video, in upload order; a folder uploads its files A-Z):
39
+
40
+ ```bash
41
+ batchedits style create "Hooks" --captions --silence --hook --set hook.position=top
42
+ batchedits run ./raw --style "Hooks" --hooks hooks.txt --out ./edited --yes
43
+ ```
35
44
 
36
45
  ```bash
37
46
  batchedits style create "Shorts" --crop 9:16 --silence --captions \
package/dist/cli.js CHANGED
@@ -355,19 +355,21 @@ async function confirm(flags, question) {
355
355
  rl.close();
356
356
  if (answer !== "y" && answer !== "yes") throw new CliError("Cancelled. Nothing was processed.", EXIT.ERROR);
357
357
  }
358
- var ACTION_ORDER = ["crop_video", "remove_silence", "remove_repeated_sentences", "add_captions", "punch_zoom", "ai_clips"];
358
+ var ACTION_ORDER = ["crop_video", "remove_silence", "remove_repeated_sentences", "add_captions", "punch_zoom", "ai_clips", "hook"];
359
359
  var ACTION_ALIASES = {
360
360
  crop: "crop_video",
361
361
  silence: "remove_silence",
362
362
  repeats: "remove_repeated_sentences",
363
363
  captions: "add_captions",
364
364
  zoom: "punch_zoom",
365
- clips: "ai_clips"
365
+ clips: "ai_clips",
366
+ hook: "hook"
366
367
  };
367
368
  var SET_HELP = [
368
369
  " --set <action.setting=value> Any setting from `batchedits actions` (repeatable). Adds the edit if missing.",
369
370
  " captions.position=top|center|bottom captions.fontColor=#FFFFFF captions.highlightColor=#FF6B00",
370
- " captions.fontSize=64 silence.silentThreshold=0.06 zoom.zoomPercent=125 clips.maxSeconds=60"
371
+ " captions.fontSize=64 silence.silentThreshold=0.06 zoom.zoomPercent=125 clips.maxSeconds=60",
372
+ " 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"
371
373
  ].join("\n");
372
374
  var actionName = (raw) => ACTION_ALIASES[raw.trim().toLowerCase()] ?? raw.trim().toLowerCase();
373
375
  var list = (v) => Array.isArray(v) ? v : typeof v === "string" ? [v] : [];
@@ -385,6 +387,7 @@ function applyFlagActions(actions, flags) {
385
387
  if (flags.captions || flags.emojis) out = upsert(out, "add_captions", flags.emojis ? { emojis: true } : void 0);
386
388
  if (flags.zoom) out = upsert(out, "punch_zoom");
387
389
  if (flags.clips) out = upsert(out, "ai_clips", typeof flags.clips === "string" ? { maxClips: Number(flags.clips) } : void 0);
390
+ if (flags.hook) out = upsert(out, "hook");
388
391
  return out;
389
392
  }
390
393
  function parseValue(raw) {
@@ -416,6 +419,17 @@ function describeActions(actions) {
416
419
  return p.length ? `${a.name} (${p.map(([k, v]) => `${k} ${v}`).join(", ")})` : a.name;
417
420
  }).join(", ");
418
421
  }
422
+ function readHooksFile(file, style, count) {
423
+ if (typeof file !== "string" || !file) return [];
424
+ if (!import_node_fs3.default.existsSync(file)) throw new CliError(`No such file: ${file}`, EXIT.USAGE, "Pass a text file with one hook per line.");
425
+ const lines = import_node_fs3.default.readFileSync(file, "utf8").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
426
+ if (!(style.actions ?? []).some((a) => a.name === "hook")) {
427
+ info(`Note: "${style.name}" has no hook, so --hooks won't show. Add one with: batchedits style edit "${style.name}" --hook`);
428
+ }
429
+ if (lines.length < count) info(`Note: ${lines.length} hook${lines.length === 1 ? "" : "s"} for ${count} videos; the rest get an AI hook (or none, if the style's hook source is manual).`);
430
+ else if (lines.length > count) info(`Note: ${lines.length - count} extra hook line${lines.length - count === 1 ? "" : "s"} ignored.`);
431
+ return lines.slice(0, count).map((l) => l.slice(0, 120));
432
+ }
419
433
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
420
434
  function withClips(all, ids) {
421
435
  const wanted = new Set(ids);
@@ -524,7 +538,7 @@ var commands = {
524
538
  },
525
539
  "style create": {
526
540
  summary: "Create a style from simple flags",
527
- usage: "batchedits style create <name> [--captions] [--emojis] [--silence] [--crop 9:16] [--zoom] [--clips [n]] [--repeats] [--set action.setting=value\u2026]",
541
+ usage: "batchedits style create <name> [--captions] [--emojis] [--silence] [--crop 9:16] [--zoom] [--clips [n]] [--repeats] [--hook] [--set action.setting=value\u2026]",
528
542
  help: [
529
543
  "Creates a reusable style. Order of edits: crop, silence, repeats, captions, zoom, AI clips.",
530
544
  " --captions Auto captions (word-by-word highlight)",
@@ -534,8 +548,10 @@ var commands = {
534
548
  " --crop <ratio> Crop every video, e.g. 9:16, 1:1, 4:5",
535
549
  " --zoom Zoom in on key moments",
536
550
  " --clips [n] Cut long videos into n short clips (default 10; 1 credit per clip)",
551
+ " --hook A text hook at the start of each video (AI writes it, or pass --hooks to run/process)",
537
552
  SET_HELP,
538
- 'Example: batchedits style create "Shorts" --crop 9:16 --silence --captions --zoom --set captions.position=center'
553
+ 'Example: batchedits style create "Shorts" --crop 9:16 --silence --captions --zoom --set captions.position=center',
554
+ 'Example: batchedits style create "Hooks" --captions --hook --set hook.position=top --set hook.duration=3'
539
555
  ].join("\n"),
540
556
  async run({ api, positionals, flags }) {
541
557
  const name = positionals.join(" ").trim();
@@ -610,17 +626,18 @@ Next: batchedits process ${uploaded.map((v) => v.id).join(" ")} --style "<name>"
610
626
  },
611
627
  process: {
612
628
  summary: "Edit videos with a style (uses credits)",
613
- usage: "batchedits process <video ids\u2026> --style <name|id> [--yes]",
614
- help: 'Queues each video with the style. Each video uses 1 credit; with AI clips each clip uses 1.\nAsks before spending credits; agents and scripts pass --yes.\nExample: batchedits process 3f2a\u2026 9c1d\u2026 --style "Shorts" --yes',
629
+ usage: "batchedits process <video ids\u2026> --style <name|id> [--hooks <file>] [--yes]",
630
+ help: 'Queues each video with the style. Each video uses 1 credit; with AI clips each clip uses 1.\nAsks before spending credits; agents and scripts pass --yes.\n --hooks <file> One hook per line, in the same order as the ids (for styles with a hook)\nExample: batchedits process 3f2a\u2026 9c1d\u2026 --style "Shorts" --yes',
615
631
  async run({ api, positionals, flags }) {
616
632
  if (positionals.length === 0) throw new CliError("Give the video ids to process.", EXIT.USAGE, "Find them with: batchedits status");
617
633
  const style = await resolveStyle(api, flags.style);
634
+ const hooks = readHooksFile(flags.hooks, style, positionals.length);
618
635
  const clips = (style.actions ?? []).some((a) => a.name === "ai_clips");
619
636
  await confirm(flags, `Edit ${positionals.length} video${positionals.length === 1 ? "" : "s"} with "${style.name}"? This uses ${positionals.length} credit${positionals.length === 1 ? "" : "s"}${clips ? ", plus 1 per AI clip" : ""}.`);
620
637
  const results = [];
621
- for (const videoId of positionals) {
638
+ for (const [i, videoId] of positionals.entries()) {
622
639
  try {
623
- const r = await api.call("process_video", { videoId, styleId: style.id });
640
+ const r = await api.call("process_video", { videoId, styleId: style.id, ...hooks[i] ? { hookText: hooks[i] } : {} });
624
641
  results.push({ videoId, status: r.status ?? "queued" });
625
642
  } catch (err) {
626
643
  if (err instanceof CliError && err.hint && results.length === 0) throw err;
@@ -670,12 +687,13 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
670
687
  },
671
688
  run: {
672
689
  summary: "Upload, edit, wait and download in one go",
673
- usage: "batchedits run <files or folders\u2026> --style <name|id> [--out ./edited] [--yes]",
674
- help: 'Uploads up to 50 videos, edits them with the style, waits, and downloads the results.\nExample: batchedits run ./raw --style "Shorts" --out ./edited --yes',
690
+ usage: "batchedits run <files or folders\u2026> --style <name|id> [--out ./edited] [--hooks <file>] [--yes]",
691
+ help: 'Uploads up to 50 videos, 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\nExample: batchedits run ./raw --style "Shorts" --out ./edited --yes',
675
692
  async run(ctx) {
676
693
  const { api, positionals, flags } = ctx;
677
694
  const files = expandFiles(positionals);
678
695
  const style = await resolveStyle(api, flags.style);
696
+ const hooks = readHooksFile(flags.hooks, style, files.length);
679
697
  const clips = (style.actions ?? []).some((a) => a.name === "ai_clips");
680
698
  await confirm(flags, `Edit ${files.length} video${files.length === 1 ? "" : "s"} with "${style.name}"? This uses ${files.length} credit${files.length === 1 ? "" : "s"}${clips ? ", plus 1 per AI clip" : ""}.`);
681
699
  const ids = [];
@@ -683,7 +701,7 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
683
701
  info(`Uploading ${i + 1}/${files.length}: ${import_node_path3.default.basename(files[i])}`);
684
702
  ids.push((await api.upload(files[i])).id);
685
703
  }
686
- for (const videoId of ids) await api.call("process_video", { videoId, styleId: style.id });
704
+ for (const [i, videoId] of ids.entries()) await api.call("process_video", { videoId, styleId: style.id, ...hooks[i] ? { hookText: hooks[i] } : {} });
687
705
  info(`Editing ${ids.length} video${ids.length === 1 ? "" : "s"} with "${style.name}"\u2026`);
688
706
  const group = await watch(api, ids, Number(flags.interval) || 10);
689
707
  const { saved, notReady } = await downloadAll(api, group, typeof flags.out === "string" ? flags.out : "edited");
@@ -699,7 +717,7 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
699
717
  };
700
718
 
701
719
  // src/cli.ts
702
- var VERSION = true ? "1.2.0" : "dev";
720
+ var VERSION = true ? "1.3.1" : "dev";
703
721
  var FLAG_OPTIONS = {
704
722
  help: { type: "boolean", short: "h" },
705
723
  version: { type: "boolean", short: "v" },
@@ -717,6 +735,8 @@ var FLAG_OPTIONS = {
717
735
  crop: { type: "string" },
718
736
  zoom: { type: "boolean" },
719
737
  clips: { type: "string" },
738
+ hook: { type: "boolean" },
739
+ hooks: { type: "string" },
720
740
  set: { type: "string", multiple: true },
721
741
  remove: { type: "string", multiple: true },
722
742
  name: { type: "string" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "batchedits",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
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",