mulmocast 2.6.15 → 2.6.16

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.
@@ -8,4 +8,5 @@ export * from "./pdf.js";
8
8
  export * from "./translate.js";
9
9
  export * from "./markdown.js";
10
10
  export * from "./html.js";
11
+ export * from "./viewer.js";
11
12
  export * from "./bundle.js";
@@ -8,4 +8,5 @@ export * from "./pdf.js";
8
8
  export * from "./translate.js";
9
9
  export * from "./markdown.js";
10
10
  export * from "./html.js";
11
+ export * from "./viewer.js";
11
12
  export * from "./bundle.js";
@@ -0,0 +1,3 @@
1
+ import { MulmoStudioContext } from "../types/index.js";
2
+ export declare const viewerFilePath: (context: MulmoStudioContext) => string;
3
+ export declare const viewer: (context: MulmoStudioContext) => Promise<void>;
@@ -0,0 +1,173 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { localizedText } from "../utils/utils.js";
4
+ import { writingMessage } from "../utils/file.js";
5
+ import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
6
+ import { escapeHtml } from "../slide/utils.js";
7
+ const imageMimeTypes = {
8
+ png: "image/png",
9
+ jpg: "image/jpeg",
10
+ jpeg: "image/jpeg",
11
+ gif: "image/gif",
12
+ webp: "image/webp",
13
+ svg: "image/svg+xml",
14
+ };
15
+ const imageToDataUri = (filePath) => {
16
+ if (!fs.existsSync(filePath)) {
17
+ return null;
18
+ }
19
+ const ext = path.extname(filePath).slice(1).toLowerCase();
20
+ const mime = imageMimeTypes[ext] ?? "image/png";
21
+ const base64 = fs.readFileSync(filePath).toString("base64");
22
+ return `data:${mime};base64,${base64}`;
23
+ };
24
+ const collectSlides = (context) => {
25
+ const { studio, multiLingual, lang = "en" } = context;
26
+ const slides = [];
27
+ studio.script.beats.forEach((beat, index) => {
28
+ const studioBeat = studio.beats[index];
29
+ // Prefer the HTML-rendered frame over the source image, matching pdf.ts / movie.ts.
30
+ const source = studioBeat?.htmlImageFile ?? studioBeat?.imageFile;
31
+ if (!source)
32
+ return;
33
+ const dataUri = imageToDataUri(source);
34
+ if (!dataUri)
35
+ return;
36
+ slides.push({
37
+ dataUri,
38
+ caption: localizedText(beat, multiLingual?.[index], lang),
39
+ });
40
+ });
41
+ return slides;
42
+ };
43
+ const viewerStyles = `
44
+ html, body { margin: 0; padding: 0; height: 100vh; overflow: hidden; background: #000; }
45
+ #deck { position: relative; width: 100vw; height: 100vh; }
46
+ section.slide {
47
+ position: absolute;
48
+ inset: 0;
49
+ display: flex;
50
+ flex-direction: column;
51
+ align-items: center;
52
+ justify-content: center;
53
+ visibility: hidden;
54
+ padding: 24px;
55
+ box-sizing: border-box;
56
+ }
57
+ section.slide.active { visibility: visible; }
58
+ section.slide img {
59
+ max-width: 100%;
60
+ max-height: 80vh;
61
+ object-fit: contain;
62
+ user-select: none;
63
+ }
64
+ section.slide p.caption {
65
+ color: #eee;
66
+ margin-top: 16px;
67
+ text-align: center;
68
+ font-family: system-ui, -apple-system, sans-serif;
69
+ max-width: 80ch;
70
+ max-height: 15vh;
71
+ overflow: hidden;
72
+ }
73
+ #counter {
74
+ position: fixed;
75
+ right: 16px;
76
+ bottom: 12px;
77
+ color: #aaa;
78
+ font-family: system-ui, -apple-system, sans-serif;
79
+ font-size: 14px;
80
+ user-select: none;
81
+ pointer-events: none;
82
+ }
83
+ `;
84
+ const viewerScript = `
85
+ (function () {
86
+ const slides = document.querySelectorAll("section.slide");
87
+ const counter = document.getElementById("counter");
88
+ if (slides.length === 0) return;
89
+ let current = 0;
90
+ const show = (i) => {
91
+ slides[current].classList.remove("active");
92
+ current = Math.max(0, Math.min(slides.length - 1, i));
93
+ slides[current].classList.add("active");
94
+ counter.textContent = (current + 1) + " / " + slides.length;
95
+ };
96
+ document.addEventListener("keydown", (e) => {
97
+ if (e.key === "ArrowRight" || e.key === " " || e.key === "PageDown") {
98
+ e.preventDefault();
99
+ show(current + 1);
100
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
101
+ e.preventDefault();
102
+ show(current - 1);
103
+ } else if (e.key === "Home") {
104
+ e.preventDefault();
105
+ show(0);
106
+ } else if (e.key === "End") {
107
+ e.preventDefault();
108
+ show(slides.length - 1);
109
+ } else if (e.key === "f" || e.key === "F") {
110
+ if (!document.fullscreenElement) {
111
+ document.documentElement.requestFullscreen && document.documentElement.requestFullscreen();
112
+ } else {
113
+ document.exitFullscreen && document.exitFullscreen();
114
+ }
115
+ } else if (e.key === "Escape") {
116
+ document.exitFullscreen && document.exitFullscreen();
117
+ }
118
+ });
119
+ document.addEventListener("click", () => show(current + 1));
120
+ show(0);
121
+ })();
122
+ `;
123
+ const generateViewerHtml = (context) => {
124
+ const title = context.studio.script.title || "MulmoCast Viewer";
125
+ const slides = collectSlides(context);
126
+ const slidesHtml = slides
127
+ .map((s, i) => {
128
+ const captionHtml = s.caption.trim() ? `<p class="caption">${escapeHtml(s.caption)}</p>` : "";
129
+ const cls = i === 0 ? "slide active" : "slide";
130
+ return ` <section class="${cls}"><img src="${s.dataUri}" alt="Slide ${i + 1}" />${captionHtml}</section>`;
131
+ })
132
+ .join("\n");
133
+ return `<!doctype html>
134
+ <html lang="en">
135
+ <head>
136
+ <meta charset="UTF-8" />
137
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
138
+ <title>${escapeHtml(title)}</title>
139
+ <style>${viewerStyles}</style>
140
+ </head>
141
+ <body>
142
+ <div id="deck">
143
+ ${slidesHtml}
144
+ </div>
145
+ <div id="counter"></div>
146
+ <script>${viewerScript}</script>
147
+ </body>
148
+ </html>
149
+ `;
150
+ };
151
+ export const viewerFilePath = (context) => {
152
+ const { studio, fileDirs, lang = "en" } = context;
153
+ const langSuffix = studio.script.lang !== lang ? `_${lang}` : "";
154
+ const filename = `${studio.filename}${langSuffix}_viewer.html`;
155
+ return path.join(fileDirs.outDirPath, filename);
156
+ };
157
+ const generateViewer = (context) => {
158
+ const outputPath = viewerFilePath(context);
159
+ const htmlContent = generateViewerHtml(context);
160
+ fs.writeFileSync(outputPath, htmlContent, "utf8");
161
+ writingMessage(outputPath);
162
+ };
163
+ export const viewer = async (context) => {
164
+ try {
165
+ MulmoStudioContextMethods.setSessionState(context, "viewer", true);
166
+ generateViewer(context);
167
+ MulmoStudioContextMethods.setSessionState(context, "viewer", false, true);
168
+ }
169
+ catch (error) {
170
+ MulmoStudioContextMethods.setSessionState(context, "viewer", false, false);
171
+ throw error;
172
+ }
173
+ };
@@ -0,0 +1,20 @@
1
+ import type { Argv } from "yargs";
2
+ export declare const builder: (yargs: Argv) => Argv<{
3
+ o: string | undefined;
4
+ } & {
5
+ b: string | undefined;
6
+ } & {
7
+ l: string | undefined;
8
+ } & {
9
+ f: boolean;
10
+ } & {
11
+ g: boolean;
12
+ } & {
13
+ backup: boolean;
14
+ } & {
15
+ p: string | undefined;
16
+ } & {
17
+ file: string | undefined;
18
+ } & {
19
+ i: string | undefined;
20
+ }>;
@@ -0,0 +1,6 @@
1
+ import { commonOptions } from "../../common.js";
2
+ export const builder = (yargs) => commonOptions(yargs).option("i", {
3
+ alias: "imagedir",
4
+ describe: "Image output directory",
5
+ type: "string",
6
+ });
@@ -0,0 +1,4 @@
1
+ import { CliArgs } from "../../../types/cli_types.js";
2
+ export declare const handler: (argv: CliArgs<{
3
+ i?: string;
4
+ }>) => Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { images, viewer } from "../../../actions/index.js";
2
+ import { initializeContext, runTranslateIfNeeded } from "../../helpers.js";
3
+ export const handler = async (argv) => {
4
+ const context = await initializeContext(argv);
5
+ if (!context) {
6
+ process.exit(1);
7
+ }
8
+ await runTranslateIfNeeded(context);
9
+ await images(context);
10
+ await viewer(context);
11
+ };
@@ -0,0 +1,4 @@
1
+ export declare const command = "viewer <file>";
2
+ export declare const desc = "Generate a zero-dependency, single-file HTML slideshow viewer (openable via file://)";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
@@ -0,0 +1,4 @@
1
+ export const command = "viewer <file>";
2
+ export const desc = "Generate a zero-dependency, single-file HTML slideshow viewer (openable via file://)";
3
+ export { builder } from "./builder.js";
4
+ export { handler } from "./handler.js";
package/lib/cli/main.js CHANGED
@@ -12,6 +12,7 @@ import * as pdfCmd from "./commands/pdf/index.js";
12
12
  import * as markdownCmd from "./commands/markdown/index.js";
13
13
  import * as bundleCmd from "./commands/bundle/index.js";
14
14
  import * as htmlCmd from "./commands/html/index.js";
15
+ import * as viewerCmd from "./commands/viewer/index.js";
15
16
  import * as toolCmd from "./commands/tool/index.js";
16
17
  import { GraphAILogger } from "graphai";
17
18
  dotenv.config({ quiet: true });
@@ -42,6 +43,7 @@ export const cliMain = async () => {
42
43
  .command(markdownCmd)
43
44
  .command(bundleCmd)
44
45
  .command(htmlCmd)
46
+ .command(viewerCmd)
45
47
  .command(toolCmd)
46
48
  .demandCommand()
47
49
  .strict()
@@ -10800,6 +10800,7 @@ export declare const mulmoSessionStateSchema: z.ZodObject<{
10800
10800
  pdf: z.ZodBoolean;
10801
10801
  markdown: z.ZodBoolean;
10802
10802
  html: z.ZodBoolean;
10803
+ viewer: z.ZodBoolean;
10803
10804
  }, z.core.$strip>;
10804
10805
  inBeatSession: z.ZodObject<{
10805
10806
  audio: z.ZodRecord<z.ZodString, z.ZodBoolean>;
@@ -669,6 +669,7 @@ export const mulmoSessionStateSchema = z.object({
669
669
  pdf: z.boolean(),
670
670
  markdown: z.boolean(),
671
671
  html: z.boolean(),
672
+ viewer: z.boolean(),
672
673
  }),
673
674
  inBeatSession: z.object({
674
675
  audio: z.record(z.string(), z.boolean()),
@@ -104,7 +104,7 @@ export type Text2HtmlAgentInfo = {
104
104
  };
105
105
  export type BeatMediaType = "movie" | "image";
106
106
  export type StoryToScriptGenerateMode = (typeof storyToScriptGenerateMode)[keyof typeof storyToScriptGenerateMode];
107
- export type SessionType = "audio" | "image" | "video" | "multiLingual" | "caption" | "pdf" | "markdown" | "html";
107
+ export type SessionType = "audio" | "image" | "video" | "multiLingual" | "caption" | "pdf" | "markdown" | "html" | "viewer";
108
108
  export type BeatSessionType = "audio" | "image" | "multiLingual" | "caption" | "movie" | "html" | "imageReference" | "soundEffect" | "lipSync";
109
109
  export type SessionProgressEvent = {
110
110
  kind: "session";
@@ -4887,6 +4887,7 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
4887
4887
  pdf: boolean;
4888
4888
  markdown: boolean;
4889
4889
  html: boolean;
4890
+ viewer: boolean;
4890
4891
  };
4891
4892
  inBeatSession: {
4892
4893
  audio: {};
@@ -46,6 +46,7 @@ const initSessionState = () => {
46
46
  pdf: false,
47
47
  markdown: false,
48
48
  html: false,
49
+ viewer: false,
49
50
  },
50
51
  inBeatSession: {
51
52
  audio: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "2.6.15",
3
+ "version": "2.6.16",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",
@@ -25,8 +25,8 @@
25
25
  },
26
26
  "resolutions": {
27
27
  "minimatch": "^10.2.5",
28
- "tar": "7.5.13",
29
- "yauzl": "^3.3.0"
28
+ "tar": "7.5.15",
29
+ "yauzl": "^3.3.1"
30
30
  },
31
31
  "bin": {
32
32
  "mulmo": "lib/cli/bin.js",
@@ -108,12 +108,12 @@
108
108
  "archiver": "^7.0.1",
109
109
  "clipboardy": "^5.3.1",
110
110
  "dotenv": "^17.4.2",
111
- "graphai": "^2.0.16",
111
+ "graphai": "^2.0.17",
112
112
  "jsdom": "^29.1.1",
113
- "marked": "^18.0.3",
113
+ "marked": "^18.0.4",
114
114
  "mulmocast-vision": "^1.0.10",
115
115
  "ora": "^9.4.0",
116
- "puppeteer": "^25.0.3",
116
+ "puppeteer": "^25.0.4",
117
117
  "replicate": "^1.4.0",
118
118
  "yaml": "^2.9.0",
119
119
  "yargs": "^18.0.0",
@@ -133,9 +133,9 @@
133
133
  "eslint-plugin-sonarjs": "^4.0.3",
134
134
  "globals": "^17.6.0",
135
135
  "prettier": "^3.8.3",
136
- "tsx": "^4.22.1",
136
+ "tsx": "^4.22.3",
137
137
  "typescript": "6.0.3",
138
- "typescript-eslint": "^8.59.3"
138
+ "typescript-eslint": "^8.59.4"
139
139
  },
140
140
  "engines": {
141
141
  "node": ">=22.0.0"