@lalalic/markcut 3.2.13 → 3.2.14

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 CHANGED
@@ -370,3 +370,13 @@ export MARKCUT_VTT_CLI='my-video-backend --prompt "{prompt}" {input}'
370
370
  ```
371
371
 
372
372
  ## Architecture
373
+
374
+ ### Global queue CLI template (opt-in)
375
+
376
+ Set `MARKCUT_QUEUE_CLI_TEMPLATE` in Markcut's environment to route generation and vision CLI subprocesses through the PM2-managed queue worker. `{command}` is the shell-quoted original command; the queue wrapper sets `MARKCUT_QUEUE_ACTIVE=1` for descendants to prevent re-entrance.
377
+
378
+ ```bash
379
+ export MARKCUT_QUEUE_CLI_TEMPLATE='node "$HOME/.agents/skills/queue/queue.mjs" run --queue media-ai -- /bin/sh -c {command}'
380
+ ```
381
+
382
+ The template must use an executable reachable from the process environment. To avoid ambiguity with shell quoting of `$HOME`, use a literal absolute path in production. If unset, execution is unchanged. Rendering cache continues to be managed by Markcut, not the queue. No service restart or Markcut-specific queue daemon is required.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalalic/markcut",
3
- "version": "3.2.13",
3
+ "version": "3.2.14",
4
4
  "description": "Markdown-to-video engine. Describe scenes in markdown, get a rendered video.",
5
5
  "bin": {
6
6
  "markcut": "bin/markcut"
@@ -0,0 +1,17 @@
1
+ /** Optional global queue CLI template for expensive Markcut operations. */
2
+ export function shellQuote(value) {
3
+ return "'" + String(value).replace(/'/g, "'\\''") + "'";
4
+ }
5
+ export function queueCommand(command, env = process.env) {
6
+ const template = env.MARKCUT_QUEUE_CLI_TEMPLATE;
7
+ if (!template || env.MARKCUT_QUEUE_ACTIVE === "1") return command;
8
+ if (!template.includes("{command}")) throw Error("MARKCUT_QUEUE_CLI_TEMPLATE must contain {command}");
9
+ // The worker runs a shell to execute the original CLI without re-entering the queue.
10
+ return template.replaceAll("{command}", shellQuote(`MARKCUT_QUEUE_ACTIVE=1 ${command}`));
11
+ }
12
+ export function queueArgv(command, argv, env = process.env) {
13
+ const template = env.MARKCUT_QUEUE_CLI_TEMPLATE;
14
+ if (!template || env.MARKCUT_QUEUE_ACTIVE === "1") return { command, argv };
15
+ const cmd = [command, ...argv].map(shellQuote).join(" ");
16
+ return { command: "/bin/sh", argv: ["-c", queueCommand(cmd, env)] };
17
+ }
@@ -11,6 +11,7 @@
11
11
  * Override via MARKCUT_TTS_CLI / MARKCUT_STT_CLI / MARKCUT_TTI_CLI / MARKCUT_TTV_CLI.
12
12
  */
13
13
  import { execSync, exec } from "node:child_process";
14
+ import { queueCommand } from "../queue-cli.mjs";
14
15
  import { existsSync, mkdirSync } from "node:fs";
15
16
  import { dirname } from "node:path";
16
17
  import {
@@ -89,7 +90,7 @@ export function generateTTS(text: string, outputPath: string, cli?: string): str
89
90
  mkdirSync(dirname(outputPath), { recursive: true });
90
91
  const cmd = substituteCli(cli ?? DEFAULT_TTS_CLI, text, outputPath);
91
92
  try {
92
- execSync(cmd, { stdio: "pipe" });
93
+ execSync(queueCommand(cmd), { stdio: "pipe" });
93
94
  } catch (e: any) {
94
95
  console.warn(` ⚠ TTS failed: ${e.message}`);
95
96
  return "";
@@ -104,7 +105,7 @@ export function generateTTS(text: string, outputPath: string, cli?: string): str
104
105
  export async function generateSTT(audioPath: string, outputDir: string, cli?: string): Promise<void> {
105
106
  const cmd = substituteCli(cli ?? DEFAULT_STT_CLI, audioPath, outputDir);
106
107
  try {
107
- execSync(cmd, { stdio: "pipe" });
108
+ execSync(queueCommand(cmd), { stdio: "pipe" });
108
109
  } catch (e: any) {
109
110
  console.warn(` ⚠ STT failed: ${e.message}`);
110
111
  }
@@ -119,7 +120,7 @@ export function generateTTI(prompt: string, outputPath: string, cli?: string, se
119
120
  mkdirSync(dirname(outputPath), { recursive: true });
120
121
  const cmd = substituteCli(cli ?? DEFAULT_TTI_CLI, prompt, outputPath, seed);
121
122
  try {
122
- execSync(cmd, {
123
+ execSync(queueCommand(cmd), {
123
124
  encoding: "utf-8",
124
125
  stdio: ["pipe", "pipe", "pipe"],
125
126
  timeout: 300_000,
@@ -162,7 +163,7 @@ export function generateTTV(
162
163
  }
163
164
  try {
164
165
  execSync(
165
- `ffmpeg -y -loop 1 -i "${pngPath}" -c:v libx264 -t 3 -pix_fmt yuv420p "${outputPath}"`,
166
+ queueCommand(`ffmpeg -y -loop 1 -i "${pngPath}" -c:v libx264 -t 3 -pix_fmt yuv420p "${outputPath}"`),
166
167
  { stdio: ["pipe", "pipe", "pipe"], timeout: 60_000 },
167
168
  );
168
169
  } catch (e: any) {
@@ -177,7 +178,7 @@ export function generateTTV(
177
178
  // Custom CLI mode: substitute template and run
178
179
  const cmd = substituteCli(cli, prompt, outputPath, seed);
179
180
  try {
180
- execSync(cmd, {
181
+ execSync(queueCommand(cmd), {
181
182
  encoding: "utf-8",
182
183
  stdio: ["pipe", "pipe", "pipe"],
183
184
  timeout: 300_000,
@@ -5,6 +5,7 @@ import {
5
5
  import { basename, dirname, extname, join, resolve } from "node:path";
6
6
  import { z } from "zod";
7
7
  import { execSync } from "node:child_process";
8
+ import { queueCommand } from "../queue-cli.mjs";
8
9
  import { VIDEO_EXTS, DEFAULT_VTT_SAMPLE_INTERVAL } from "../config.mjs";
9
10
 
10
11
  export const CANDIDATE_CONTRACT_VERSION = 1;
@@ -107,7 +108,7 @@ function shQuote(value) {
107
108
  }
108
109
 
109
110
  function run(command, options = {}) {
110
- return execSync(command, {
111
+ return execSync(queueCommand(command), {
111
112
  encoding: "utf-8",
112
113
  stdio: ["pipe", "pipe", "pipe"],
113
114
  timeout: 300_000,
@@ -37,6 +37,7 @@
37
37
  */
38
38
 
39
39
  import { execSync, spawn } from "node:child_process";
40
+ import { queueCommand } from "../queue-cli.mjs";
40
41
  import { createHash } from "node:crypto";
41
42
  import {
42
43
  existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync, renameSync, rmSync,
@@ -67,7 +68,7 @@ function emitWarn(msg) { console.error(`⚠️ ${msg}`); }
67
68
  function emitError(msg) { console.error(`❌ ${msg}`); }
68
69
 
69
70
  function run(cmd, opts = {}) {
70
- return execSync(cmd, {
71
+ return execSync(queueCommand(cmd), {
71
72
  encoding: "utf-8",
72
73
  stdio: ["pipe", "pipe", "pipe"],
73
74
  timeout: 300_000,
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
+ import { queueArgv } from "../queue-cli.mjs";
3
4
  import { accessSync, constants, existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
4
5
  import { tmpdir } from "node:os";
5
6
  import { join } from "node:path";
@@ -28,7 +29,8 @@ function which(name) {
28
29
  }
29
30
 
30
31
  function run(command, args, options = {}) {
31
- const r = spawnSync(command, args, { encoding: "utf8", timeout: options.timeout || 600_000, env: process.env });
32
+ const queued = queueArgv(command, args);
33
+ const r = spawnSync(queued.command, queued.argv, { encoding: "utf8", timeout: options.timeout || 600_000, env: process.env });
32
34
  return { ok: r.status === 0, stdout: (r.stdout || "").trim(), stderr: (r.stderr || "").trim(), status: r.status };
33
35
  }
34
36
 
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { queueCommand, queueArgv } from "../src/queue-cli.mjs";
3
+ describe("global queue CLI template", () => {
4
+ it("is disabled unless global env is set", () => expect(queueCommand("echo one", {})).toBe("echo one"));
5
+ it("wraps a shell command with a recursion guard", () => {
6
+ const env = { MARKCUT_QUEUE_CLI_TEMPLATE: "node queue.mjs run --queue media-ai -- /bin/sh -c {command}" };
7
+ const result = queueCommand('echo "a b"', env);
8
+ expect(result).toContain("MARKCUT_QUEUE_ACTIVE=1");
9
+ expect(result).toContain("node queue.mjs run --queue media-ai");
10
+ expect(queueCommand("echo no nested", {...env, MARKCUT_QUEUE_ACTIVE:"1"})).toBe("echo no nested");
11
+ });
12
+ it("rejects unusable templates", () => expect(() => queueCommand("echo 1", {MARKCUT_QUEUE_CLI_TEMPLATE:"invalid"})).toThrow(/\{command\}/));
13
+ it("handles spawned CLI argv without escaping loss", () => {
14
+ const r=queueArgv("python",["file with spaces", "quoted'word"],{MARKCUT_QUEUE_CLI_TEMPLATE:"queue run -- /bin/sh -c {command}"});
15
+ expect(r.command).toBe("/bin/sh");expect(r.argv[1]).toContain("MARKCUT_QUEUE_ACTIVE");
16
+ });
17
+ });