11"""Service to notify external services when a new recording is ready."""
22
33import logging
4+ import os
45import smtplib
6+ from datetime import datetime , timezone
57
68from django .conf import settings
79from django .core .mail import send_mail
1012from django .utils .translation import gettext_lazy as _
1113
1214import requests
15+ from asgiref .sync import async_to_sync
16+ from livekit import api as livekit_api
1317
14- from core import models
18+ from core import models , utils
1519
1620logger = logging .getLogger (__name__ )
1721
@@ -130,6 +134,49 @@ def _notify_user_by_email(recording) -> bool:
130134
131135 return not has_failures
132136
137+ @staticmethod
138+ def _get_recording_timestamps (worker_id ):
139+ """Fetch FileInfo.started_at and ended_at from LiveKit's egress API.
140+
141+ FileInfo.started_at is more accurate than EgressInfo.started_at because it
142+ reflects when file recording actually began, not when the egress
143+ process was initialized.
144+
145+ Returns:
146+ Tuple of (started_at, ended_at) datetimes, either may be None.
147+ """
148+ if not worker_id :
149+ return None , None
150+
151+ @async_to_sync
152+ async def _fetch ():
153+ lkapi = utils .create_livekit_client ()
154+ try :
155+ egress_list = await lkapi .egress .list_egress (
156+ livekit_api .ListEgressRequest (egress_id = worker_id )
157+ )
158+ if egress_list .items :
159+ file_results = egress_list .items [0 ].file_results
160+ if file_results :
161+ started_at = None
162+ ended_at = None
163+ if file_results [0 ].started_at :
164+ started_at = datetime .fromtimestamp (
165+ file_results [0 ].started_at / 1e9 , tz = timezone .utc
166+ )
167+ if file_results [0 ].ended_at :
168+ ended_at = datetime .fromtimestamp (
169+ file_results [0 ].ended_at / 1e9 , tz = timezone .utc
170+ )
171+ return started_at , ended_at
172+ except Exception :
173+ logger .exception ("Could not fetch egress info for worker %s" , worker_id )
174+ finally :
175+ await lkapi .aclose ()
176+ return None , None
177+
178+ return _fetch ()
179+
133180 @staticmethod
134181 def _notify_summary_service (recording ):
135182 """Notify summary service about a new recording."""
@@ -150,24 +197,32 @@ def _notify_summary_service(recording):
150197 .first ()
151198 )
152199
200+ # TODO: change how we get metadata_filename
201+ output_folder = os .getenv ("AWS_S3_OUTPUT_FOLDER" , "metadata" )
202+ metadata_filename = f"{ output_folder } /{ recording .id } -metadata.json"
203+
153204 if not owner_access :
154205 logger .error ("No owner found for recording %s" , recording .id )
155206 return False
207+
208+ started_at , ended_at = NotificationService ._get_recording_timestamps (
209+ recording .worker_id
210+ )
211+
156212 payload = {
157213 "owner_id" : str (owner_access .user .id ),
158- "filename" : recording .key ,
214+ "recording_filename" : recording .key ,
215+ "metadata_filename" : metadata_filename ,
159216 "email" : owner_access .user .email ,
160217 "sub" : owner_access .user .sub ,
161218 "room" : recording .room .name ,
162219 "language" : recording .options .get ("language" ),
163- "recording_date" : recording .created_at .astimezone (
164- owner_access .user .timezone
165- ).strftime ("%Y-%m-%d" ),
166- "recording_time" : recording .created_at .astimezone (
167- owner_access .user .timezone
168- ).strftime ("%H:%M" ),
220+ "worker_id" : recording .worker_id ,
221+ "owner_timezone" : str (owner_access .user .timezone ),
169222 "download_link" : f"{ get_recording_download_base_url ()} /{ recording .id } " ,
170223 "context_language" : owner_access .user .language ,
224+ "recording_start_at" : (started_at .isoformat () if started_at else None ),
225+ "recording_end_at" : (ended_at .isoformat () if ended_at else None ),
171226 }
172227
173228 headers = {
0 commit comments