diff --git a/VERSION b/VERSION
index 085135e..38f8e88 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v0.1
+dev
diff --git a/cdmf_generation.py b/cdmf_generation.py
index a0eeed9..5e07049 100644
--- a/cdmf_generation.py
+++ b/cdmf_generation.py
@@ -400,7 +400,9 @@ def generate():
out_dir = (
request.form.get("out_dir", DEFAULT_OUT_DIR).strip() or DEFAULT_OUT_DIR
)
- basename = request.form.get("basename", "Candy Dreams").strip() or "Candy Dreams"
+ basename = request.form.get("basename", "").strip()
+ if not basename:
+ raise ValueError("Base filename is required and cannot be empty.")
seed_vibe = request.form.get("seed_vibe", "any").strip() or "any"
preset_id = request.form.get("preset_id", "").strip()
@@ -417,6 +419,7 @@ def generate():
# Determine reference-audio path
uploaded_ref = request.files.get("ref_audio_file")
src_audio_path: Optional[str] = None
+ ref_audio_filename: Optional[str] = None
if uploaded_ref and uploaded_ref.filename:
try:
filename = secure_filename(uploaded_ref.filename)
@@ -424,6 +427,7 @@ def generate():
filename = uploaded_ref.filename or ""
if not filename:
filename = f"ref_{int(time.time() * 1000)}.wav"
+ ref_audio_filename = filename # Save original filename
name_root, ext = os.path.splitext(filename)
ext = (ext or "").lower()
@@ -580,7 +584,14 @@ def generate():
"lora_name_or_path", lora_name_or_path
)
entry["lora_weight"] = summary.get("lora_weight", lora_weight)
- entry["generator"] = "ace_step"
+ entry["generator"] = "gen"
+ # Save input file as full path when available
+ if src_audio_path:
+ entry["input_file"] = src_audio_path
+ entry["input_file_path"] = src_audio_path
+ entry["src_audio_path"] = src_audio_path # Keep for backward compatibility
+ elif ref_audio_filename:
+ entry["input_file"] = ref_audio_filename # Fallback: filename only (legacy)
meta[wav_path.name] = entry
cdmf_tracks.save_track_meta(meta)
diff --git a/cdmf_midi_generation_bp.py b/cdmf_midi_generation_bp.py
index ccc5b5e..dead4c9 100644
--- a/cdmf_midi_generation_bp.py
+++ b/cdmf_midi_generation_bp.py
@@ -13,7 +13,7 @@
from werkzeug.utils import secure_filename
import cdmf_tracks
-from cdmf_paths import DEFAULT_OUT_DIR, APP_VERSION
+from cdmf_paths import DEFAULT_OUT_DIR, APP_VERSION, get_next_available_output_path
from cdmf_midi_generation import get_midi_generator
logger = logging.getLogger(__name__)
@@ -88,22 +88,29 @@ def midi_generate():
melodia_trick = request.form.get("melodia_trick", "true").lower() == "true"
midi_tempo = float(request.form.get("midi_tempo", DEFAULT_MIDI_TEMPO))
- # Get output filename
+ # Get output filename (required)
output_filename = request.form.get("output_filename", "").strip()
if not output_filename:
- # Generate default filename from input
- input_basename = Path(filename).stem
- output_filename = f"{input_basename}_midi"
+ return jsonify({
+ "error": True,
+ "message": "Output filename is required and cannot be empty."
+ }), 400
- # Ensure .mid extension
- if not output_filename.lower().endswith('.mid'):
- output_filename = f"{output_filename}.mid"
+ # Ensure .mid extension for stem
+ if output_filename.lower().endswith('.mid'):
+ stem = Path(output_filename).stem
+ else:
+ stem = output_filename
# Get output directory (same as music generation)
out_dir = request.form.get("out_dir", DEFAULT_OUT_DIR)
out_dir_path = Path(out_dir)
out_dir_path.mkdir(parents=True, exist_ok=True)
+ # Resolve path without overwriting existing files (-1, -2, …)
+ output_path = get_next_available_output_path(out_dir_path, stem, ".mid")
+ output_filename = output_path.name
+
# Save uploaded input file temporarily
import tempfile
temp_dir = Path(tempfile.mkdtemp(prefix="aceforge_midi_temp_"))
@@ -111,8 +118,7 @@ def midi_generate():
input_file.save(str(temp_input_path))
try:
- # Generate output path
- output_path = out_dir_path / output_filename
+ # output_path already set above (next-available, no overwrite)
# Perform MIDI generation
logger.info(f"[MIDI Generation] Starting: input={filename}, output={output_path}")
@@ -153,8 +159,9 @@ def midi_generate():
if "favorite" not in entry:
entry["favorite"] = False
entry["created"] = time.time()
- entry["generator"] = "midi_generation"
+ entry["generator"] = "midi"
entry["basename"] = Path(midi_filename).stem
+ # original_file already saved below
entry["onset_threshold"] = onset_threshold
entry["frame_threshold"] = frame_threshold
entry["minimum_note_length_ms"] = minimum_note_length_ms
@@ -164,7 +171,8 @@ def midi_generate():
entry["melodia_trick"] = melodia_trick
entry["midi_tempo"] = midi_tempo
entry["out_dir"] = str(out_dir_path)
- entry["original_file"] = filename
+ entry["original_file"] = str(temp_input_path)
+ entry["input_file"] = str(temp_input_path) # Full path for consistency
track_meta[midi_filename] = entry
cdmf_tracks.save_track_meta(track_meta)
diff --git a/cdmf_paths.py b/cdmf_paths.py
index caa9f03..240027d 100644
--- a/cdmf_paths.py
+++ b/cdmf_paths.py
@@ -128,6 +128,33 @@ def _get_default_output_dir() -> Path:
DEFAULT_OUT_DIR = str(_get_default_output_dir())
+
+def get_next_available_output_path(out_dir: Path | str, base_stem: str, ext: str = ".wav") -> Path:
+ """
+ Return a path under out_dir for the given base name and extension that does not
+ yet exist. If the exact path exists, appends -1, -2, -3, etc. to avoid overwriting.
+ base_stem should not include the extension (e.g. "My Track" not "My Track.wav").
+ """
+ out_dir = Path(out_dir)
+ out_dir.mkdir(parents=True, exist_ok=True)
+ if not ext.startswith("."):
+ ext = "." + ext
+ stem = (base_stem or "").strip()
+ if not stem:
+ stem = "output"
+ # Sanitize: remove path separators
+ stem = stem.replace("/", "_").replace("\\", "_").replace(":", "_")
+ candidate = out_dir / f"{stem}{ext}"
+ if not candidate.exists():
+ return candidate
+ idx = 1
+ while True:
+ candidate = out_dir / f"{stem}-{idx}{ext}"
+ if not candidate.exists():
+ return candidate
+ idx += 1
+
+
# Presets / tracks metadata / user presets
# Keep these in APP_DIR as they're bundled with the application
# User presets go in user data directory
@@ -189,19 +216,49 @@ def _get_custom_lora_root() -> Path:
def get_app_version() -> str:
"""
Read the application version from VERSION file.
- Falls back to 'v0.1' if file doesn't exist or can't be read.
+ Falls back to 'dev' if file doesn't exist or can't be read.
The VERSION file is updated by GitHub Actions during release builds.
+
+ For frozen apps (PyInstaller), checks multiple locations:
+ 1. sys._MEIPASS (PyInstaller temp extraction directory) - primary location
+ 2. APP_DIR (executable directory) - fallback
+ 3. Bundle root (for macOS app bundles) - fallback
"""
- version_file = APP_DIR / "VERSION"
- if version_file.exists():
- try:
- with version_file.open("r", encoding="utf-8") as f:
- version = f.read().strip()
- if version:
- return version
- except Exception as e:
- print(f"[AceForge] Warning: Failed to read VERSION file: {e}", flush=True)
- # Default fallback
- return "v0.1"
+ # Try multiple locations for frozen apps
+ candidates = []
+
+ if getattr(sys, "frozen", False):
+ # For frozen apps, check sys._MEIPASS first (PyInstaller extraction dir)
+ # This is where PyInstaller extracts bundled files during execution
+ if hasattr(sys, "_MEIPASS"):
+ candidates.append(Path(sys._MEIPASS) / "VERSION")
+ # Also check executable directory (MacOS folder)
+ candidates.append(APP_DIR / "VERSION")
+ # For macOS app bundles, also check bundle root (Contents/)
+ if sys.platform == "darwin":
+ # sys.executable is Contents/MacOS/AceForge_bin
+ # APP_DIR is Contents/MacOS/
+ # Bundle root (Contents/) is APP_DIR.parent
+ bundle_root = APP_DIR.parent
+ candidates.append(bundle_root / "VERSION")
+ else:
+ # For development, just check APP_DIR (project root)
+ candidates.append(APP_DIR / "VERSION")
+
+ # Try each candidate location
+ for version_file in candidates:
+ if version_file.exists():
+ try:
+ with version_file.open("r", encoding="utf-8") as f:
+ version = f.read().strip()
+ if version:
+ print(f"[AceForge] Loaded version '{version}' from {version_file}", flush=True)
+ return version
+ except Exception as e:
+ print(f"[AceForge] Warning: Failed to read VERSION file from {version_file}: {e}", flush=True)
+
+ # Default fallback for development builds
+ print(f"[AceForge] No VERSION file found, using default 'dev'", flush=True)
+ return "dev"
APP_VERSION = get_app_version()
diff --git a/cdmf_stem_splitting_bp.py b/cdmf_stem_splitting_bp.py
index e7c175b..f731881 100644
--- a/cdmf_stem_splitting_bp.py
+++ b/cdmf_stem_splitting_bp.py
@@ -84,6 +84,11 @@ def stem_split():
input_basename = Path(filename).stem
# Sanitize basename
input_basename = input_basename.replace("/", "_").replace("\\", "_").replace(":", "_")
+ # Optional base filename prefix from form
+ base_filename = request.form.get("base_filename", "").strip()
+ if base_filename:
+ prefix = base_filename.replace("/", "_").replace("\\", "_").replace(":", "_")
+ input_basename = f"{prefix}_{input_basename}"
# Save uploaded input file temporarily
# Use a temp directory to avoid cluttering the output directory
@@ -151,15 +156,21 @@ def stem_split():
entry["favorite"] = False
entry["seconds"] = dur
entry["created"] = time.time()
- entry["generator"] = "stem_split"
+ entry["generator"] = "stem"
entry["basename"] = Path(stem_filename).stem
+ # original_file already saved below
entry["stem_name"] = stem_name
entry["stem_count"] = stem_count
entry["mode"] = mode or ""
entry["export_format"] = export_format
entry["device_preference"] = device_preference
entry["out_dir"] = str(out_dir_path)
- entry["original_file"] = filename
+ entry["original_file"] = str(temp_input_path)
+ entry["input_file"] = str(temp_input_path) # Full path for consistency
+ # Save base_filename if provided
+ base_filename = request.form.get("base_filename", "").strip()
+ if base_filename:
+ entry["base_filename"] = base_filename
track_meta[stem_filename] = entry
cdmf_tracks.save_track_meta(track_meta)
diff --git a/cdmf_template.py b/cdmf_template.py
index 657cdfe..892624c 100644
--- a/cdmf_template.py
+++ b/cdmf_template.py
@@ -30,7 +30,7 @@
- {{ version or 'v0.1' }}
+ {{ version or 'dev' }}
@@ -142,7 +142,8 @@
-
+
+
Required. Used as the base name for the generated track file (e.g. My Track.wav).
@@ -793,15 +794,16 @@
-
+
+ value="voice_clone_output"
+ required>
- Output filename (without extension). Will be saved as .wav in the output directory.
+ Required. Output filename (without extension). Saved as MP3 in the output directory. If the file already exists, a number is appended (-1, -2, …).
@@ -963,6 +965,19 @@
+
+
+
+
+ Optional. If set, this prefix is added to generated stem filenames (e.g. myprefix_song_stems_vocals.wav).
+
+
+
-
+
+ value=""
+ required>
- Output filename (without extension). Will be saved as .mid in the output directory. If left empty, uses input filename + "_midi".
+ Required. Output filename (without extension). Saved as .mid in the output directory. If the file already exists, a number is appended (-1, -2, …).
diff --git a/cdmf_voice_cloning_bp.py b/cdmf_voice_cloning_bp.py
index c01aff6..7629b8c 100644
--- a/cdmf_voice_cloning_bp.py
+++ b/cdmf_voice_cloning_bp.py
@@ -13,7 +13,7 @@
from werkzeug.utils import secure_filename
import cdmf_tracks
-from cdmf_paths import DEFAULT_OUT_DIR, APP_VERSION
+from cdmf_paths import DEFAULT_OUT_DIR, APP_VERSION, get_next_available_output_path
from cdmf_voice_cloning import get_voice_cloner
logger = logging.getLogger(__name__)
@@ -72,10 +72,13 @@ def voice_clone():
"message": "Invalid file format. Please use MP3, WAV, M4A, or FLAC."
}), 400
- # Get output filename (default MP3 256k for cloned voices)
+ # Get output filename (required)
output_filename = request.form.get("output_filename", "").strip()
if not output_filename:
- output_filename = "voice_clone_output"
+ return jsonify({
+ "error": True,
+ "message": "Output filename is required and cannot be empty."
+ }), 400
if not output_filename.lower().endswith((".wav", ".mp3")):
output_filename += ".mp3"
@@ -84,6 +87,12 @@ def voice_clone():
out_dir_path = Path(out_dir)
out_dir_path.mkdir(parents=True, exist_ok=True)
+ # Resolve stem and extension for next-available path
+ stem = Path(output_filename).stem
+ ext = Path(output_filename).suffix or ".mp3"
+ output_path = get_next_available_output_path(out_dir_path, stem, ext)
+ output_filename = output_path.name
+
# Save uploaded reference audio temporarily
temp_ref_path = out_dir_path / f"_temp_ref_{filename}"
speaker_file.save(str(temp_ref_path))
@@ -100,8 +109,7 @@ def voice_clone():
speed = float(request.form.get("speed", DEFAULT_SPEED))
enable_text_splitting = request.form.get("enable_text_splitting", "true").lower() == "true"
- # Generate output path
- output_path = out_dir_path / output_filename
+ # output_path already set above (next-available, no overwrite)
# Perform voice cloning
logger.info(f"[Voice Cloning] Starting: text='{text[:50]}...', language={language}, output={output_path}")
@@ -143,8 +151,9 @@ def voice_clone():
entry["favorite"] = False
entry["seconds"] = dur
entry["created"] = time.time()
- entry["generator"] = "voice_clone"
+ entry["generator"] = "tts"
entry["basename"] = Path(final_name).stem
+ entry["input_file"] = str(temp_ref_path) # Full path to reference audio
entry["text"] = text
entry["language"] = language
entry["temperature"] = temperature
diff --git a/generate_ace.py b/generate_ace.py
index 5e92a2d..79bd344 100644
--- a/generate_ace.py
+++ b/generate_ace.py
@@ -466,23 +466,9 @@ def _choose_effective_seed(seed: int) -> int:
def _next_available_output_path(out_dir: Path, basename: str, ext: str = ".wav") -> Path:
- out_dir = Path(out_dir)
- out_dir.mkdir(parents=True, exist_ok=True)
-
- stem = Path(basename).stem
- if not ext.startswith("."):
- ext = "." + ext
-
- candidate = out_dir / f"{stem}{ext}"
- if not candidate.exists():
- return candidate
-
- idx = 2
- while True:
- candidate = out_dir / f"{stem}{idx}{ext}"
- if not candidate.exists():
- return candidate
- idx += 1
+ """Use shared helper to avoid overwriting existing files (-1, -2, -3, ...)."""
+ stem = Path(basename).stem if basename else "output"
+ return cdmf_paths.get_next_available_output_path(out_dir, stem, ext)
def _apply_vibe_to_tags(prompt: str, seed_vibe: str) -> str:
diff --git a/static/scripts/cdmf.css b/static/scripts/cdmf.css
index 7b87dc7..2f499ae 100644
--- a/static/scripts/cdmf.css
+++ b/static/scripts/cdmf.css
@@ -699,3 +699,4 @@ p {
#consoleToggleIcon.expanded {
transform: rotate(180deg);
}
+
diff --git a/static/scripts/cdmf_generation_ui.js b/static/scripts/cdmf_generation_ui.js
index ff3b29e..da219c9 100644
--- a/static/scripts/cdmf_generation_ui.js
+++ b/static/scripts/cdmf_generation_ui.js
@@ -429,6 +429,17 @@
return false;
}
+ // Base filename is required
+ const basenameEl = document.getElementById("basename");
+ if (basenameEl && !(basenameEl.value || "").trim()) {
+ if (ev && ev.preventDefault) {
+ ev.preventDefault();
+ }
+ alert("Base filename is required. Please enter a name for the generated track.");
+ basenameEl.focus();
+ return false;
+ }
+
// Sync Audio2Audio flag with the file selector
try {
const form = ev && ev.target ? ev.target : document;
diff --git a/static/scripts/cdmf_midi_generation_ui.js b/static/scripts/cdmf_midi_generation_ui.js
index 71c6695..66f5bf0 100644
--- a/static/scripts/cdmf_midi_generation_ui.js
+++ b/static/scripts/cdmf_midi_generation_ui.js
@@ -23,6 +23,13 @@
CDMF.onSubmitMidiGen = function (event) {
event.preventDefault();
+ var outputFilenameEl = document.getElementById("midi_gen_output_filename");
+ if (outputFilenameEl && !(outputFilenameEl.value || "").trim()) {
+ alert("Output filename is required. Please enter a name for the MIDI file.");
+ outputFilenameEl.focus();
+ return false;
+ }
+
// If basic-pitch model is not ready, trigger download (like ACE-Step "Download Models")
var statusEl = document.getElementById("midiGenModelStatusNotice");
var downloadBtn = document.getElementById("midiGenDownloadModelsBtn");
@@ -174,6 +181,16 @@
}
setVal("midi_gen_output_filename", settings.basename);
+
+ // Restore input file (show basename from full path)
+ var inputFileName = settings.original_file || settings.input_file;
+ if (inputFileName && typeof inputFileName === "string") {
+ var inputFileEl = document.getElementById("midi_gen_input_file");
+ if (inputFileEl && typeof CDMF.restoreFileInput === "function") {
+ CDMF.restoreFileInput(inputFileEl, inputFileName);
+ }
+ }
+
setNumPair("midi_gen_onset_threshold", "midi_gen_onset_threshold_range", settings.onset_threshold);
setNumPair("midi_gen_frame_threshold", "midi_gen_frame_threshold_range", settings.frame_threshold);
setNumPair("midi_gen_minimum_note_length_ms", "midi_gen_minimum_note_length_ms_range", settings.minimum_note_length_ms);
diff --git a/static/scripts/cdmf_presets_ui.js b/static/scripts/cdmf_presets_ui.js
index b515a44..dcf973a 100644
--- a/static/scripts/cdmf_presets_ui.js
+++ b/static/scripts/cdmf_presets_ui.js
@@ -169,6 +169,62 @@
};
}
+ // ---------------------------------------------------------------------------
+ // Helper: Restore file input from path using DataTransfer API
+ // Only restores if file is accessible, otherwise does nothing
+ // ---------------------------------------------------------------------------
+
+ CDMF.restoreFileInput = function(fileInput, filePath) {
+ if (!fileInput || !filePath || typeof filePath !== "string") {
+ return;
+ }
+
+ // Extract basename from path (handle both / and \ separators)
+ var basename = filePath.split(/[/\\]/).pop() || filePath;
+
+ // Try to fetch the file and restore it
+ // For files in output directory, try to serve via /music/
+ // For other paths, try direct fetch (may fail due to CORS/file:// restrictions)
+ var url = filePath;
+
+ // If path looks like it's in the output directory, try /music/
+ // This is a heuristic - we check if the path ends with the basename
+ if (filePath.includes(basename)) {
+ // Try serving via /music endpoint if it's in output directory
+ url = "/music/" + encodeURIComponent(basename);
+ }
+
+ // Try to fetch and restore the file
+ fetch(url)
+ .then(function(response) {
+ if (!response.ok) {
+ throw new Error("File not accessible");
+ }
+ return response.blob();
+ })
+ .then(function(blob) {
+ // Create a File object from the blob
+ var file = new File([blob], basename, {
+ type: blob.type || "application/octet-stream",
+ lastModified: new Date()
+ });
+
+ // Use DataTransfer to set the file
+ var dataTransfer = new DataTransfer();
+ dataTransfer.items.add(file);
+ fileInput.files = dataTransfer.files;
+
+ // Set data-file attribute for Safari compatibility
+ if (fileInput.webkitEntries && fileInput.webkitEntries.length) {
+ fileInput.setAttribute("data-file", basename);
+ }
+ })
+ .catch(function() {
+ // File doesn't exist or isn't accessible - do nothing
+ // (e.g., deleted temp files from uploads)
+ });
+ };
+
// ---------------------------------------------------------------------------
// Apply settings → form (used by presets + tracks)
// ---------------------------------------------------------------------------
@@ -177,7 +233,7 @@
if (!settings) return;
// Voice Clone tracks: switch to Voice Clone tab and fill that form
- if (settings.generator === "voice_clone") {
+ if (settings.generator === "tts" || settings.generator === "voice_clone") {
if (typeof CDMF.applyVoiceCloneSettingsToForm === "function") {
CDMF.applyVoiceCloneSettingsToForm(settings);
return;
@@ -185,13 +241,21 @@
}
// Stem Split tracks: switch to Stem Splitting tab and fill that form
- if (settings.generator === "stem_split") {
+ if (settings.generator === "stem" || settings.generator === "stem_split") {
if (typeof CDMF.applyStemSplitSettingsToForm === "function") {
CDMF.applyStemSplitSettingsToForm(settings);
return;
}
}
+ // MIDI Generation tracks: switch to MIDI Generation tab and fill that form
+ if (settings.generator === "midi" || settings.generator === "midi_generation") {
+ if (typeof CDMF.applyMidiGenSettingsToForm === "function") {
+ CDMF.applyMidiGenSettingsToForm(settings);
+ return;
+ }
+ }
+
const promptField = document.getElementById("prompt");
const lyricsField = document.getElementById("lyrics");
const instrumentalCheckbox = document.getElementById("instrumental");
@@ -354,8 +418,18 @@
if (refAudioStrengthField && settings.ref_audio_strength != null) {
refAudioStrengthField.value = String(settings.ref_audio_strength);
}
- if (srcAudioPathField && typeof settings.src_audio_path === "string") {
- srcAudioPathField.value = settings.src_audio_path;
+ if (srcAudioPathField) {
+ // Restore from input_file_path if available (new field), otherwise src_audio_path
+ if (settings.input_file_path && typeof settings.input_file_path === "string") {
+ srcAudioPathField.value = settings.input_file_path;
+ } else if (settings.src_audio_path && typeof settings.src_audio_path === "string") {
+ srcAudioPathField.value = settings.src_audio_path;
+ }
+ // Show indicator if input_file exists but file input can't be set
+ if (settings.input_file && typeof settings.input_file === "string" && refAudioFileField) {
+ // Can't set file input directly, but we can show a helper message
+ // The src_audio_path field above should handle the path case
+ }
}
if (loraNameField && typeof settings.lora_name_or_path === "string") {
loraNameField.value = settings.lora_name_or_path;
diff --git a/static/scripts/cdmf_stem_splitting_ui.js b/static/scripts/cdmf_stem_splitting_ui.js
index 91aea65..36a5369 100644
--- a/static/scripts/cdmf_stem_splitting_ui.js
+++ b/static/scripts/cdmf_stem_splitting_ui.js
@@ -383,6 +383,16 @@
setVal("stem_split_mode", settings.mode || "");
setVal("stem_split_export_format", settings.export_format);
setVal("stem_split_out_dir", settings.out_dir);
+ setVal("stem_split_base_filename", settings.base_filename || "");
+
+ // Restore input file (show basename from full path)
+ var inputFileName = settings.original_file || settings.input_file;
+ if (inputFileName && typeof inputFileName === "string") {
+ var inputFileEl = document.getElementById("stem_split_input_file");
+ if (inputFileEl && typeof CDMF.restoreFileInput === "function") {
+ CDMF.restoreFileInput(inputFileEl, inputFileName);
+ }
+ }
};
// Update mode-specific UI when stem_count or mode changes
diff --git a/static/scripts/cdmf_voice_cloning_ui.js b/static/scripts/cdmf_voice_cloning_ui.js
index b2096be..a225cc2 100644
--- a/static/scripts/cdmf_voice_cloning_ui.js
+++ b/static/scripts/cdmf_voice_cloning_ui.js
@@ -9,7 +9,14 @@
CDMF.onSubmitVoiceClone = function (event) {
event.preventDefault();
-
+
+ var outputFilenameEl = document.getElementById("voice_clone_output_filename");
+ if (outputFilenameEl && !(outputFilenameEl.value || "").trim()) {
+ alert("Output filename is required. Please enter a name for the output file.");
+ outputFilenameEl.focus();
+ return;
+ }
+
var form = event.target;
var formData = new FormData(form);
@@ -148,6 +155,14 @@
setVal("voice_clone_output_filename", settings.basename);
setVal("voice_clone_language", settings.language);
setVal("voice_clone_device", settings.device_preference);
+
+ // Restore input file (show basename from full path)
+ if (settings.input_file && typeof settings.input_file === "string") {
+ var inputFileEl = document.getElementById("speaker_wav");
+ if (inputFileEl && typeof CDMF.restoreFileInput === "function") {
+ CDMF.restoreFileInput(inputFileEl, settings.input_file);
+ }
+ }
setNumPair("voice_clone_temperature", "voice_clone_temperature_range", settings.temperature);
setNumPair("voice_clone_length_penalty", "voice_clone_length_penalty_range", settings.length_penalty);
setNumPair("voice_clone_repetition_penalty", "voice_clone_repetition_penalty_range", settings.repetition_penalty);