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.
- package/README.md +12 -3
- package/dist/cli.js +33 -13
- 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.
|
|
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.
|
|
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",
|