use super::SunoClient;
use super::types::{Clip, GenerateRequest};
use crate::core::CliError;
pub struct ExtendClipOptions<'a> {
pub clip_id: &'a str,
pub continue_at: f64,
pub tags: Option<&'a str>,
pub negative_tags: Option<&'a str>,
pub lyrics: Option<&'a str>,
pub title: Option<&'a str>,
pub instrumental: Option<bool>,
pub challenge_token: Option<String>,
}
impl SunoClient {
pub async fn extend(&self, options: ExtendClipOptions<'_>) -> Result<Vec<Clip>, CliError> {
let requested = [options.clip_id.to_string()];
let mut source = self
.get_clips(&requested)
.await?
.into_iter()
.find(|clip| clip.id == options.clip_id)
.ok_or_else(|| CliError::NotFound(format!("clip: {}", options.clip_id)))?;
if extend_source_needs_feed_metadata(&source, &options)
&& let Ok(feed) = self.search(&source.title).await
&& let Some(enriched) = feed
.clips
.into_iter()
.find(|clip| clip.id == options.clip_id)
{
merge_extend_source_metadata(&mut source, enriched);
}
let mut req = GenerateRequest::new("chirp-fenix", "custom");
req.task = Some("extend".into());
req.title = Some(
options
.title
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or(&source.title)
.to_string(),
);
req.prompt = options.lyrics.unwrap_or_default().to_string();
req.continued_aligned_prompt = Some(source.metadata.prompt.unwrap_or_default());
req.tags = Some(
options
.tags
.map(str::to_string)
.or(source.metadata.tags)
.unwrap_or_default(),
);
req.negative_tags = options
.negative_tags
.map(str::to_string)
.or(source.metadata.negative_tags)
.unwrap_or_default();
req.make_instrumental = options
.instrumental
.or(source.metadata.make_instrumental)
.unwrap_or_default();
req.continue_clip_id = Some(options.clip_id.to_string());
req.continue_at = Some(options.continue_at);
req.metadata.is_remix = Some(true);
req.metadata.lyrics_updated = Some(true);
req.set_challenge_token(options.challenge_token);
self.generate(&req).await
}
}
fn extend_source_needs_feed_metadata(source: &Clip, options: &ExtendClipOptions<'_>) -> bool {
(options.tags.is_none() && option_string_is_blank(source.metadata.tags.as_ref()))
|| (options.negative_tags.is_none() && source.metadata.negative_tags.is_none())
|| (options.instrumental.is_none() && source.metadata.make_instrumental.is_none())
}
fn option_string_is_blank(value: Option<&String>) -> bool {
value.map(|value| value.trim().is_empty()).unwrap_or(true)
}
fn merge_extend_source_metadata(source: &mut Clip, enriched: Clip) {
if option_string_is_blank(source.metadata.tags.as_ref())
&& let Some(tags) = enriched.metadata.tags
{
source.metadata.tags = Some(tags);
}
if source.metadata.negative_tags.is_none() {
source.metadata.negative_tags = enriched.metadata.negative_tags;
}
if source.metadata.make_instrumental.is_none() {
source.metadata.make_instrumental = enriched.metadata.make_instrumental;
}
}