@@ -19,6 +19,23 @@ def build_t2m_data(
1919 return data
2020
2121
22+ def build_t2m_async_data (
23+ prompt : str ,
24+ duration : int ,
25+ segments : Optional [List [Segment ]],
26+ mode : Optional [str ],
27+ output_format : Optional [str ],
28+ ) -> Dict [str , str ]:
29+ data = build_t2m_data (prompt , duration , segments )
30+ resolved = mode or "async"
31+ if resolved != "async" :
32+ raise SoniloError ("text-to-music submit() requires mode='async'" )
33+ data ["mode" ] = resolved
34+ if output_format is not None :
35+ data ["output_format" ] = output_format
36+ return data
37+
38+
2239def normalize_video (video : Any ) -> Tuple [str , Any , bool ]:
2340 """Normalize a video input into (filename, httpx-uploadable, opened_here).
2441
@@ -66,18 +83,31 @@ def build_v2m_parts(
6683 return data , files , opened
6784
6885
69- def _resolve_music_mode (mode : Optional [str ], isolate_vocals : Optional [bool ]) -> str :
70- """isolate_vocals only works with async processing: auto-select mode
71- "async" when the caller didn't specify one, but fail fast (mirroring the
72- video/video_url XOR check above) if they explicitly asked for anything
73- else. Without isolate_vocals, submit() still needs an async response
74- (a task_id ack, not a stream), so "async" is also the default there.
86+ def _resolve_music_mode (
87+ mode : Optional [str ],
88+ isolate_vocals : Optional [bool ],
89+ preserve_speech : Optional [bool ] = None ,
90+ output_format : Optional [str ] = None ,
91+ ducking : Optional [bool ] = None ,
92+ ) -> str :
93+ """isolate_vocals/preserve_speech/ducking/output_format='wav' only work
94+ with async processing: auto-select mode "async" when the caller didn't
95+ specify one, but fail fast if they explicitly asked for anything else.
96+ submit() also needs an async response (a task_id ack, not a stream), so
97+ "async" is the default regardless.
7598 """
76- if isolate_vocals :
77- if mode is not None and mode != "async" :
78- raise SoniloError ("isolate_vocals=True requires mode='async'" )
79- return "async"
80- return mode or "async"
99+ needs_async = (
100+ bool (isolate_vocals )
101+ or bool (preserve_speech )
102+ or output_format == "wav"
103+ or ducking is not None
104+ )
105+ if needs_async and mode is not None and mode != "async" :
106+ raise SoniloError (
107+ "isolate_vocals/preserve_speech/ducking/output_format='wav' "
108+ "require mode='async'"
109+ )
110+ return "async" if needs_async else (mode or "async" )
81111
82112
83113def build_v2m_async_parts (
@@ -87,17 +117,53 @@ def build_v2m_async_parts(
87117 segments : Optional [List [Segment ]],
88118 mode : Optional [str ],
89119 isolate_vocals : Optional [bool ],
120+ preserve_speech : Optional [bool ] = None ,
121+ output_format : Optional [str ] = None ,
122+ ducking : Optional [bool ] = None ,
90123) -> Tuple [Dict [str , str ], Optional [Dict [str , tuple ]], bool ]:
91- """Like build_v2m_parts, plus the async-only `mode`/`isolate_vocals`
92- fields used by the video-to-music submit()/generate_async() path."""
93- resolved_mode = _resolve_music_mode (mode , isolate_vocals )
124+ """Like build_v2m_parts, plus the async-only fields for the
125+ video-to-music submit()/generate_async() path."""
126+ resolved_mode = _resolve_music_mode (
127+ mode , isolate_vocals , preserve_speech , output_format , ducking
128+ )
94129 data , files , opened = build_v2m_parts (video , video_url , prompt , segments )
95130 data ["mode" ] = resolved_mode
96131 if isolate_vocals is not None :
97132 data ["isolate_vocals" ] = "true" if isolate_vocals else "false"
133+ if preserve_speech is not None :
134+ data ["preserve_speech" ] = "true" if preserve_speech else "false"
135+ if output_format is not None :
136+ data ["output_format" ] = output_format
137+ if ducking is not None :
138+ data ["ducking" ] = "true" if ducking else "false"
98139 return data , files , opened
99140
100141
142+ def build_v2v_music_parts (
143+ video : Any ,
144+ video_url : Optional [str ],
145+ prompt : Optional [str ],
146+ preserve_speech : Optional [bool ],
147+ isolate_vocals : Optional [bool ],
148+ ) -> Tuple [Dict [str , str ], Optional [Dict [str , tuple ]], bool ]:
149+ data , files , opened = build_v2m_parts (video , video_url , prompt , None )
150+ if preserve_speech is not None :
151+ data ["preserve_speech" ] = "true" if preserve_speech else "false"
152+ if isolate_vocals is not None :
153+ data ["isolate_vocals" ] = "true" if isolate_vocals else "false"
154+ return data , files , opened
155+
156+
157+ def build_v2v_sfx_parts (
158+ video : Any ,
159+ video_url : Optional [str ],
160+ prompt : Optional [str ],
161+ segments : Optional [List [Segment ]],
162+ ) -> Tuple [Dict [str , str ], Optional [Dict [str , tuple ]], bool ]:
163+ # build_v2m_parts JSON-serializes `segments` — fine for SFX start/end segments too.
164+ return build_v2m_parts (video , video_url , prompt , segments )
165+
166+
101167def build_sfx_t2s_data (
102168 prompt : str , duration : int , audio_format : Optional [str ]
103169) -> Dict [str , str ]:
0 commit comments