Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions roex_python/controllers/enhance_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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}
5 changes: 5 additions & 0 deletions roex_python/controllers/mastering_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions roex_python/models/enhance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions roex_python/models/mastering.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions tests/unit/test_controllers/test_enhance_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"]
57 changes: 57 additions & 0 deletions tests/unit/test_controllers/test_mastering_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down