mulmocast 0.0.28 → 0.1.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.
@@ -20,7 +20,7 @@ export const speechOptionsSchema = z
20
20
  })
21
21
  .strict();
22
22
  const speakerIdSchema = z.string();
23
- export const text2SpeechProviderSchema = z.union([z.literal("openai"), z.literal("nijivoice"), z.literal("google"), z.literal("elevenlabs")]).default("openai");
23
+ export const text2SpeechProviderSchema = z.enum(["openai", "nijivoice", "google", "elevenlabs"]).default("openai");
24
24
  export const speakerDataSchema = z
25
25
  .object({
26
26
  displayName: z.record(langSchema, z.string()).optional(),
@@ -117,6 +117,12 @@ export const mulmoBeatReferenceMediaSchema = z
117
117
  id: z.string().optional().describe("Specifies the beat to reference."),
118
118
  })
119
119
  .strict();
120
+ export const mulmoVoiceOverMediaSchema = z
121
+ .object({
122
+ type: z.literal("voice_over"),
123
+ startAt: z.number().optional().describe("The time to start the voice over the video in seconds."),
124
+ })
125
+ .strict();
120
126
  export const mulmoImageAssetSchema = z.union([
121
127
  mulmoMarkdownMediaSchema,
122
128
  mulmoWebMediaSchema,
@@ -131,6 +137,7 @@ export const mulmoImageAssetSchema = z.union([
131
137
  mulmoMermaidMediaSchema,
132
138
  mulmoHtmlTailwindMediaSchema,
133
139
  mulmoBeatReferenceMediaSchema,
140
+ mulmoVoiceOverMediaSchema,
134
141
  ]);
135
142
  const mulmoAudioMediaSchema = z
136
143
  .object({
@@ -152,8 +159,24 @@ export const mulmoFillOptionSchema = z
152
159
  style: z.enum(["aspectFit", "aspectFill"]).default("aspectFit"),
153
160
  })
154
161
  .describe("How to handle aspect ratio differences between image and canvas");
162
+ export const text2ImageProviderSchema = z.enum(["openai", "google"]).default("openai");
163
+ // NOTE: This is for UI only. (until we figure out how to use it in mulmoImageParamsSchema)
164
+ export const mulmoOpenAIImageModelSchema = z
165
+ .object({
166
+ provider: z.literal("openai"),
167
+ model: z.enum(["dall-e-3", "gpt-image-1"]).optional(),
168
+ })
169
+ .strict();
170
+ // NOTE: This is for UI only. (until we figure out how to use it in mulmoImageParamsSchema)
171
+ export const mulmoGoogleImageModelSchema = z
172
+ .object({
173
+ provider: z.literal("google"),
174
+ model: z.enum(["imagen-3.0-fast-generate-001", "imagen-3.0-generate-002", "imagen-3.0-capability-001"]).optional(),
175
+ })
176
+ .strict();
155
177
  export const mulmoImageParamsSchema = z
156
178
  .object({
179
+ provider: text2ImageProviderSchema, // has default value
157
180
  model: z.string().optional(), // default: provider specific
158
181
  style: z.string().optional(), // optional image style
159
182
  moderation: z.string().optional(), // optional image style
@@ -209,7 +232,8 @@ export const mulmoBeatSchema = z
209
232
  audioParams: beatAudioParamsSchema.optional(), // beat specific parameters
210
233
  movieParams: z
211
234
  .object({
212
- fillOption: mulmoFillOptionSchema,
235
+ fillOption: mulmoFillOptionSchema.optional(),
236
+ speed: z.number().optional().describe("Speed of the video. 1.0 is normal speed. 0.5 is half speed. 2.0 is double speed."),
213
237
  })
214
238
  .optional(),
215
239
  htmlImageParams: mulmoHtmlImageParamsSchema.optional(),
@@ -241,9 +265,22 @@ export const mulmoSpeechParamsSchema = z
241
265
  speakers: speakerDictionarySchema,
242
266
  })
243
267
  .strict();
244
- export const text2ImageProviderSchema = z.union([z.literal("openai"), z.literal("google")]).default("openai");
245
- export const text2HtmlImageProviderSchema = z.union([z.literal("openai"), z.literal("anthropic")]).default("openai");
246
- export const text2MovieProviderSchema = z.union([z.literal("openai"), z.literal("google"), z.literal("replicate")]).default("google");
268
+ export const text2HtmlImageProviderSchema = z.enum(["openai", "anthropic"]).default("openai");
269
+ export const text2MovieProviderSchema = z.enum(["google", "replicate"]).default("google");
270
+ // NOTE: This is UI only. (until we figure out how to use it in mulmoMovieParamsSchema)
271
+ export const mulmoGoogleMovieModelSchema = z
272
+ .object({
273
+ provider: z.literal("google"),
274
+ model: z.enum(["veo-2.0-generate-001"]).optional(),
275
+ })
276
+ .strict();
277
+ // NOTE: This is UI only. (until we figure out how to use it in mulmoMovieParamsSchema)
278
+ export const mulmoReplicateMovieModelSchema = z
279
+ .object({
280
+ provider: z.literal("replicate"),
281
+ model: z.enum(["bytedance/seedance-1-lite", "kwaivgi/kling-v2.1", "google/veo-3"]).optional(),
282
+ })
283
+ .strict();
247
284
  export const mulmoTransitionSchema = z.object({
248
285
  type: z.enum(["fade", "slideout_left"]),
249
286
  duration: z.number().min(0).max(2).default(0.3), // transition duration in seconds
@@ -269,11 +306,7 @@ export const mulmoPresentationStyleSchema = z.object({
269
306
  },
270
307
  },
271
308
  }),
272
- imageParams: mulmoImageParamsSchema
273
- .extend({
274
- provider: text2ImageProviderSchema, // has default value
275
- })
276
- .optional(),
309
+ imageParams: mulmoImageParamsSchema.optional(),
277
310
  movieParams: mulmoMovieParamsSchema.optional(),
278
311
  htmlImageParams: mulmoHtmlImageParamsSchema
279
312
  .extend({
@@ -296,7 +329,7 @@ export const mulmoReferenceSchema = z.object({
296
329
  url: URLStringSchema,
297
330
  title: z.string().optional(),
298
331
  description: z.string().optional(),
299
- type: z.union([z.literal("article"), z.literal("paper"), z.literal("image"), z.literal("video"), z.literal("audio")]).default("article"),
332
+ type: z.enum(["article", "paper", "image", "video", "audio"]).default("article"),
300
333
  });
301
334
  export const mulmoScriptSchema = mulmoPresentationStyleSchema
302
335
  .extend({
@@ -315,6 +348,10 @@ export const mulmoStudioBeatSchema = z
315
348
  .object({
316
349
  hash: z.string().optional(),
317
350
  duration: z.number().optional(),
351
+ startAt: z.number().optional(),
352
+ audioDuration: z.number().optional(),
353
+ movieDuration: z.number().optional(),
354
+ silenceDuration: z.number().optional(),
318
355
  audioFile: z.string().optional(),
319
356
  imageFile: z.string().optional(), // path to the image
320
357
  movieFile: z.string().optional(), // path to the movie file
@@ -1,4 +1,4 @@
1
- import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualDataSchema, speakerDictionarySchema, mulmoImageParamsSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoMovieParamsSchema, mulmoSpeechParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoScriptTemplateSchema, mulmoScriptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoMermaidMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mulmoSessionStateSchema } from "./schema.js";
1
+ import { langSchema, localizedTextSchema, mulmoBeatSchema, mulmoScriptSchema, mulmoStudioSchema, mulmoStudioBeatSchema, mulmoStoryboardSchema, mulmoStoryboardSceneSchema, mulmoStudioMultiLingualSchema, mulmoStudioMultiLingualDataSchema, speakerDictionarySchema, mulmoImageParamsSchema, mulmoImageParamsImagesSchema, mulmoFillOptionSchema, mulmoMovieParamsSchema, mulmoSpeechParamsSchema, textSlideParamsSchema, speechOptionsSchema, speakerDataSchema, mulmoCanvasDimensionSchema, mulmoScriptTemplateSchema, mulmoScriptTemplateFileSchema, text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoPresentationStyleSchema, multiLingualTextsSchema, mulmoMermaidMediaSchema, mulmoTextSlideMediaSchema, mulmoMarkdownMediaSchema, mulmoImageMediaSchema, mulmoChartMediaSchema, mediaSourceSchema, mulmoSessionStateSchema, mulmoOpenAIImageModelSchema, mulmoGoogleImageModelSchema, mulmoGoogleMovieModelSchema, mulmoReplicateMovieModelSchema } from "./schema.js";
2
2
  import { pdf_modes, pdf_sizes, storyToScriptGenerateMode } from "../utils/const.js";
3
3
  import { LLM } from "../utils/utils.js";
4
4
  import { z } from "zod";
@@ -31,6 +31,10 @@ export type MulmoStudioMultiLingual = z.infer<typeof mulmoStudioMultiLingualSche
31
31
  export type MulmoStudioMultiLingualData = z.infer<typeof mulmoStudioMultiLingualDataSchema>;
32
32
  export type MultiLingualTexts = z.infer<typeof multiLingualTextsSchema>;
33
33
  export type MulmoMovieParams = z.infer<typeof mulmoMovieParamsSchema>;
34
+ export type MulmoOpenAIImageModel = z.infer<typeof mulmoOpenAIImageModelSchema>;
35
+ export type MulmoGoogleImageModel = z.infer<typeof mulmoGoogleImageModelSchema>;
36
+ export type MulmoGoogleMovieModel = z.infer<typeof mulmoGoogleMovieModelSchema>;
37
+ export type MulmoReplicateMovieModel = z.infer<typeof mulmoReplicateMovieModelSchema>;
34
38
  export type MulmoTextSlideMedia = z.infer<typeof mulmoTextSlideMediaSchema>;
35
39
  export type MulmoMarkdownMedia = z.infer<typeof mulmoMarkdownMediaSchema>;
36
40
  export type MulmoImageMedia = z.infer<typeof mulmoImageMediaSchema>;
@@ -74,7 +78,6 @@ export type ImageProcessorParams = {
74
78
  export type PDFMode = (typeof pdf_modes)[number];
75
79
  export type PDFSize = (typeof pdf_sizes)[number];
76
80
  export type Text2ImageAgentInfo = {
77
- provider: Text2ImageProvider;
78
81
  agent: string;
79
82
  imageParams: MulmoImageParams;
80
83
  };
@@ -7,7 +7,11 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
7
7
  studio: {
8
8
  beats: {
9
9
  duration?: number | undefined;
10
+ startAt?: number | undefined;
10
11
  hash?: string | undefined;
12
+ audioDuration?: number | undefined;
13
+ movieDuration?: number | undefined;
14
+ silenceDuration?: number | undefined;
11
15
  audioFile?: string | undefined;
12
16
  imageFile?: string | undefined;
13
17
  movieFile?: string | undefined;
@@ -149,6 +153,9 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
149
153
  } | {
150
154
  type: "beat";
151
155
  id?: string | undefined;
156
+ } | {
157
+ type: "voice_over";
158
+ startAt?: number | undefined;
152
159
  } | {
153
160
  type: "movie";
154
161
  source: {
@@ -188,6 +195,7 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
188
195
  } | undefined;
189
196
  description?: string | undefined;
190
197
  imageParams?: {
198
+ provider: "openai" | "google";
191
199
  style?: string | undefined;
192
200
  model?: string | undefined;
193
201
  moderation?: string | undefined;
@@ -212,9 +220,10 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
212
220
  padding?: number | undefined;
213
221
  } | undefined;
214
222
  movieParams?: {
215
- fillOption: {
223
+ speed?: number | undefined;
224
+ fillOption?: {
216
225
  style: "aspectFit" | "aspectFill";
217
- };
226
+ } | undefined;
218
227
  } | undefined;
219
228
  htmlImageParams?: {
220
229
  model?: string | undefined;
@@ -262,7 +271,7 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
262
271
  }> | undefined;
263
272
  } | undefined;
264
273
  movieParams?: {
265
- provider?: "openai" | "google" | "replicate" | undefined;
274
+ provider?: "google" | "replicate" | undefined;
266
275
  model?: string | undefined;
267
276
  fillOption?: {
268
277
  style: "aspectFit" | "aspectFill";
@@ -380,7 +389,7 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
380
389
  }> | undefined;
381
390
  } | undefined;
382
391
  movieParams?: {
383
- provider?: "openai" | "google" | "replicate" | undefined;
392
+ provider?: "google" | "replicate" | undefined;
384
393
  model?: string | undefined;
385
394
  fillOption?: {
386
395
  style: "aspectFit" | "aspectFill";
package/lib/utils/file.js CHANGED
@@ -81,10 +81,18 @@ export const getOutputVideoFilePath = (outDirPath, fileName, lang, caption) => {
81
81
  export const imageSuffix = "p";
82
82
  export const getBeatPngImagePath = (context, index) => {
83
83
  const imageProjectDirPath = MulmoStudioContextMethods.getImageProjectDirPath(context);
84
+ const beat = context.studio.script.beats[index]; // beat could be undefined only in a test case.
85
+ if (beat?.id) {
86
+ return `${imageProjectDirPath}/${beat.id}.png`;
87
+ }
84
88
  return `${imageProjectDirPath}/${index}${imageSuffix}.png`;
85
89
  };
86
90
  export const getBeatMoviePath = (context, index) => {
87
91
  const imageProjectDirPath = MulmoStudioContextMethods.getImageProjectDirPath(context);
92
+ const beat = context.studio.script.beats[index]; // beat could be undefined only in a test case.
93
+ if (beat?.id) {
94
+ return `${imageProjectDirPath}/${beat.id}.mov`;
95
+ }
88
96
  return `${imageProjectDirPath}/${index}.mov`;
89
97
  };
90
98
  export const getReferenceImagePath = (context, key, extension) => {
@@ -6,4 +6,5 @@ import * as pluginHtmlTailwind from "./html_tailwind.js";
6
6
  import * as pluginImage from "./image.js";
7
7
  import * as pluginMovie from "./movie.js";
8
8
  import * as pluginBeat from "./beat.js";
9
- export declare const findImagePlugin: (imageType?: string) => typeof pluginTextSlide | typeof pluginMarkdown | typeof pluginChart | typeof pluginMermaid | typeof pluginHtmlTailwind | typeof pluginImage | typeof pluginMovie | typeof pluginBeat | undefined;
9
+ import * as pluginVoiceOver from "./voice_over.js";
10
+ export declare const findImagePlugin: (imageType?: string) => typeof pluginTextSlide | typeof pluginMarkdown | typeof pluginChart | typeof pluginMermaid | typeof pluginHtmlTailwind | typeof pluginImage | typeof pluginMovie | typeof pluginBeat | typeof pluginVoiceOver | undefined;
@@ -6,7 +6,8 @@ import * as pluginHtmlTailwind from "./html_tailwind.js";
6
6
  import * as pluginImage from "./image.js";
7
7
  import * as pluginMovie from "./movie.js";
8
8
  import * as pluginBeat from "./beat.js";
9
- const imagePlugins = [pluginTextSlide, pluginMarkdown, pluginImage, pluginChart, pluginMermaid, pluginMovie, pluginHtmlTailwind, pluginBeat];
9
+ import * as pluginVoiceOver from "./voice_over.js";
10
+ const imagePlugins = [pluginTextSlide, pluginMarkdown, pluginImage, pluginChart, pluginMermaid, pluginMovie, pluginHtmlTailwind, pluginBeat, pluginVoiceOver];
10
11
  export const findImagePlugin = (imageType) => {
11
12
  return imagePlugins.find((plugin) => plugin.imageType === imageType);
12
13
  };
@@ -0,0 +1,5 @@
1
+ import { ImageProcessorParams } from "../../types/index.js";
2
+ export declare const imageType = "voice_over";
3
+ export declare const processBeatReference: (__: ImageProcessorParams) => Promise<undefined>;
4
+ export declare const process: (__: ImageProcessorParams) => Promise<undefined>;
5
+ export declare const path: (__: ImageProcessorParams) => undefined;
@@ -0,0 +1,9 @@
1
+ export const imageType = "voice_over";
2
+ export const processBeatReference = async (__) => {
3
+ // For voice-over, return undefined to indicate no image should be generated
4
+ return undefined;
5
+ };
6
+ export const process = processBeatReference;
7
+ export const path = (__) => {
8
+ return undefined;
9
+ };
@@ -2,7 +2,11 @@ import { MulmoStudio, MulmoScript } from "../types/index.js";
2
2
  export declare const createOrUpdateStudioData: (_mulmoScript: MulmoScript, currentStudio: MulmoStudio | undefined, fileName: string, videoCaption?: string) => {
3
3
  beats: {
4
4
  duration?: number | undefined;
5
+ startAt?: number | undefined;
5
6
  hash?: string | undefined;
7
+ audioDuration?: number | undefined;
8
+ movieDuration?: number | undefined;
9
+ silenceDuration?: number | undefined;
6
10
  audioFile?: string | undefined;
7
11
  imageFile?: string | undefined;
8
12
  movieFile?: string | undefined;
@@ -144,6 +148,9 @@ export declare const createOrUpdateStudioData: (_mulmoScript: MulmoScript, curre
144
148
  } | {
145
149
  type: "beat";
146
150
  id?: string | undefined;
151
+ } | {
152
+ type: "voice_over";
153
+ startAt?: number | undefined;
147
154
  } | {
148
155
  type: "movie";
149
156
  source: {
@@ -183,6 +190,7 @@ export declare const createOrUpdateStudioData: (_mulmoScript: MulmoScript, curre
183
190
  } | undefined;
184
191
  description?: string | undefined;
185
192
  imageParams?: {
193
+ provider: "openai" | "google";
186
194
  style?: string | undefined;
187
195
  model?: string | undefined;
188
196
  moderation?: string | undefined;
@@ -207,9 +215,10 @@ export declare const createOrUpdateStudioData: (_mulmoScript: MulmoScript, curre
207
215
  padding?: number | undefined;
208
216
  } | undefined;
209
217
  movieParams?: {
210
- fillOption: {
218
+ speed?: number | undefined;
219
+ fillOption?: {
211
220
  style: "aspectFit" | "aspectFill";
212
- };
221
+ } | undefined;
213
222
  } | undefined;
214
223
  htmlImageParams?: {
215
224
  model?: string | undefined;
@@ -257,7 +266,7 @@ export declare const createOrUpdateStudioData: (_mulmoScript: MulmoScript, curre
257
266
  }> | undefined;
258
267
  } | undefined;
259
268
  movieParams?: {
260
- provider?: "openai" | "google" | "replicate" | undefined;
269
+ provider?: "google" | "replicate" | undefined;
261
270
  model?: string | undefined;
262
271
  fillOption?: {
263
272
  style: "aspectFit" | "aspectFill";
@@ -19,3 +19,4 @@ export declare const localizedText: (beat: MulmoBeat, multiLingualData?: MulmoSt
19
19
  export declare const sleep: (milliseconds: number) => Promise<unknown>;
20
20
  export declare function userAssert(condition: boolean, message: string): asserts condition;
21
21
  export declare const settings2GraphAIConfig: (settings?: Record<string, string>) => ConfigDataDictionary<DefaultConfigData>;
22
+ export declare const getExtention: (contentType: string | null, url: string) => string;
@@ -102,3 +102,17 @@ export const settings2GraphAIConfig = (settings) => {
102
102
  }
103
103
  return config;
104
104
  };
105
+ export const getExtention = (contentType, url) => {
106
+ if (contentType?.includes("jpeg") || contentType?.includes("jpg")) {
107
+ return "jpg";
108
+ }
109
+ else if (contentType?.includes("png")) {
110
+ return "png";
111
+ }
112
+ // Fall back to URL extension
113
+ const urlExtension = url.split(".").pop()?.toLowerCase();
114
+ if (urlExtension && ["jpg", "jpeg", "png"].includes(urlExtension)) {
115
+ return urlExtension === "jpeg" ? "jpg" : urlExtension;
116
+ }
117
+ return "png"; // default
118
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "0.0.28",
3
+ "version": "0.1.1",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -60,47 +60,47 @@
60
60
  "homepage": "https://github.com/receptron/mulmocast-cli#readme",
61
61
  "dependencies": {
62
62
  "@google-cloud/text-to-speech": "^6.1.0",
63
- "@graphai/anthropic_agent": "^2.0.2",
63
+ "@graphai/anthropic_agent": "^2.0.5",
64
64
  "@graphai/browserless_agent": "^2.0.1",
65
65
  "@graphai/gemini_agent": "^2.0.0",
66
66
  "@graphai/groq_agent": "^2.0.0",
67
67
  "@graphai/input_agents": "^1.0.1",
68
68
  "@graphai/openai_agent": "^2.0.3",
69
69
  "@graphai/stream_agent_filter": "^2.0.2",
70
- "@graphai/vanilla": "^2.0.4",
70
+ "@graphai/vanilla": "^2.0.5",
71
71
  "@graphai/vanilla_node_agents": "^2.0.1",
72
72
  "@modelcontextprotocol/sdk": "^1.13.1",
73
- "@tavily/core": "^0.5.8",
73
+ "@tavily/core": "^0.5.9",
74
74
  "canvas": "^3.1.2",
75
75
  "clipboardy": "^4.0.0",
76
- "dotenv": "^17.0.0",
76
+ "dotenv": "^17.0.1",
77
77
  "fluent-ffmpeg": "^2.1.3",
78
78
  "google-auth-library": "^9.15.1",
79
- "graphai": "^2.0.9",
80
- "inquirer": "^12.6.3",
79
+ "graphai": "^2.0.11",
80
+ "inquirer": "^12.7.0",
81
81
  "marked": "^16.0.0",
82
82
  "ora": "^8.2.0",
83
- "puppeteer": "^24.11.1",
83
+ "puppeteer": "^24.11.2",
84
84
  "replicate": "^1.0.1",
85
85
  "yaml": "^2.8.0",
86
86
  "yargs": "^18.0.0",
87
- "zod": "^3.25.67",
87
+ "zod": "^3.25.74",
88
88
  "zod-to-json-schema": "^3.24.6"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@anatine/zod-mock": "^3.14.0",
92
- "@faker-js/faker": "^9.8.0",
92
+ "@faker-js/faker": "^9.9.0",
93
93
  "@receptron/test_utils": "^2.0.0",
94
94
  "@types/fluent-ffmpeg": "^2.1.26",
95
95
  "@types/yargs": "^17.0.33",
96
- "eslint": "^9.30.0",
96
+ "eslint": "^9.30.1",
97
97
  "eslint-config-prettier": "^10.1.5",
98
98
  "eslint-plugin-prettier": "^5.5.1",
99
99
  "prettier": "^3.6.2",
100
100
  "ts-node": "^10.9.2",
101
101
  "tsx": "^4.20.3",
102
102
  "typescript": "^5.7.3",
103
- "typescript-eslint": "^8.35.0"
103
+ "typescript-eslint": "^8.35.1"
104
104
  },
105
105
  "engines": {
106
106
  "node": ">=18.0.0"
@@ -0,0 +1,60 @@
1
+ {
2
+ "$mulmocast": {
3
+ "version": "1.0"
4
+ },
5
+ "title": "Voice Over Test",
6
+ "captionParams": {
7
+ "lang": "en"
8
+ },
9
+ "canvasSize": {
10
+ "width": 1552,
11
+ "height": 2064
12
+ },
13
+ "beats": [
14
+ {
15
+ "text": "Description of this section of the movie",
16
+ "image": {
17
+ "type": "movie",
18
+ "source": {
19
+ "kind": "url",
20
+ "url": "https://github.com/receptron/mulmocast-media/raw/refs/heads/main/movies/actions.mp4"
21
+ }
22
+ }
23
+ },
24
+ {
25
+ "text": "Description of this section of the movie starting at 8 seconds",
26
+ "image": {
27
+ "type": "voice_over",
28
+ "startAt": 8.0
29
+ }
30
+ },
31
+ {
32
+ "text": "Description of this section of the movie starting at 14.5 seconds",
33
+ "image": {
34
+ "type": "voice_over",
35
+ "startAt": 14.5
36
+ }
37
+ },
38
+ {
39
+ "text": "Description of this section of the movie starting at 21 seconds",
40
+ "image": {
41
+ "type": "voice_over",
42
+ "startAt": 21.0
43
+ }
44
+ },
45
+ {
46
+ "text": "Description of this section of the movie starting at 25 seconds",
47
+ "image": {
48
+ "type": "voice_over",
49
+ "startAt": 25.0
50
+ }
51
+ },
52
+ {
53
+ "text": "Description of this section of the movie starting at 30 seconds",
54
+ "image": {
55
+ "type": "voice_over",
56
+ "startAt": 30.0
57
+ }
58
+ }
59
+ ]
60
+ }