diff --git a/README.md b/README.md index db25d13..7275243 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,8 @@ mastering_request = MasteringRequest( track_url=uploaded_mixdown_url, musical_style=MusicalStyle.ROCK_INDIE, desired_loudness=DesiredLoudness.MEDIUM, - sample_rate="44100" # Match the source file's sample rate + sample_rate="44100", # Match the source file's sample rate + preview_start_time=30.0 # Optional: start the 30s preview at 30 seconds (omit for auto RMS-based selection) ) # Create mastering preview task @@ -136,11 +137,12 @@ print(f"Mastering task submitted. Task ID: {task.mastering_task_id}") # Retrieve the preview master (polls until ready) preview = client.mastering.retrieve_preview_master(task.mastering_task_id) -print(f"Preview Master URL: {preview.get('download_url_mastered_preview')}") +print(f"Preview Master URL: {preview.download_url_mastered_preview}") +print(f"Preview starts at: {preview.preview_start_time}s") # Optionally, retrieve the final master (polls until ready) -final_url = client.mastering.retrieve_final_master(task.mastering_task_id) -print(f"Final Master URL: {final_url}") +final_result = client.mastering.retrieve_final_master(task.mastering_task_id) +print(f"Final Master URL: {final_result.download_url_mastered}") ``` **Output:** Returns task IDs and download URLs for the mastered preview and final audio files. @@ -180,7 +182,7 @@ print("Analysis Results:", analysis_results) Enhance an existing mix using AI. If using a local file, it must be uploaded first. ```python -from roex_python.models import EnhanceMixRequest +from roex_python.models import MixEnhanceRequest, EnhanceMusicalStyle, LoudnessPreference # Assuming 'client' is an initialized RoExClient # And 'upload_file' is a helper function like the one in examples/common.py or roex_python.utils @@ -190,17 +192,21 @@ mix_to_enhance_local_path = "/path/to/your/mix_to_enhance.wav" uploaded_enhance_url = upload_file(client, mix_to_enhance_local_path) # Step 2: Use the obtained URL in the request -enhance_request = EnhanceMixRequest( - track_url=uploaded_enhance_url +enhance_request = MixEnhanceRequest( + audio_file_location=uploaded_enhance_url, + musical_style=EnhanceMusicalStyle.POP, + loudness_preference=LoudnessPreference.STREAMING_LOUDNESS, + preview_start_time=45.0 # Optional: start the 30s preview at 45 seconds (omit for auto RMS-based selection) ) -# Submit enhancement task -task = client.enhance.create_enhancement(enhance_request) -print(f"Enhancement task submitted. Task ID: {task.enhance_task_id}") +# Submit enhancement preview task +task = client.enhance.create_mix_enhance_preview(enhance_request) +print(f"Enhancement task submitted. Task ID: {task.mixrevive_task_id}") -# Retrieve enhanced mix (polls until ready) -enhanced_mix = client.enhance.retrieve_enhancement(task.enhance_task_id) -print(f"Enhanced Mix URL: {enhanced_mix.get('download_url_enhanced_mix')}") +# Retrieve enhanced preview (polls until ready) +result = client.enhance.retrieve_enhanced_track(task.mixrevive_task_id) +print(f"Enhanced Preview URL: {result.download_url_preview_revived}") +print(f"Preview starts at: {result.preview_start_time}s") ``` **Output:** Returns a task ID and the download URL for the enhanced audio file and it's stems if requested. diff --git a/roex_python/controllers/enhance_controller.py b/roex_python/controllers/enhance_controller.py index b93b1a6..52e86d5 100644 --- a/roex_python/controllers/enhance_controller.py +++ b/roex_python/controllers/enhance_controller.py @@ -52,6 +52,8 @@ def create_mix_enhance_preview(self, request: MixEnhanceRequest) -> MixEnhanceRe - `loudness_preference` (LoudnessPreference): Target loudness. - `stem_processing` (bool): If True, generate stems (preview not guaranteed to contain full stems). - `webhook_url` (Optional[str]): URL for task completion notification. + - `preview_start_time` (Optional[float]): If provided (>= 0), the 30-second + preview starts at this offset (seconds) instead of auto-selecting by RMS energy. Returns: MixEnhanceResponse: An object containing: @@ -122,6 +124,8 @@ def create_mix_enhance(self, request: MixEnhanceRequest) -> MixEnhanceResponse: - `loudness_preference` (LoudnessPreference): Target loudness. - `stem_processing` (bool): If True, also generate stems (vocals, bass, drums, other). - `webhook_url` (Optional[str]): URL for task completion notification. + - `preview_start_time` (Optional[float]): If provided (>= 0), the 30-second + preview starts at this offset (seconds) instead of auto-selecting by RMS energy. Returns: MixEnhanceResponse: An object containing: @@ -269,4 +273,6 @@ def _prepare_mix_enhance_payload(self, request: MixEnhanceRequest) -> Dict[str, } if request.webhook_url is not None: data["webhookURL"] = request.webhook_url + if request.preview_start_time is not None: + data["previewStartTime"] = request.preview_start_time return {"mixReviveData": data} diff --git a/roex_python/controllers/mastering_controller.py b/roex_python/controllers/mastering_controller.py index de044a7..729360a 100644 --- a/roex_python/controllers/mastering_controller.py +++ b/roex_python/controllers/mastering_controller.py @@ -50,6 +50,9 @@ def create_mastering_preview(self, request: MasteringRequest) -> MasteringTaskRe request (MasteringRequest): An object containing the track URL and mastering parameters (MusicalStyle, DesiredLoudness, etc.). The track URL must point to an accessible WAV or FLAC file. + Optionally includes ``preview_start_time`` (float, >= 0) to set + the offset in seconds where the 30-second preview begins, instead + of auto-selecting by highest RMS energy. Returns: MasteringTaskResponse: An object containing the unique `mastering_task_id` @@ -93,6 +96,8 @@ def create_mastering_preview(self, request: MasteringRequest) -> MasteringTaskRe } if request.webhook_url is not None: data["webhookURL"] = request.webhook_url + if request.preview_start_time is not None: + data["previewStartTime"] = request.preview_start_time payload = {"masteringData": data} try: diff --git a/roex_python/models/enhance.py b/roex_python/models/enhance.py index 43c0f12..26d9b75 100644 --- a/roex_python/models/enhance.py +++ b/roex_python/models/enhance.py @@ -50,6 +50,10 @@ class MixEnhanceRequest: """bool: If True, requests the generation of stems (e.g., vocals, bass, drums, other) alongside the enhanced mix. Defaults to False.""" get_processed_stems: bool = False """bool: If True, requests the processed stems alongside the enhanced mix. Defaults to False.""" + preview_start_time: Optional[float] = None + """Optional[float]: When provided (>= 0), the 30-second preview starts at this offset in seconds + instead of being auto-selected based on highest energy (RMS). If the value exceeds the track + duration, the API clamps it to produce a valid segment. Omit to preserve default behavior.""" @dataclass diff --git a/roex_python/models/mastering.py b/roex_python/models/mastering.py index b5ac02c..1f118a2 100644 --- a/roex_python/models/mastering.py +++ b/roex_python/models/mastering.py @@ -27,6 +27,10 @@ class MasteringRequest: """str: The desired sample rate for the output mastered file. Defaults to "44100" Hz.""" webhook_url: Optional[str] = None """Optional[str]: A URL to which a notification will be sent upon task completion.""" + preview_start_time: Optional[float] = None + """Optional[float]: When provided (>= 0), the 30-second preview starts at this offset in seconds + instead of being auto-selected based on highest energy (RMS). If the value exceeds the track + duration, the API clamps it to produce a valid segment. Omit to preserve default behavior.""" @dataclass diff --git a/tests/unit/test_controllers/test_enhance_controller.py b/tests/unit/test_controllers/test_enhance_controller.py index 37b8f95..a38a108 100644 --- a/tests/unit/test_controllers/test_enhance_controller.py +++ b/tests/unit/test_controllers/test_enhance_controller.py @@ -93,6 +93,28 @@ def test_with_custom_settings(self, mock_api_provider): assert mix_data["applyMastering"] is True assert mix_data["stemProcessing"] is True + def test_with_preview_start_time(self, mock_api_provider): + """Test enhance preview includes previewStartTime in API payload""" + mock_api_provider.post.return_value = { + "mixrevive_task_id": "enhance_task_789", + "error": False, + "message": "Success" + } + + controller = EnhanceController(mock_api_provider) + + request = MixEnhanceRequest( + audio_file_location="https://example.com/mix.wav", + musical_style=MusicalStyle.POP, + preview_start_time=45.0 + ) + + result = controller.create_mix_enhance_preview(request) + + assert result.mixrevive_task_id == "enhance_task_789" + payload = mock_api_provider.post.call_args[0][1] + assert payload["mixReviveData"]["previewStartTime"] == 45.0 + def test_http_error_handling(self, mock_api_provider): """Test error handling when API returns HTTP error""" # Setup @@ -322,3 +344,38 @@ def test_payload_no_fix_drc_issues(self, mock_api_provider): ) payload = controller._prepare_mix_enhance_payload(request) assert "fixDRCIssues" not in payload["mixReviveData"] + + def test_payload_preview_start_time_included_when_provided(self, mock_api_provider): + """Test that previewStartTime is included in the payload when set""" + controller = EnhanceController(mock_api_provider) + + request = MixEnhanceRequest( + audio_file_location="https://example.com/mix.wav", + musical_style=MusicalStyle.POP, + preview_start_time=45.0 + ) + payload = controller._prepare_mix_enhance_payload(request) + assert payload["mixReviveData"]["previewStartTime"] == 45.0 + + def test_payload_preview_start_time_zero_is_included(self, mock_api_provider): + """Test that previewStartTime=0 is included (not treated as falsy)""" + controller = EnhanceController(mock_api_provider) + + request = MixEnhanceRequest( + audio_file_location="https://example.com/mix.wav", + musical_style=MusicalStyle.POP, + preview_start_time=0.0 + ) + payload = controller._prepare_mix_enhance_payload(request) + assert payload["mixReviveData"]["previewStartTime"] == 0.0 + + def test_payload_preview_start_time_omitted_when_none(self, mock_api_provider): + """Test that previewStartTime is NOT in the payload when not set (backward compat)""" + controller = EnhanceController(mock_api_provider) + + request = MixEnhanceRequest( + audio_file_location="https://example.com/mix.wav", + musical_style=MusicalStyle.POP + ) + payload = controller._prepare_mix_enhance_payload(request) + assert "previewStartTime" not in payload["mixReviveData"] diff --git a/tests/unit/test_controllers/test_mastering_controller.py b/tests/unit/test_controllers/test_mastering_controller.py index bc08088..38858da 100644 --- a/tests/unit/test_controllers/test_mastering_controller.py +++ b/tests/unit/test_controllers/test_mastering_controller.py @@ -79,6 +79,63 @@ def test_with_webhook_url(self, mock_api_provider): payload = call_args[0][1] assert payload["masteringData"]["webhookURL"] == "https://example.com/webhook" + def test_with_preview_start_time(self, mock_api_provider): + """Test mastering preview includes previewStartTime in API payload""" + mock_api_provider.post.return_value = { + "mastering_task_id": "task_789" + } + + controller = MasteringController(mock_api_provider) + request = MasteringRequest( + track_url="https://example.com/track.wav", + musical_style=MusicalStyle.POP, + desired_loudness=DesiredLoudness.MEDIUM, + preview_start_time=45.0 + ) + + result = controller.create_mastering_preview(request) + + assert result.mastering_task_id == "task_789" + payload = mock_api_provider.post.call_args[0][1] + assert payload["masteringData"]["previewStartTime"] == 45.0 + + def test_preview_start_time_zero_is_included(self, mock_api_provider): + """Test that previewStartTime=0 is included (not treated as falsy)""" + mock_api_provider.post.return_value = { + "mastering_task_id": "task_zero" + } + + controller = MasteringController(mock_api_provider) + request = MasteringRequest( + track_url="https://example.com/track.wav", + musical_style=MusicalStyle.POP, + desired_loudness=DesiredLoudness.MEDIUM, + preview_start_time=0.0 + ) + + controller.create_mastering_preview(request) + + payload = mock_api_provider.post.call_args[0][1] + assert payload["masteringData"]["previewStartTime"] == 0.0 + + def test_preview_start_time_omitted_when_none(self, mock_api_provider): + """Test that previewStartTime is NOT in the payload when not set (backward compat)""" + mock_api_provider.post.return_value = { + "mastering_task_id": "task_compat" + } + + controller = MasteringController(mock_api_provider) + request = MasteringRequest( + track_url="https://example.com/track.wav", + musical_style=MusicalStyle.POP, + desired_loudness=DesiredLoudness.MEDIUM + ) + + controller.create_mastering_preview(request) + + payload = mock_api_provider.post.call_args[0][1] + assert "previewStartTime" not in payload["masteringData"] + def test_http_error_handling(self, mock_api_provider): """Test error handling when API returns HTTP error""" # Setup