Include start offset in voice-name window tag
Clones that don't start at 0:00 are tagged with where the reference window begins (grandma-at1m32s-30s), so voices cloned from different sections of the same source are distinguishable in the voice list. Zero-start names keep the existing -30s form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+58
-16
@@ -177,13 +177,25 @@ fn tts_ref_seconds() -> u32 {
|
||||
.unwrap_or(30)
|
||||
}
|
||||
|
||||
/// Tag a (sanitized) voice name with the reference-clip cap used to create it,
|
||||
/// e.g. `grandma` → `grandma-30s`. The tag makes the ref length visible in the
|
||||
/// voice list so clones of the same source at different caps can be compared.
|
||||
/// Tag a (sanitized) voice name with the reference window used to create it:
|
||||
/// `grandma` → `grandma-30s` (from the start), or `grandma-at1m32s-30s` (30s
|
||||
/// window starting at 1:32). The tag makes the window visible in the voice
|
||||
/// list so clones of the same source from different sections can be compared.
|
||||
/// Skips the append when the name already ends in the same tag; keeps the
|
||||
/// 64-char bound by truncating the base name, never the tag.
|
||||
fn append_ref_seconds(name: &str, secs: u32) -> String {
|
||||
let suffix = format!("-{secs}s");
|
||||
fn append_ref_window(name: &str, start: f64, secs: u32) -> String {
|
||||
let start_whole = start.round().max(0.0) as u64;
|
||||
let suffix = if start_whole > 0 {
|
||||
// ':' isn't in the safe voice-name charset, so 1:32 becomes 1m32s.
|
||||
let at = if start_whole >= 60 {
|
||||
format!("at{}m{:02}s", start_whole / 60, start_whole % 60)
|
||||
} else {
|
||||
format!("at{start_whole}s")
|
||||
};
|
||||
format!("-{at}-{secs}s")
|
||||
} else {
|
||||
format!("-{secs}s")
|
||||
};
|
||||
if name.ends_with(&suffix) {
|
||||
return name.to_string();
|
||||
}
|
||||
@@ -880,7 +892,7 @@ pub async fn create_voice_upload_handler(
|
||||
};
|
||||
// Tag the name with the ref-clip length (e.g. `grandma-30s`) so the
|
||||
// library shows which reference length produced each clone.
|
||||
let name = append_ref_seconds(&name, ref_duration.round().max(1.0) as u32);
|
||||
let name = append_ref_window(&name, ref_start, ref_duration.round().max(1.0) as u32);
|
||||
if file_bytes.is_empty() {
|
||||
span.set_status(Status::error("voice_file is required"));
|
||||
return HttpResponse::BadRequest().json(json!({ "error": "voice_file is required" }));
|
||||
@@ -970,7 +982,8 @@ pub async fn create_voice_from_library_handler(
|
||||
};
|
||||
// Tag the name with the ref-clip length (e.g. `grandma-30s`) so the
|
||||
// library shows which reference length produced each clone.
|
||||
let voice_name = append_ref_seconds(&voice_name, ref_duration.round().max(1.0) as u32);
|
||||
let voice_name =
|
||||
append_ref_window(&voice_name, ref_start, ref_duration.round().max(1.0) as u32);
|
||||
|
||||
let library = match libraries::resolve_library_param(&app_state, req.library.as_deref()) {
|
||||
Ok(Some(l)) => l,
|
||||
@@ -1075,24 +1088,53 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_ref_seconds_tags_name() {
|
||||
assert_eq!(append_ref_seconds("grandma", 30), "grandma-30s");
|
||||
assert_eq!(append_ref_seconds("voice_01", 15), "voice_01-15s");
|
||||
fn append_ref_window_tags_name() {
|
||||
assert_eq!(append_ref_window("grandma", 0.0, 30), "grandma-30s");
|
||||
assert_eq!(append_ref_window("voice_01", 0.0, 15), "voice_01-15s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_ref_seconds_is_idempotent_for_same_cap() {
|
||||
assert_eq!(append_ref_seconds("grandma-30s", 30), "grandma-30s");
|
||||
// A different cap still appends — that's the comparison use-case.
|
||||
assert_eq!(append_ref_seconds("grandma-15s", 30), "grandma-15s-30s");
|
||||
fn append_ref_window_includes_nonzero_start() {
|
||||
// Sub-minute starts stay in seconds; longer ones read as XmYYs since
|
||||
// ':' isn't allowed in voice names.
|
||||
assert_eq!(append_ref_window("grandma", 45.0, 30), "grandma-at45s-30s");
|
||||
assert_eq!(
|
||||
append_ref_window("grandma", 92.4, 30),
|
||||
"grandma-at1m32s-30s"
|
||||
);
|
||||
assert_eq!(
|
||||
append_ref_window("grandma", 600.0, 12),
|
||||
"grandma-at10m00s-12s"
|
||||
);
|
||||
// A start that rounds to zero is "from the start".
|
||||
assert_eq!(append_ref_window("grandma", 0.3, 30), "grandma-30s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_ref_seconds_keeps_64_char_bound() {
|
||||
fn append_ref_window_is_idempotent_for_same_window() {
|
||||
assert_eq!(append_ref_window("grandma-30s", 0.0, 30), "grandma-30s");
|
||||
assert_eq!(
|
||||
append_ref_window("grandma-at45s-30s", 45.0, 30),
|
||||
"grandma-at45s-30s"
|
||||
);
|
||||
// A different window still appends — that's the comparison use-case.
|
||||
assert_eq!(append_ref_window("grandma-15s", 0.0, 30), "grandma-15s-30s");
|
||||
assert_eq!(
|
||||
append_ref_window("grandma-30s", 45.0, 30),
|
||||
"grandma-30s-at45s-30s"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_ref_window_keeps_64_char_bound() {
|
||||
let long = "a".repeat(64);
|
||||
let tagged = append_ref_seconds(&long, 30);
|
||||
let tagged = append_ref_window(&long, 0.0, 30);
|
||||
assert_eq!(tagged.len(), 64);
|
||||
assert!(tagged.ends_with("-30s"));
|
||||
|
||||
let tagged = append_ref_window(&long, 92.0, 30);
|
||||
assert_eq!(tagged.len(), 64);
|
||||
assert!(tagged.ends_with("-at1m32s-30s"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user