Skip to content

Commit e4fd6fd

Browse files
committed
feat: add speaker variant audio upload endpoints
- Add POST /api/2/speakers/{id}/upload-variant/ for file uploads - Add delete-binary param to remove-variant-uri endpoint - Reuse existing audio processing pipeline for variants - Support single file upload with client-side multiple file handling - Include file validation, processing, and cleanup on failure
1 parent cf30702 commit e4fd6fd

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

roundware/api2/views.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,10 @@ def add_variant_uri(self, request, pk=None):
13901390
def remove_variant_uri(self, request, pk=None):
13911391
"""
13921392
POST api/2/speakers/:id/remove-variant-uri/ - Remove a URI from the varianturis array
1393+
1394+
Parameters:
1395+
- uri: URI to remove from varianturis array
1396+
- delete-binary: Boolean to delete the actual audio file (default: false)
13931397
"""
13941398
speaker = self.get_object(pk)
13951399

@@ -1401,16 +1405,111 @@ def remove_variant_uri(self, request, pk=None):
14011405
)
14021406

14031407
uri_to_remove = request.data['uri']
1408+
delete_binary = request.data.get('delete-binary', False)
1409+
1410+
# Convert string boolean to actual boolean if needed
1411+
if isinstance(delete_binary, str):
1412+
delete_binary = delete_binary.lower() in ('true', '1', 'yes', 'on')
14041413

14051414
# Remove the URI from the array
14061415
if uri_to_remove in speaker.varianturis:
14071416
speaker.varianturis.remove(uri_to_remove)
14081417
speaker.save(update_fields=['varianturis'])
1418+
1419+
# Delete the binary file if requested
1420+
if delete_binary:
1421+
try:
1422+
from django.conf import settings
1423+
import os
1424+
from urllib.parse import urlparse
1425+
1426+
# Extract filename from URI
1427+
parsed_url = urlparse(uri_to_remove)
1428+
filename = os.path.basename(parsed_url.path)
1429+
1430+
if filename:
1431+
# Construct full file path
1432+
file_path = os.path.join(settings.MEDIA_ROOT, filename)
1433+
1434+
# Delete the file if it exists
1435+
if os.path.exists(file_path):
1436+
os.remove(file_path)
1437+
1438+
except Exception as e:
1439+
# Log the error but don't fail the request
1440+
import logging
1441+
logger = logging.getLogger(__name__)
1442+
logger.warning(f"Failed to delete binary file for URI {uri_to_remove}: {e}")
14091443

14101444
# Return the updated speaker data
14111445
serializer = serializers.SpeakerSerializer(speaker)
14121446
return Response(serializer.data)
14131447

1448+
@action(methods=['post'], detail=True, url_path='upload-variant')
1449+
def upload_variant(self, request, pk=None):
1450+
"""
1451+
POST api/2/speakers/:id/upload-variant/ - Upload audio file and add to varianturis array
1452+
"""
1453+
speaker = self.get_object(pk)
1454+
1455+
# Validate that 'file' is provided
1456+
if 'file' not in request.FILES:
1457+
return Response(
1458+
{"detail": "Request must include 'file' field."},
1459+
status=status.HTTP_400_BAD_REQUEST
1460+
)
1461+
1462+
uploaded_file = request.FILES['file']
1463+
1464+
# Validate the file using the same validation as main speaker creation
1465+
from roundware.rw.file_utils import validate_audio_file
1466+
is_valid, error_message = validate_audio_file(uploaded_file)
1467+
if not is_valid:
1468+
return Response(
1469+
{"detail": f"Invalid audio file: {error_message}"},
1470+
status=status.HTTP_400_BAD_REQUEST
1471+
)
1472+
1473+
try:
1474+
# Process the file using the same logic as main speaker creation
1475+
from roundware.lib.api import save_speaker_from_request
1476+
1477+
# Create a mock request object with the necessary attributes
1478+
class MockRequest:
1479+
def __init__(self, original_request, project_id, audio_compression):
1480+
self.FILES = original_request.FILES
1481+
self.data = {
1482+
'project': project_id,
1483+
'audio_compression': audio_compression
1484+
}
1485+
self.get_host = original_request.get_host
1486+
1487+
mock_request = MockRequest(
1488+
request,
1489+
speaker.project.id,
1490+
request.data.get('audio_compression', False)
1491+
)
1492+
1493+
# Process the file and get the URI
1494+
file_uri = save_speaker_from_request(mock_request)
1495+
1496+
# Add the URI to varianturis if it's not already there
1497+
if file_uri not in speaker.varianturis:
1498+
speaker.varianturis.append(file_uri)
1499+
speaker.save(update_fields=['varianturis'])
1500+
1501+
# Return the updated speaker data
1502+
serializer = serializers.SpeakerSerializer(speaker)
1503+
return Response(serializer.data)
1504+
1505+
except Exception as e:
1506+
# Clean up any uploaded files on failure
1507+
# Note: save_speaker_from_request handles its own cleanup
1508+
return Response(
1509+
{"detail": f"Upload failed: {str(e)}"},
1510+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
1511+
)
1512+
14141513

14151514
# class StreamViewSet(viewsets.ViewSet):
14161515
# """

0 commit comments

Comments
 (0)