|
|
|
@ -93,7 +93,7 @@ class PlugNMeetWebhookAPIView(APIView): |
|
|
|
'room_finished': self._handle_room_finished, |
|
|
|
'participant_joined': self._handle_participant_joined, |
|
|
|
'participant_left': self._handle_participant_left, |
|
|
|
'end_recording': self._handle_end_recording, |
|
|
|
'RECORDING_PROCEEDED': self._handle_recording_proceeded, |
|
|
|
} |
|
|
|
|
|
|
|
handler = handler_map.get(event) |
|
|
|
@ -252,17 +252,41 @@ class PlugNMeetWebhookAPIView(APIView): |
|
|
|
except Exception as e: |
|
|
|
return {'error': str(e)} |
|
|
|
|
|
|
|
def _handle_end_recording(self, payload: Dict[str, Any]) -> Dict[str, Any]: |
|
|
|
def _get_video_duration(self, video_path: str) -> timezone.timedelta: |
|
|
|
"""Get video duration using ffprobe, return as timedelta.""" |
|
|
|
try: |
|
|
|
cmd = [ |
|
|
|
'ffprobe', |
|
|
|
'-v', 'error', |
|
|
|
'-show_entries', 'format=duration', |
|
|
|
'-of', 'default=noprint_wrappers=1:nokey=1', |
|
|
|
video_path |
|
|
|
] |
|
|
|
result = subprocess.run( |
|
|
|
cmd, |
|
|
|
stdout=subprocess.PIPE, |
|
|
|
stderr=subprocess.PIPE, |
|
|
|
timeout=30 |
|
|
|
) |
|
|
|
if result.returncode == 0: |
|
|
|
duration_seconds = float(result.stdout.decode().strip()) |
|
|
|
return timezone.timedelta(seconds=duration_seconds) |
|
|
|
except Exception as e: |
|
|
|
logger.warning(f"⚠️ [PlugNMeet Webhook] Duration extraction failed: {e}") |
|
|
|
return timezone.timedelta(seconds=0) |
|
|
|
|
|
|
|
def _handle_recording_proceeded(self, payload: Dict[str, Any]) -> Dict[str, Any]: |
|
|
|
room_data = payload.get('room', {}) |
|
|
|
recording_info = payload.get('recording_info', {}) |
|
|
|
|
|
|
|
room_id = room_data.get('identity') |
|
|
|
recording_id = recording_info.get('recordingId') |
|
|
|
file_name = recording_info.get('fileName', 'recording.mp4') |
|
|
|
duration = recording_info.get('duration', 0) |
|
|
|
recording_id = recording_info.get('recordId') or recording_info.get('recordingId') |
|
|
|
file_path = recording_info.get('filePath') |
|
|
|
|
|
|
|
if not room_id or not recording_id: |
|
|
|
return {'message': 'Missing recording metadata'} |
|
|
|
|
|
|
|
file_name = os.path.basename(file_path) if file_path else f"{recording_id}.mp4" |
|
|
|
|
|
|
|
try: |
|
|
|
session = CourseLiveSession.objects.get(room_id=room_id) |
|
|
|
@ -284,19 +308,22 @@ class PlugNMeetWebhookAPIView(APIView): |
|
|
|
logger.info(f"📥 [PlugNMeet Webhook] Downloading recording {recording_id}...") |
|
|
|
client.download_file(download_path, tmp_file_path) |
|
|
|
|
|
|
|
# 3. Save to Database |
|
|
|
# 3. Read duration using ffprobe |
|
|
|
duration = self._get_video_duration(tmp_file_path) |
|
|
|
|
|
|
|
# 4. Save to Database |
|
|
|
with open(tmp_file_path, 'rb') as f: |
|
|
|
content = f.read() |
|
|
|
|
|
|
|
recording = LiveSessionRecording.objects.create( |
|
|
|
session=session, |
|
|
|
title=f"{session.subject} - Recording", |
|
|
|
file_time=timezone.timedelta(seconds=duration) if duration > 0 else None, |
|
|
|
file_time=duration if duration.total_seconds() > 0 else None, |
|
|
|
recording_type='video' if file_name.lower().endswith('.mp4') else 'voice' |
|
|
|
) |
|
|
|
recording.file.save(file_name, ContentFile(content), save=True) |
|
|
|
|
|
|
|
# 4. Generate thumbnail (Optional) |
|
|
|
# 5. Generate thumbnail (Optional) |
|
|
|
self._generate_video_thumbnail(tmp_file_path, recording) |
|
|
|
|
|
|
|
logger.info(f"💾 [PlugNMeet Webhook] Recording saved successfully: {recording.id}") |
|
|
|
@ -307,7 +334,7 @@ class PlugNMeetWebhookAPIView(APIView): |
|
|
|
os.unlink(tmp_file_path) |
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
logger.error(f"❌ [PlugNMeet Webhook] End Recording Error: {e}", exc_info=True) |
|
|
|
logger.error(f"❌ [PlugNMeet Webhook] RECORDING_PROCEEDED Error: {e}", exc_info=True) |
|
|
|
return {'error': str(e)} |
|
|
|
|
|
|
|
def _generate_video_thumbnail(self, video_path: str, recording: LiveSessionRecording) -> bool: |
|
|
|
|