|
61 | 61 | let installed: boolean | null = null; |
62 | 62 | let loadingModel = false; |
63 | 63 | let running = false; |
| 64 | + let rewritingCaption = false; |
64 | 65 | let status = 'Ready'; |
65 | 66 | let warningStatus = ''; |
66 | 67 | let errorStatus = ''; |
|
230 | 231 | } |
231 | 232 |
|
232 | 233 | function setDuration(value: number) { |
233 | | - duration = Math.max(1, Number.isFinite(value) ? value : 1); |
| 234 | + const minimum = selected?.family === 'ace_step' ? -1 : 1; |
| 235 | + duration = Math.max(minimum, Number.isFinite(value) ? value : minimum); |
234 | 236 | if (selected?.family === 'minimax_h3') { |
235 | 237 | advancedValues = { ...advancedValues, num_frames: miniMaxFramesForDuration(duration) }; |
236 | 238 | } |
|
386 | 388 | modelMatchesSelectedPackage(model, selected)); |
387 | 389 | $: isFireRedAudioEdit = selected?.id === 'firered-audio-semantic-edit' || |
388 | 390 | selected?.id === 'firered-audio-acoustic-edit'; |
| 391 | + $: allowsAutoDuration = selected?.family === 'ace_step'; |
389 | 392 | $: usesDurationSecOption = |
390 | 393 | selected?.family === 'controlfoley' || |
391 | 394 | selected?.family === 'midashenglm_gen'; |
|
1220 | 1223 | return { ...defaults, ...advancedValues, ...raw }; |
1221 | 1224 | } |
1222 | 1225 |
|
| 1226 | + function base64Text(value: string): string { |
| 1227 | + const binary = atob(value); |
| 1228 | + const bytes = new Uint8Array(binary.length); |
| 1229 | + for (let index = 0; index < binary.length; index += 1) { |
| 1230 | + bytes[index] = binary.charCodeAt(index); |
| 1231 | + } |
| 1232 | + return new TextDecoder().decode(bytes); |
| 1233 | + } |
| 1234 | +
|
| 1235 | + function acePlanFromResult(result: Record<string, unknown>): Record<string, unknown> { |
| 1236 | + if (Array.isArray(result.artifacts)) { |
| 1237 | + const artifact = result.artifacts.find((entry): entry is { id: string; payload: string } => |
| 1238 | + typeof entry === 'object' && entry !== null && |
| 1239 | + (entry as { id?: unknown }).id === 'ace_step_caption_plan' && |
| 1240 | + typeof (entry as { payload?: unknown }).payload === 'string'); |
| 1241 | + if (artifact) return JSON.parse(base64Text(artifact.payload)); |
| 1242 | + } |
| 1243 | + if (typeof result.text === 'string') return { caption: result.text }; |
| 1244 | + return {}; |
| 1245 | + } |
| 1246 | +
|
| 1247 | + async function rewriteAceCaption() { |
| 1248 | + if (selected?.family !== 'ace_step' || running || rewritingCaption) return; |
| 1249 | + if (!text.trim() && !lyrics.trim()) { |
| 1250 | + status = 'Enter a caption or lyrics to rewrite.'; |
| 1251 | + warningStatus = status; |
| 1252 | + errorStatus = ''; |
| 1253 | + return; |
| 1254 | + } |
| 1255 | + rewritingCaption = true; |
| 1256 | + warningStatus = ''; |
| 1257 | + errorStatus = ''; |
| 1258 | + status = tr('request.rewritingCaption'); |
| 1259 | + try { |
| 1260 | + await ensureLoaded(); |
| 1261 | + const options = { ...requestOptions(), rewrite_caption: true }; |
| 1262 | + const request: Record<string, unknown> = { |
| 1263 | + text, |
| 1264 | + seed: resolveRequestSeed(seed), |
| 1265 | + duration_seconds: duration, |
| 1266 | + options |
| 1267 | + }; |
| 1268 | + if (language.trim()) request.language = language; |
| 1269 | + if (lyrics.trim()) request.lyrics = lyrics; |
| 1270 | + const result = await runTask({ model: selected.id, request }); |
| 1271 | + const plan = acePlanFromResult(result); |
| 1272 | + if (typeof plan.caption === 'string' && plan.caption.trim()) text = plan.caption; |
| 1273 | + if (typeof plan.language === 'string' && plan.language.trim()) language = plan.language; |
| 1274 | + if (typeof plan.duration_seconds === 'number' && Number.isFinite(plan.duration_seconds) && plan.duration_seconds > 0) { |
| 1275 | + duration = plan.duration_seconds; |
| 1276 | + } |
| 1277 | + const nextAdvanced = { ...advancedValues }; |
| 1278 | + if (typeof plan.bpm === 'number' && Number.isFinite(plan.bpm)) nextAdvanced.bpm = plan.bpm; |
| 1279 | + if (typeof plan.keyscale === 'string') nextAdvanced.keyscale = plan.keyscale; |
| 1280 | + if (typeof plan.timesignature === 'string') nextAdvanced.timesignature = plan.timesignature; |
| 1281 | + advancedValues = nextAdvanced; |
| 1282 | + outputText = typeof result.text === 'string' ? result.text : ''; |
| 1283 | + outputJson = JSON.stringify(plan, null, 2); |
| 1284 | + status = 'Caption rewritten.'; |
| 1285 | + } catch (error) { |
| 1286 | + status = error instanceof Error ? error.message : String(error); |
| 1287 | + errorStatus = status; |
| 1288 | + log(`Caption rewrite failed: ${status}`); |
| 1289 | + } finally { |
| 1290 | + rewritingCaption = false; |
| 1291 | + } |
| 1292 | + } |
| 1293 | +
|
1223 | 1294 | function clearOutput() { |
1224 | 1295 | for (const output of outputAudio) URL.revokeObjectURL(output.url); |
1225 | 1296 | outputAudio = []; |
|
2112 | 2183 | <label for="lyrics">{tr('request.lyrics')} <span>{lyricsRequired ? tr('voice.required') : tr('request.optional')}</span></label> |
2113 | 2184 | <textarea id="lyrics" rows="3" bind:value={lyrics} required={lyricsRequired} |
2114 | 2185 | aria-required={lyricsRequired} placeholder="[Verse]…"></textarea> |
| 2186 | + {#if selected.family === 'ace_step'} |
| 2187 | + <div class="media-actions"> |
| 2188 | + <button type="button" disabled={running || rewritingCaption || (!text.trim() && !lyrics.trim())} |
| 2189 | + on:click={rewriteAceCaption}> |
| 2190 | + {rewritingCaption ? tr('request.rewritingCaption') : tr('request.rewriteCaption')} |
| 2191 | + </button> |
| 2192 | + </div> |
| 2193 | + {/if} |
2115 | 2194 | {/if} |
2116 | 2195 |
|
2117 | 2196 | {#if selected.task === 'asr'} |
|
2147 | 2226 | {#if selected.task === 'gen'} |
2148 | 2227 | <div> |
2149 | 2228 | <label for="duration">{tr('request.duration')}</label> |
2150 | | - <input id="duration" type="number" min="1" step="0.1" value={duration} |
| 2229 | + <input id="duration" type="number" min={allowsAutoDuration ? -1 : 1} step="0.1" value={duration} |
2151 | 2230 | on:input={(event) => setDuration(event.currentTarget.valueAsNumber)} /> |
| 2231 | + {#if allowsAutoDuration} |
| 2232 | + <small>{tr('request.autoDuration')}</small> |
| 2233 | + {/if} |
2152 | 2234 | {#if selected.family === 'minimax_h3'} |
2153 | 2235 | <small>{tr('request.minimaxFrames', { frames: Number(advancedValues.num_frames || 0) })}</small> |
2154 | 2236 | {/if} |
|
0 commit comments