batchedits 1.1.0 → 1.3.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 +22 -2
- package/dist/cli.js +141 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,15 +19,35 @@ 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]]` | 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
|
+
| `style edit <name> [same flags] [--set …] [--remove <action>…] [--name <new>]` | Change a saved style's edits and settings |
|
|
23
24
|
| `upload <files or folders…>` | Upload up to 50 videos, print their ids |
|
|
24
25
|
| `process <ids…> --style <name> [--yes]` | Edit videos (1 credit each, 1 per AI clip) |
|
|
25
26
|
| `status [ids…] [--watch]` | Show status, or wait until done |
|
|
26
27
|
| `download <ids…> [--out dir]` | Save finished videos (and their AI clips) |
|
|
27
|
-
| `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 |
|
|
28
29
|
|
|
29
30
|
Every command has `--help` with an example, and `--json` for machine-readable output.
|
|
30
31
|
|
|
32
|
+
### Settings
|
|
33
|
+
|
|
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
|
+
```
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
batchedits style create "Shorts" --crop 9:16 --silence --captions \
|
|
47
|
+
--set captions.position=center --set captions.highlightColor=#FF6B00 --set zoom.zoomPercent=125
|
|
48
|
+
batchedits style edit "Shorts" --set captions.fontColor=#FFFF00 --remove zoom
|
|
49
|
+
```
|
|
50
|
+
|
|
31
51
|
## Sign in
|
|
32
52
|
|
|
33
53
|
- `batchedits login` opens BatchEdits in your browser to approve the CLI, then stores the login in `~/.config/batchedits`. The token is never printed, so agents can run commands without seeing it.
|
package/dist/cli.js
CHANGED
|
@@ -355,6 +355,81 @@ 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", "hook"];
|
|
359
|
+
var ACTION_ALIASES = {
|
|
360
|
+
crop: "crop_video",
|
|
361
|
+
silence: "remove_silence",
|
|
362
|
+
repeats: "remove_repeated_sentences",
|
|
363
|
+
captions: "add_captions",
|
|
364
|
+
zoom: "punch_zoom",
|
|
365
|
+
clips: "ai_clips",
|
|
366
|
+
hook: "hook"
|
|
367
|
+
};
|
|
368
|
+
var SET_HELP = [
|
|
369
|
+
" --set <action.setting=value> Any setting from `batchedits actions` (repeatable). Adds the edit if missing.",
|
|
370
|
+
" captions.position=top|center|bottom captions.fontColor=#FFFFFF captions.highlightColor=#FF6B00",
|
|
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.backgroundColor=#FF6B00"
|
|
373
|
+
].join("\n");
|
|
374
|
+
var actionName = (raw) => ACTION_ALIASES[raw.trim().toLowerCase()] ?? raw.trim().toLowerCase();
|
|
375
|
+
var list = (v) => Array.isArray(v) ? v : typeof v === "string" ? [v] : [];
|
|
376
|
+
var sortActions = (a) => [...a].sort((x, y) => (ACTION_ORDER.indexOf(x.name) + 1 || 99) - (ACTION_ORDER.indexOf(y.name) + 1 || 99));
|
|
377
|
+
function upsert(actions, name, params) {
|
|
378
|
+
const existing = actions.find((a) => a.name === name);
|
|
379
|
+
if (!existing) return sortActions([...actions, { name, ...params ? { customParams: params } : {} }]);
|
|
380
|
+
return actions.map((a) => a.name === name && params ? { ...a, customParams: { ...a.customParams ?? {}, ...params } } : a);
|
|
381
|
+
}
|
|
382
|
+
function applyFlagActions(actions, flags) {
|
|
383
|
+
let out = actions;
|
|
384
|
+
if (typeof flags.crop === "string") out = upsert(out, "crop_video", { aspectRatio: flags.crop });
|
|
385
|
+
if (flags.silence) out = upsert(out, "remove_silence");
|
|
386
|
+
if (flags.repeats) out = upsert(out, "remove_repeated_sentences");
|
|
387
|
+
if (flags.captions || flags.emojis) out = upsert(out, "add_captions", flags.emojis ? { emojis: true } : void 0);
|
|
388
|
+
if (flags.zoom) out = upsert(out, "punch_zoom");
|
|
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");
|
|
391
|
+
return out;
|
|
392
|
+
}
|
|
393
|
+
function parseValue(raw) {
|
|
394
|
+
if (raw === "true" || raw === "false") return raw === "true";
|
|
395
|
+
if (raw.trim() !== "" && !Number.isNaN(Number(raw))) return Number(raw);
|
|
396
|
+
return raw;
|
|
397
|
+
}
|
|
398
|
+
function applySets(actions, sets) {
|
|
399
|
+
let out = actions;
|
|
400
|
+
for (const entry of list(sets)) {
|
|
401
|
+
const m = entry.match(/^([\w-]+)\.([\w-]+)=(.*)$/s);
|
|
402
|
+
if (!m) throw new CliError(`Can't read --set ${entry}.`, EXIT.USAGE, "Use action.setting=value, e.g. --set captions.position=center");
|
|
403
|
+
out = upsert(out, actionName(m[1]), { [m[2]]: parseValue(m[3]) });
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
async function actionIds(api) {
|
|
408
|
+
const { actions } = await api.call("list_actions");
|
|
409
|
+
return new Map(actions.map((a) => [a.name, a.id]));
|
|
410
|
+
}
|
|
411
|
+
function toToolActions(actions, ids) {
|
|
412
|
+
const unknown = actions.filter((a) => !ids.has(a.name)).map((a) => a.name);
|
|
413
|
+
if (unknown.length) throw new CliError(`Unknown edit: ${unknown.join(", ")}.`, EXIT.USAGE, `Use one of: ${Object.keys(ACTION_ALIASES).join(", ")} (or a name from: batchedits actions).`);
|
|
414
|
+
return actions.map((a) => ({ id: ids.get(a.name), ...a.customParams ? { customParams: a.customParams } : {} }));
|
|
415
|
+
}
|
|
416
|
+
function describeActions(actions) {
|
|
417
|
+
return actions.map((a) => {
|
|
418
|
+
const p = Object.entries(a.customParams ?? {}).filter(([k]) => !["focusX", "focusY", "fitToVideo"].includes(k));
|
|
419
|
+
return p.length ? `${a.name} (${p.map(([k, v]) => `${k} ${v}`).join(", ")})` : a.name;
|
|
420
|
+
}).join(", ");
|
|
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
|
+
}
|
|
358
433
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
359
434
|
function withClips(all, ids) {
|
|
360
435
|
const wanted = new Set(ids);
|
|
@@ -463,7 +538,7 @@ var commands = {
|
|
|
463
538
|
},
|
|
464
539
|
"style create": {
|
|
465
540
|
summary: "Create a style from simple flags",
|
|
466
|
-
usage: "batchedits style create <name> [--captions] [--emojis] [--silence] [--crop 9:16] [--zoom] [--clips [n]] [--repeats]",
|
|
541
|
+
usage: "batchedits style create <name> [--captions] [--emojis] [--silence] [--crop 9:16] [--zoom] [--clips [n]] [--repeats] [--hook] [--set action.setting=value\u2026]",
|
|
467
542
|
help: [
|
|
468
543
|
"Creates a reusable style. Order of edits: crop, silence, repeats, captions, zoom, AI clips.",
|
|
469
544
|
" --captions Auto captions (word-by-word highlight)",
|
|
@@ -473,29 +548,59 @@ var commands = {
|
|
|
473
548
|
" --crop <ratio> Crop every video, e.g. 9:16, 1:1, 4:5",
|
|
474
549
|
" --zoom Zoom in on key moments",
|
|
475
550
|
" --clips [n] Cut long videos into n short clips (default 10; 1 credit per clip)",
|
|
476
|
-
|
|
551
|
+
" --hook A text hook at the start of each video (AI writes it, or pass --hooks to run/process)",
|
|
552
|
+
SET_HELP,
|
|
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'
|
|
477
555
|
].join("\n"),
|
|
478
556
|
async run({ api, positionals, flags }) {
|
|
479
557
|
const name = positionals.join(" ").trim();
|
|
480
558
|
if (!name) throw new CliError("Give the style a name.", EXIT.USAGE, 'Example: batchedits style create "Shorts" --captions --silence');
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
const wanted = [];
|
|
484
|
-
if (typeof flags.crop === "string") wanted.push({ name: "crop_video", customParams: { aspectRatio: flags.crop } });
|
|
485
|
-
if (flags.silence) wanted.push({ name: "remove_silence" });
|
|
486
|
-
if (flags.repeats) wanted.push({ name: "remove_repeated_sentences" });
|
|
487
|
-
if (flags.captions || flags.emojis) wanted.push({ name: "add_captions", customParams: flags.emojis ? { emojis: true } : void 0 });
|
|
488
|
-
if (flags.zoom) wanted.push({ name: "punch_zoom" });
|
|
489
|
-
if (flags.clips) wanted.push({ name: "ai_clips", customParams: typeof flags.clips === "string" ? { maxClips: Number(flags.clips) } : void 0 });
|
|
559
|
+
const ids = await actionIds(api);
|
|
560
|
+
const wanted = applySets(applyFlagActions([], flags), flags.set);
|
|
490
561
|
if (wanted.length === 0) throw new CliError("Pick at least one edit.", EXIT.USAGE, "See: batchedits style create --help");
|
|
491
|
-
const
|
|
492
|
-
if (
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
562
|
+
const res = await api.call("create_style", { name, actions: toToolActions(wanted, ids) });
|
|
563
|
+
if (isJson()) printJson({ styleId: res.styleId, name, actions: wanted });
|
|
564
|
+
else say(`Created style "${name}" (${res.styleId}): ${describeActions(wanted)}.
|
|
565
|
+
Use it with: --style "${name}"`);
|
|
566
|
+
return EXIT.OK;
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
"style edit": {
|
|
570
|
+
summary: "Change a style's edits and settings",
|
|
571
|
+
usage: "batchedits style edit <name|id> [same flags as style create] [--set action.setting=value\u2026] [--remove <action>\u2026] [--name <new name>]",
|
|
572
|
+
help: [
|
|
573
|
+
"Changes a saved style in place. Flags add an edit (or update it, e.g. --crop 1:1); other edits stay as they are.",
|
|
574
|
+
" --remove <action> Remove an edit, e.g. --remove zoom (repeatable)",
|
|
575
|
+
" --name <name> Rename the style",
|
|
576
|
+
SET_HELP,
|
|
577
|
+
"Videos already edited keep their look; the next ones use the new settings.",
|
|
578
|
+
'Example: batchedits style edit "Shorts" --set captions.fontColor=#FFFF00 --remove zoom'
|
|
579
|
+
].join("\n"),
|
|
580
|
+
async run({ api, positionals, flags }) {
|
|
581
|
+
const ref = positionals.join(" ").trim();
|
|
582
|
+
if (!ref) throw new CliError("Give the style to edit.", EXIT.USAGE, 'Example: batchedits style edit "Shorts" --set captions.position=center');
|
|
583
|
+
const style = await resolveStyle(api, ref);
|
|
584
|
+
const ids = await actionIds(api);
|
|
585
|
+
let actions = (style.actions ?? []).map((a) => ({ name: a.name, ...a.customParams ? { customParams: { ...a.customParams } } : {} }));
|
|
586
|
+
const removes = list(flags.remove).map(actionName);
|
|
587
|
+
const missing = removes.filter((r) => !actions.some((a) => a.name === r));
|
|
588
|
+
if (missing.length) throw new CliError(`"${style.name}" has no ${missing.join(", ")} to remove.`, EXIT.USAGE, `It has: ${describeActions(actions) || "no edits"}.`);
|
|
589
|
+
actions = actions.filter((a) => !removes.includes(a.name));
|
|
590
|
+
actions = applySets(applyFlagActions(actions, flags), flags.set);
|
|
591
|
+
const newName = typeof flags.name === "string" ? flags.name.trim() : void 0;
|
|
592
|
+
if (actions.length === 0) throw new CliError("A style needs at least one edit.", EXIT.USAGE, "Remove fewer edits, or add one with a flag like --captions.");
|
|
593
|
+
await api.call("edit_style", { styleId: style.id, ...newName ? { name: newName } : {}, actions: toToolActions(actions, ids) });
|
|
594
|
+
const saved = (await api.styles()).find((s) => s.id === style.id);
|
|
595
|
+
const savedNames = (saved?.actions ?? []).map((a) => a.name).join(",");
|
|
596
|
+
const setKeys = list(flags.set).map((e) => e.split("=")[0].split(".")).map(([a, k]) => [actionName(a), k]);
|
|
597
|
+
const keysSaved = setKeys.every(([a, k]) => saved?.actions?.find((x) => x.name === a)?.customParams?.[k] !== void 0);
|
|
598
|
+
if (savedNames !== actions.map((a) => a.name).join(",") || !keysSaved) {
|
|
599
|
+
throw new CliError("The server didn't save the new edits.", EXIT.ERROR, "It may not support editing styles yet. Create a new style with: batchedits style create");
|
|
600
|
+
}
|
|
601
|
+
const finalName = newName || style.name;
|
|
602
|
+
if (isJson()) printJson({ styleId: style.id, name: finalName, actions });
|
|
603
|
+
else say(`Updated style "${finalName}": ${describeActions(actions)}.`);
|
|
499
604
|
return EXIT.OK;
|
|
500
605
|
}
|
|
501
606
|
},
|
|
@@ -521,17 +626,18 @@ Next: batchedits process ${uploaded.map((v) => v.id).join(" ")} --style "<name>"
|
|
|
521
626
|
},
|
|
522
627
|
process: {
|
|
523
628
|
summary: "Edit videos with a style (uses credits)",
|
|
524
|
-
usage: "batchedits process <video ids\u2026> --style <name|id> [--yes]",
|
|
525
|
-
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',
|
|
526
631
|
async run({ api, positionals, flags }) {
|
|
527
632
|
if (positionals.length === 0) throw new CliError("Give the video ids to process.", EXIT.USAGE, "Find them with: batchedits status");
|
|
528
633
|
const style = await resolveStyle(api, flags.style);
|
|
634
|
+
const hooks = readHooksFile(flags.hooks, style, positionals.length);
|
|
529
635
|
const clips = (style.actions ?? []).some((a) => a.name === "ai_clips");
|
|
530
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" : ""}.`);
|
|
531
637
|
const results = [];
|
|
532
|
-
for (const videoId of positionals) {
|
|
638
|
+
for (const [i, videoId] of positionals.entries()) {
|
|
533
639
|
try {
|
|
534
|
-
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] } : {} });
|
|
535
641
|
results.push({ videoId, status: r.status ?? "queued" });
|
|
536
642
|
} catch (err) {
|
|
537
643
|
if (err instanceof CliError && err.hint && results.length === 0) throw err;
|
|
@@ -581,12 +687,13 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
581
687
|
},
|
|
582
688
|
run: {
|
|
583
689
|
summary: "Upload, edit, wait and download in one go",
|
|
584
|
-
usage: "batchedits run <files or folders\u2026> --style <name|id> [--out ./edited] [--yes]",
|
|
585
|
-
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',
|
|
586
692
|
async run(ctx) {
|
|
587
693
|
const { api, positionals, flags } = ctx;
|
|
588
694
|
const files = expandFiles(positionals);
|
|
589
695
|
const style = await resolveStyle(api, flags.style);
|
|
696
|
+
const hooks = readHooksFile(flags.hooks, style, files.length);
|
|
590
697
|
const clips = (style.actions ?? []).some((a) => a.name === "ai_clips");
|
|
591
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" : ""}.`);
|
|
592
699
|
const ids = [];
|
|
@@ -594,7 +701,7 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
594
701
|
info(`Uploading ${i + 1}/${files.length}: ${import_node_path3.default.basename(files[i])}`);
|
|
595
702
|
ids.push((await api.upload(files[i])).id);
|
|
596
703
|
}
|
|
597
|
-
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] } : {} });
|
|
598
705
|
info(`Editing ${ids.length} video${ids.length === 1 ? "" : "s"} with "${style.name}"\u2026`);
|
|
599
706
|
const group = await watch(api, ids, Number(flags.interval) || 10);
|
|
600
707
|
const { saved, notReady } = await downloadAll(api, group, typeof flags.out === "string" ? flags.out : "edited");
|
|
@@ -610,7 +717,7 @@ Next: batchedits status ${positionals.join(" ")} --watch`);
|
|
|
610
717
|
};
|
|
611
718
|
|
|
612
719
|
// src/cli.ts
|
|
613
|
-
var VERSION = true ? "1.
|
|
720
|
+
var VERSION = true ? "1.3.0" : "dev";
|
|
614
721
|
var FLAG_OPTIONS = {
|
|
615
722
|
help: { type: "boolean", short: "h" },
|
|
616
723
|
version: { type: "boolean", short: "v" },
|
|
@@ -627,7 +734,12 @@ var FLAG_OPTIONS = {
|
|
|
627
734
|
repeats: { type: "boolean" },
|
|
628
735
|
crop: { type: "string" },
|
|
629
736
|
zoom: { type: "boolean" },
|
|
630
|
-
clips: { type: "string" }
|
|
737
|
+
clips: { type: "string" },
|
|
738
|
+
hook: { type: "boolean" },
|
|
739
|
+
hooks: { type: "string" },
|
|
740
|
+
set: { type: "string", multiple: true },
|
|
741
|
+
remove: { type: "string", multiple: true },
|
|
742
|
+
name: { type: "string" }
|
|
631
743
|
};
|
|
632
744
|
function mainHelp() {
|
|
633
745
|
const names = Object.keys(commands);
|
|
@@ -646,6 +758,7 @@ function mainHelp() {
|
|
|
646
758
|
"Quick start:",
|
|
647
759
|
" batchedits login",
|
|
648
760
|
' batchedits style create "Shorts" --crop 9:16 --silence --captions --zoom',
|
|
761
|
+
' batchedits style edit "Shorts" --set captions.position=center',
|
|
649
762
|
' batchedits run ./raw --style "Shorts" --out ./edited',
|
|
650
763
|
"",
|
|
651
764
|
"Exit codes: 0 ok \xB7 1 error \xB7 2 usage \xB7 3 sign in needed \xB7 4 some videos failed \xB7 5 needs --yes"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "batchedits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|