@@ -61,6 +61,37 @@ def generate_file_hash(filename: str) -> str:
6161 return hashlib .sha256 (filename .encode ("utf-8" )).hexdigest ()[:16 ]
6262
6363
64+ def download_from_gcs (gcs_uri : str ) -> tuple [bytes , str ]:
65+ """Download an audio file from GCS.
66+
67+ Args:
68+ gcs_uri: GCS URI in the format gs://bucket/path/to/file
69+
70+ Returns:
71+ Tuple of (file_bytes, filename)
72+ """
73+ from google .cloud import storage
74+
75+ if not gcs_uri .startswith ("gs://" ):
76+ raise ValueError (f"Invalid GCS URI (must start with gs://): { gcs_uri } " )
77+
78+ # Parse gs://bucket/path
79+ without_prefix = gcs_uri [len ("gs://" ):]
80+ slash_idx = without_prefix .index ("/" )
81+ bucket_name = without_prefix [:slash_idx ]
82+ blob_path = without_prefix [slash_idx + 1 :]
83+ filename = os .path .basename (blob_path )
84+
85+ logger .info (f"Downloading from GCS: bucket={ bucket_name } , path={ blob_path } " )
86+ client = storage .Client ()
87+ bucket = client .bucket (bucket_name )
88+ blob = bucket .blob (blob_path )
89+ audio_bytes = blob .download_as_bytes ()
90+ logger .info (f"Downloaded { len (audio_bytes )} bytes from GCS" )
91+
92+ return audio_bytes , filename
93+
94+
6495try :
6596 AUDIO_SEPARATOR_VERSION = version ("audio-separator" )
6697except Exception :
@@ -335,7 +366,8 @@ def render(self, content: typing.Any) -> bytes:
335366
336367@web_app .post ("/separate" )
337368async def separate_audio (
338- file : UploadFile = File (..., description = "Audio file to separate" ),
369+ file : Optional [UploadFile ] = File (None , description = "Audio file to separate" ),
370+ gcs_uri : Optional [str ] = Form (None , description = "GCS URI (gs://bucket/path) to fetch audio from instead of uploading" ),
339371 model : Optional [str ] = Form (None , description = "Single model to use for separation" ),
340372 models : Optional [str ] = Form (None , description = 'JSON list of models, e.g. ["model1.ckpt", "model2.onnx"]' ),
341373 preset : Optional [str ] = Form (None , description = "Ensemble preset name (e.g. instrumental_clean, karaoke)" ),
@@ -376,9 +408,14 @@ async def separate_audio(
376408 mdxc_batch_size : int = Form (1 ),
377409 mdxc_pitch_shift : int = Form (0 ),
378410) -> dict :
379- """Upload an audio file and separate it into stems."""
380- if not file .filename :
381- raise HTTPException (status_code = 400 , detail = "No file provided" )
411+ """Upload an audio file (or provide a GCS URI) and separate it into stems."""
412+ # Validate: must provide exactly one of file or gcs_uri
413+ has_file = file is not None and file .filename
414+ has_gcs = gcs_uri is not None and gcs_uri .strip ()
415+ if not has_file and not has_gcs :
416+ raise HTTPException (status_code = 400 , detail = "Must provide either a file upload or gcs_uri parameter" )
417+ if has_file and has_gcs :
418+ raise HTTPException (status_code = 400 , detail = "Provide either file upload or gcs_uri, not both" )
382419
383420 try :
384421 # Parse models parameter
@@ -403,15 +440,24 @@ async def separate_audio(
403440 except json .JSONDecodeError as e :
404441 raise HTTPException (status_code = 400 , detail = f"Invalid JSON in custom_output_names parameter: { e } " )
405442
406- audio_data = await file .read ()
443+ # Get audio data from file upload or GCS
444+ if has_gcs :
445+ try :
446+ audio_data , filename = download_from_gcs (gcs_uri .strip ())
447+ except Exception as e :
448+ raise HTTPException (status_code = 400 , detail = f"Failed to download from GCS: { e } " )
449+ else :
450+ audio_data = await file .read ()
451+ filename = file .filename
452+
407453 task_id = str (uuid .uuid4 ())
408454
409455 # Set initial status
410456 job_status_store [task_id ] = {
411457 "task_id" : task_id ,
412458 "status" : "submitted" ,
413459 "progress" : 0 ,
414- "original_filename" : file . filename ,
460+ "original_filename" : filename ,
415461 "models_used" : [f"preset:{ preset } " ] if preset else (models_list or ["default" ]),
416462 "total_models" : 1 if preset else (len (models_list ) if models_list else 1 ),
417463 "current_model_index" : 0 ,
@@ -425,7 +471,7 @@ async def separate_audio(
425471 None ,
426472 lambda : separate_audio_sync (
427473 audio_data ,
428- file . filename ,
474+ filename ,
429475 task_id ,
430476 models_list ,
431477 preset ,
@@ -608,7 +654,7 @@ async def root() -> dict:
608654 "All MDX, VR, Demucs, and MDXC architectures supported" ,
609655 ],
610656 "endpoints" : {
611- "POST /separate" : "Upload and separate audio file (supports presets, multiple models, all parameters)" ,
657+ "POST /separate" : "Separate audio file via upload or GCS URI (supports presets, multiple models, all parameters)" ,
612658 "GET /status/{task_id}" : "Get job status and progress" ,
613659 "GET /download/{task_id}/{file_hash}" : "Download separated file using hash identifier" ,
614660 "GET /presets" : "List available ensemble presets" ,
0 commit comments