@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.
- package/dist/backend/movie-backend/agent/prompt/ltx.txt +20 -0
- package/dist/backend/movie-backend/core.js +9 -1
- package/dist/backend/movie-backend/render-media.js +12 -40
- package/frontend/src/movie-app/components/EditorTabs/SetupAiModelTab.tsx +1 -1
- package/frontend/src/movie-app/components/EditorTabs/UpscaleTab.tsx +1 -0
- package/frontend/src/movie-app/stores/advancedVoiceCloneStore.ts +3 -5
- package/package.json +1 -1
|
@@ -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", [
|
|
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/
|
|
246
|
-
* (relative to the dots-tts-mlx folder), a bare variant (`
|
|
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, "
|
|
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 `
|
|
245
|
+
/** True when the dots-tts `int4` weights have been downloaded. */
|
|
259
246
|
function isDotsTtsModelDownloaded() {
|
|
260
|
-
return existsSync(join(DOTS_TTS_WEIGHTS_DIR, "
|
|
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
|
|
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 (
|
|
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
|
-
"
|
|
2594
|
+
"int4/*",
|
|
2623
2595
|
"--local-dir",
|
|
2624
2596
|
"./dots-tts-mlx-weights",
|
|
2625
2597
|
], { cwd: DOTS_TTS_FOLDER, stdout: "pipe", stderr: "pipe" });
|
|
@@ -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/
|
|
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/
|
|
194
|
+
model: "./dots-tts-mlx-weights/int4",
|
|
197
195
|
refAudio: null,
|
|
198
196
|
audios: [],
|
|
199
197
|
audiosLoading: false,
|
package/package.json
CHANGED