autocrew 0.3.9 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autocrew",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "description": "One-person content studio powered by AI — from trending topics to published posts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -100,6 +100,7 @@ import { cmd as trashCmd } from "./trash.js";
100
100
  import { cmd as restoreCmd } from "./restore.js";
101
101
  import { cmd as migrateCmd } from "./migrate.js";
102
102
  import { cmd as draftCmd } from "./draft.js";
103
+ import { cmd as renderCmd } from "./render.js";
103
104
 
104
105
  export const commands: CommandDef[] = [
105
106
  statusCmd,
@@ -130,4 +131,5 @@ export const commands: CommandDef[] = [
130
131
  restoreCmd,
131
132
  migrateCmd,
132
133
  draftCmd,
134
+ renderCmd,
133
135
  ];
@@ -0,0 +1,78 @@
1
+ import type { CommandDef } from "./index.js";
2
+ import { getOption } from "./index.js";
3
+
4
+ export const cmd: CommandDef = {
5
+ name: "render",
6
+ description: "Render a video timeline (TTS + screenshots + Jianying export)",
7
+ usage: "autocrew render <project-slug> [--voice <voiceId>] [--ratio <9:16|16:9>]",
8
+ action: async (args, runner) => {
9
+ const slug = args[0];
10
+ if (!slug) {
11
+ console.error("Usage: autocrew render <project-slug> [--voice <voiceId>] [--ratio <9:16|16:9>]");
12
+ process.exitCode = 1;
13
+ return;
14
+ }
15
+
16
+ const voice = getOption(args, "--voice") || "default";
17
+ const ratio = getOption(args, "--ratio") || "9:16";
18
+
19
+ // Find project and its timeline
20
+ try {
21
+ const { findProject } = await import("../../storage/pipeline-store.js");
22
+ const path = await import("node:path");
23
+ const fs = await import("node:fs/promises");
24
+
25
+ const found = await findProject(slug);
26
+ if (!found) {
27
+ console.error(`Project not found: "${slug}"`);
28
+ process.exitCode = 1;
29
+ return;
30
+ }
31
+
32
+ const timelinePath = path.join(found.dir, "timeline.json");
33
+ let timeline;
34
+ try {
35
+ const raw = await fs.readFile(timelinePath, "utf-8");
36
+ timeline = JSON.parse(raw);
37
+ } catch {
38
+ console.error(`No timeline.json found in project "${slug}".`);
39
+ console.error("Generate a timeline first with autocrew_timeline action='generate'.");
40
+ process.exitCode = 1;
41
+ return;
42
+ }
43
+
44
+ // Try to load studio
45
+ let renderTimeline: any;
46
+ let loadConfig: any;
47
+ try {
48
+ const studio = await import("autocrew-studio");
49
+ renderTimeline = studio.renderTimeline;
50
+ loadConfig = studio.loadConfig;
51
+ } catch {
52
+ console.error("autocrew-studio is not installed. Install it with:");
53
+ console.error(" npm install autocrew-studio");
54
+ process.exitCode = 1;
55
+ return;
56
+ }
57
+
58
+ const config = loadConfig();
59
+ const outputDir = path.join(found.dir, "render");
60
+
61
+ console.log(`Rendering timeline for "${slug}" (${ratio}, voice: ${voice})...`);
62
+
63
+ const result = await renderTimeline({
64
+ timeline,
65
+ outputDir,
66
+ tts: config.tts,
67
+ screenshot: config.screenshot,
68
+ exporter: config.exporter,
69
+ voice: { voiceId: voice },
70
+ });
71
+
72
+ console.log(`Render complete: ${result.path} (${result.format})`);
73
+ } catch (err: any) {
74
+ console.error(`Render failed: ${err.message || err}`);
75
+ process.exitCode = 1;
76
+ }
77
+ },
78
+ };
@@ -212,10 +212,14 @@ export async function executeReview(params: Record<string, unknown>) {
212
212
  const scanResult = await scanText(fullText, platform, dataDir);
213
213
  let fixedText = scanResult.autoFixedText || fullText;
214
214
 
215
- // 2. Humanizer pass
215
+ // 2. Humanizer pass (substitution + deletion only, never insertion)
216
216
  const humanResult = humanizeZh({ text: fixedText });
217
217
  fixedText = humanResult.humanizedText;
218
218
 
219
+ // 3. Safety guard — strip any hardcoded phrases that old humanizer versions
220
+ // may have inserted. This is a belt-and-suspenders defense.
221
+ fixedText = fixedText.replace(/\n*说白了,这件事拼的不是工具数量,而是表达和执行。\n*/g, "\n\n");
222
+
219
223
  // Save back if content_id provided
220
224
  if (contentId) {
221
225
  await updateContent(contentId, { body: fixedText }, dataDir);