@@ -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