@effectnode/media 0.10.0 → 0.11.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.
@@ -0,0 +1,20 @@
1
+ This LTX Prompting Guide outlines the exact structural brief required to control LTX video generation models (including LTX-2.3 and LTX-2.5). LTX is highly prompt-sensitive and requires literal, chronological, and cinematic instructions formatted as a single continuous paragraph under 200 words.
2
+
3
+
4
+ Core Prompt Structure
5
+ To generate professional-grade clips, construct your prompt like a cinematographer's shot list using these six core elements in sequential order:
6
+ Establish the Shot: Open with the camera framing, lens type, and cinematography terms matching the genre (e.g., Close-up shot, Wide anamorphic lens, Overhead shot).
7
+ Define the Characters: Detail age, hairstyle, clothing, and posture. Express emotions through concrete physical cues (e.g., trembling hands) rather than abstract emotional labels.
8
+ Describe the Action: Write the main action as a natural sequence of present-tense verbs flowing chronologically from beginning to end.
9
+ Set the Scene: Detail the environmental background, surface textures, lighting conditions, and color palette to establish mood.
10
+ Identify Camera Movements: Specify how the camera moves (e.g., dolly in, jib up, pan left). Describing how the subject looks after the movement helps the model complete the motion accurately.
11
+ Describe the Audio: LTX models generate synchronized native audio. Describe ambient sounds, music, or speech. Put spoken dialogue inside "quotation marks" and specify languages or accents if needed.
12
+
13
+
14
+
15
+ Golden Rules for LTX Prompting
16
+ Write a Single Paragraph: Do not use bullet points or line breaks inside your final prompt; merge all elements into one fluid paragraph.
17
+ Keep it Under 200 Words: Excessively long descriptions dilute the model's attention span and compromise prompt adherence.
18
+ Be Literal and Precise: Avoid poetic writing or abstract metaphors. State exactly what should be rendered on screen.
19
+ Use Hierarchical Structuring: If using an LLM to expand your prompt, structure the data strictly by Shot Type ➔ Subject ➔ Action ➔ Camera Movement ➔ Lighting ➔ Mood.
20
+ Leverage Negative Space: Explicitly describe what is absent if you need a clean shot (e.g., "clean composition with minimal background elements, no distracting motion").
@@ -575,7 +575,15 @@ async function installPythonDependencies() {
575
575
  }
576
576
  const dotsTtsFolder = join(pythonAppSrcDir, "dots-tts-mlx");
577
577
  if (!existsSync(dotsTtsFolder)) {
578
- const cloneCMD = await runCommand("git", ["clone", "https://github.com/sb1992/dots-tts-mlx.git", "dots-tts-mlx"], { cwd: pythonAppSrcDir });
578
+ const cloneCMD = await runCommand("git", [
579
+ "clone",
580
+ "--depth",
581
+ "1",
582
+ "--branch",
583
+ "v0.2.0",
584
+ "https://github.com/sb1992/dots-tts-mlx",
585
+ "dots-tts-mlx",
586
+ ], { cwd: pythonAppSrcDir });
579
587
  if (!cloneCMD.success) {
580
588
  console.error("Failed to clone dots-tts-mlx:", cloneCMD.error);
581
589
  return false;
@@ -224,40 +224,27 @@ async function getDotsTtsBin() {
224
224
  }
225
225
  return "dots-tts";
226
226
  }
227
- /** Resolve the `mlx_whisper` executable installed via `uv tool install mlx-whisper`. */
228
- async function getMlxWhisperBin() {
229
- const candidates = [
230
- join(homedir(), ".local", "bin", "mlx_whisper"),
231
- "/opt/homebrew/bin/mlx_whisper",
232
- "/usr/local/bin/mlx_whisper",
233
- ];
234
- for (const p of candidates) {
235
- if (existsSync(p))
236
- return p;
237
- }
238
- return "mlx_whisper";
239
- }
240
227
  /** The cloned dots-tts-mlx project directory. */
241
228
  const DOTS_TTS_FOLDER = join(APP_DATA_DIR, "python-src", "dots-tts-mlx");
242
229
  /** Base directory where dots-tts MLX weights live (inside the project folder). */
243
230
  const DOTS_TTS_WEIGHTS_DIR = join(DOTS_TTS_FOLDER, "dots-tts-mlx-weights");
244
231
  /**
245
- * Resolve a dots-tts `--model` value. Accepts `./dots-tts-mlx-weights/mf-int4`
246
- * (relative to the dots-tts-mlx folder), a bare variant (`mf-int4`), or an
232
+ * Resolve a dots-tts `--model` value. Accepts `./dots-tts-mlx-weights/int4`
233
+ * (relative to the dots-tts-mlx folder), a bare variant (`int4`), or an
247
234
  * explicit path.
248
235
  */
249
236
  function resolveDotsTtsModel(model) {
250
237
  if (!model)
251
- return join(DOTS_TTS_WEIGHTS_DIR, "mf-int4");
238
+ return join(DOTS_TTS_WEIGHTS_DIR, "int4");
252
239
  if (model.startsWith("./"))
253
240
  return join(DOTS_TTS_FOLDER, model.slice(2));
254
241
  if (model.includes("/"))
255
242
  return model;
256
243
  return join(DOTS_TTS_WEIGHTS_DIR, model);
257
244
  }
258
- /** True when the dots-tts `mf-int4` weights have been downloaded. */
245
+ /** True when the dots-tts `int4` weights have been downloaded. */
259
246
  function isDotsTtsModelDownloaded() {
260
- return existsSync(join(DOTS_TTS_WEIGHTS_DIR, "mf-int4"));
247
+ return existsSync(join(DOTS_TTS_WEIGHTS_DIR, "int4"));
261
248
  }
262
249
  /** Resolve the ffmpeg binary installed via Homebrew (Apple Silicon then Intel). */
263
250
  async function getFfmpegBin() {
@@ -979,8 +966,7 @@ export async function generateAdvancedVoiceClone(projectId, params, onLog) {
979
966
  .replace(/[^a-zA-Z0-9_-]/g, "_")
980
967
  .slice(0, 64) || "voice";
981
968
  const model = resolveDotsTtsModel(String(params.model || ""));
982
- // dots-tts and mlx_whisper both expect a WAV reference — convert mp3/other
983
- // formats to 16-bit PCM WAV first.
969
+ // dots-tts expects a WAV reference — convert mp3/other formats to 16-bit PCM WAV.
984
970
  let refAudio = resolvedRef;
985
971
  if (!resolvedRef.toLowerCase().endsWith(".wav")) {
986
972
  const tempDir = join(TEMP_DIR, String(projectId));
@@ -1005,19 +991,6 @@ export async function generateAdvancedVoiceClone(projectId, params, onLog) {
1005
991
  }
1006
992
  refAudio = convertedRef;
1007
993
  }
1008
- // dots-tts requires `--ref-text` (the reference audio's transcript). Transcribe
1009
- // the WAV reference with mlx_whisper to supply it.
1010
- const whisperBin = await getMlxWhisperBin();
1011
- const transcribeDir = join(TEMP_DIR, String(projectId));
1012
- ensureDir(transcribeDir);
1013
- const whisperResult = await runCommand([whisperBin, refAudio, "--output-dir", transcribeDir], { onLog });
1014
- const refTxtPath = join(transcribeDir, (refAudio.split(sep).pop() || "ref").replace(/\.[^.]+$/, "") + ".txt");
1015
- const refText = whisperResult.success && existsSync(refTxtPath)
1016
- ? readFileSync(refTxtPath, "utf-8").replace(/\s+/g, " ").trim()
1017
- : "";
1018
- if (!refText) {
1019
- return { error: "Failed to transcribe reference audio (mlx_whisper)" };
1020
- }
1021
994
  const dotsTtsBin = await getDotsTtsBin();
1022
995
  // Mirror the voice-clone tab's timestamped-folder layout so outputs never
1023
996
  // overwrite each other.
@@ -1032,14 +1005,14 @@ export async function generateAdvancedVoiceClone(projectId, params, onLog) {
1032
1005
  cleanText || "please provide text",
1033
1006
  "--ref-audio",
1034
1007
  refAudio,
1035
- "--ref-text",
1036
- refText,
1037
1008
  "--language",
1038
1009
  language,
1039
1010
  "--out-path",
1040
1011
  outDir,
1041
1012
  "--out-prefix",
1042
1013
  prefix,
1014
+ "--max-generate-length",
1015
+ "3000",
1043
1016
  ]);
1044
1017
  const result = await runCommand([
1045
1018
  dotsTtsBin,
@@ -1049,14 +1022,14 @@ export async function generateAdvancedVoiceClone(projectId, params, onLog) {
1049
1022
  cleanText || "please provide text",
1050
1023
  "--ref-audio",
1051
1024
  refAudio,
1052
- "--ref-text",
1053
- refText,
1054
1025
  "--language",
1055
1026
  language,
1056
1027
  "--out-path",
1057
1028
  outDir,
1058
1029
  "--out-prefix",
1059
1030
  prefix,
1031
+ "--max-generate-length",
1032
+ "3000",
1060
1033
  ], { cwd: dirname(dotsTtsBin), onLog });
1061
1034
  if (!result.success) {
1062
1035
  return { error: result.output || "Advanced voice clone failed" };
@@ -1072,7 +1045,6 @@ export async function generateAdvancedVoiceClone(projectId, params, onLog) {
1072
1045
  const meta = {
1073
1046
  id: voiceId,
1074
1047
  transcript: cleanText,
1075
- refText,
1076
1048
  language,
1077
1049
  model,
1078
1050
  refAudioFilename: params.refAudioPath,
@@ -2612,14 +2584,14 @@ export async function renderMediaRoutes({ app, getUvPath, }) {
2612
2584
  ensureDir(DOTS_TTS_FOLDER);
2613
2585
  send("progress", {
2614
2586
  status: "starting",
2615
- label: "Downloading dots-tts model (mf-int4)...",
2587
+ label: "Downloading dots-tts model (int4)...",
2616
2588
  });
2617
2589
  const proc = spawn([
2618
2590
  "hf",
2619
2591
  "download",
2620
2592
  "shraey/dots-tts-mlx",
2621
2593
  "--include",
2622
- "mf-int4/*",
2594
+ "int4/*",
2623
2595
  "--local-dir",
2624
2596
  "./dots-tts-mlx-weights",
2625
2597
  ], { cwd: DOTS_TTS_FOLDER, stdout: "pipe", stderr: "pipe" });
@@ -217,7 +217,7 @@ const DOWNLOAD_MODELS: {
217
217
  },
218
218
  {
219
219
  id: "dots-tts",
220
- name: "shraey/dots-tts-mlx (mf-int4)",
220
+ name: "shraey/dots-tts-mlx (int4)",
221
221
  desc: "Advanced voice clone (dots-tts)",
222
222
  },
223
223
  ];
@@ -264,6 +264,7 @@ export default function UpscaleTab({ projectId }: Props) {
264
264
  <div className="flex flex-wrap gap-2">
265
265
  {[
266
266
  { value: "1x" as UpscaleMode, label: "Refine (1x)" },
267
+ { value: "2x" as UpscaleMode, label: "Refine (2x)" },
267
268
  { value: "1000" as UpscaleMode, label: "Upscale to 1000px" },
268
269
  { value: "1500" as UpscaleMode, label: "Upscale to 1500px" },
269
270
  { value: "2000" as UpscaleMode, label: "Upscale to 2000px" },
@@ -79,7 +79,7 @@ export const useAdvancedVoiceCloneStore = create<AdvancedVoiceCloneStore>(
79
79
  text: "",
80
80
  language: "YUE",
81
81
  outPrefix: "voice",
82
- model: "./dots-tts-mlx-weights/mf-int4",
82
+ model: "./dots-tts-mlx-weights/int4",
83
83
  refAudio: null,
84
84
  audios: [],
85
85
  audiosLoading: false,
@@ -127,9 +127,7 @@ export const useAdvancedVoiceCloneStore = create<AdvancedVoiceCloneStore>(
127
127
  uploading: false,
128
128
  refAudio: {
129
129
  filename: data.filename,
130
- url: resolveUrl(
131
- `/api/files?path=${encodeURIComponent(data.path)}`,
132
- ),
130
+ url: resolveUrl(`/api/files?path=${encodeURIComponent(data.path)}`),
133
131
  },
134
132
  });
135
133
  void get().fetchAudios(projectId);
@@ -193,7 +191,7 @@ export const useAdvancedVoiceCloneStore = create<AdvancedVoiceCloneStore>(
193
191
  text: "",
194
192
  language: "YUE",
195
193
  outPrefix: "voice",
196
- model: "./dots-tts-mlx-weights/mf-int4",
194
+ model: "./dots-tts-mlx-weights/int4",
197
195
  refAudio: null,
198
196
  audios: [],
199
197
  audiosLoading: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectnode/media",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Start a full-stack media app: Vite + React + TypeScript frontend, Express backend with REST + WebSocket API",
5
5
  "license": "MIT",
6
6
  "author": "",