diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ff3daa7..bafcbd2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@ and this project adheres to
- ⬆️(agents) upgrade to python 3.14 slim
- ⬆️(dependencies) update python dependencies
+- 💥(summary) remove v1 related code #1362
+- ✨(meet) use compatible with summary v2 #1362
### Fixed
diff --git a/env.d/development/common.dist b/env.d/development/common.dist
index a045e99a..c48d903e 100644
--- a/env.d/development/common.dist
+++ b/env.d/development/common.dist
@@ -65,8 +65,9 @@ ALLOW_UNREGISTERED_ROOMS=False
RECORDING_ENABLE=True
RECORDING_STORAGE_EVENT_ENABLE=True
RECORDING_STORAGE_EVENT_TOKEN=password
-SUMMARY_SERVICE_ENDPOINT=http://app-summary-dev:8000/api/v1/tasks/
+SUMMARY_SERVICE_ENDPOINT=http://app-summary-dev:8000/api/v2/async-jobs/transcribe/
SUMMARY_SERVICE_API_TOKEN=password
+SUMMARY_SERVICE_WEBHOOK_API_TOKEN=webhook-password
RECORDING_DOWNLOAD_BASE_URL=http://localhost:3000/recording
# Recording encoding (LiveKit Egress advanced options).
diff --git a/env.d/development/summary.dist b/env.d/development/summary.dist
index 5122504a..bfd56404 100644
--- a/env.d/development/summary.dist
+++ b/env.d/development/summary.dist
@@ -30,3 +30,4 @@ POSTHOG_ENABLED="False"
# Transcription
TRANSCRIPTION_SATISFACTION_FORM_BASE_URL=
+AUTHORIZED_TENANTS='[{"id": "meet","api_key": "password","webhook_url": "https://configure-your-url.com/api/v1.0/recordings/external-process-hook/","webhook_api_key": "webhook-password","allowed_push_to_docs": true}]'
diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py
index afcea086..0722c181 100644
--- a/src/backend/core/admin.py
+++ b/src/backend/core/admin.py
@@ -11,7 +11,7 @@ from core.recording.event import notification
from . import models
from .tasks.file import process_file_deletion
-from .utils import generate_download_file_url
+from .utils import generate_download_s3_url
def hard_delete_file(file):
@@ -242,7 +242,7 @@ class FileAdmin(admin.ModelAdmin):
"""Return a clickable preview URL for the file."""
if not obj.is_ready:
return "-"
- url = generate_download_file_url(obj, expires_in=60 * 60)
+ url = generate_download_s3_url(obj.key, expires_in=60 * 60)
return format_html(
'Open File', url
diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py
index 3379a550..4a7a5637 100644
--- a/src/backend/core/api/serializers.py
+++ b/src/backend/core/api/serializers.py
@@ -563,3 +563,13 @@ class RenameParticipantSerializer(BaseValidationOnlySerializer):
"""Serializer for renaming a participant in a room."""
name = serializers.CharField(min_length=1, max_length=255, allow_blank=False)
+
+
+class ExternalProcessEventSerializer(BaseValidationOnlySerializer):
+ """Validate external process event data."""
+
+ job_id = serializers.CharField(required=True)
+ # We are not strict on purpose on those fields to avoid
+ # useless bad requests
+ type = serializers.CharField(required=False, allow_null=True, allow_blank=True)
+ status = serializers.CharField(required=False, allow_null=True, allow_blank=True)
diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py
index ee25b82d..a8def5a5 100644
--- a/src/backend/core/api/viewsets.py
+++ b/src/backend/core/api/viewsets.py
@@ -39,7 +39,10 @@ from core import analytics, enums, models, utils
from core.api.filters import ListFileFilter
from core.enums import MEDIA_STORAGE_URL_PATTERN
from core.recording.enums import FileExtension
-from core.recording.event.authentication import StorageEventAuthentication
+from core.recording.event.authentication import (
+ RecordingProcessWebhookAuthentication,
+ StorageEventAuthentication,
+)
from core.recording.event.exceptions import (
InvalidBucketError,
InvalidFilepathError,
@@ -999,6 +1002,60 @@ class RecordingViewSet(
{"message": "Event processed."},
)
+ @decorators.action(
+ detail=False,
+ methods=["post"],
+ url_path="external-process-hook",
+ authentication_classes=[RecordingProcessWebhookAuthentication],
+ serializer_class=serializers.ExternalProcessEventSerializer,
+ )
+ def on_external_process_event_received(self, request, pk=None): # pylint: disable=unused-argument
+ """Handle incoming external process events for recordings."""
+ logger.debug("Processing external process event %s", request.data)
+ serializer = self.get_serializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+
+ ok_response = drf_response.Response(
+ {"message": "Event processed."},
+ )
+
+ validated_data = serializer.validated_data
+ job_id = validated_data["job_id"]
+ try:
+ recording = models.Recording.objects.get(external_process_id=job_id)
+ except models.Recording.DoesNotExist as e:
+ logger.warning("No recording found for job_id %s: %s", job_id, e)
+ return ok_response
+
+ if validated_data.get("type") == "transcript":
+ if validated_data.get("status") == "success":
+ logger.info(
+ "External process transcript success received for recording %s",
+ job_id,
+ )
+ recording.status = (
+ models.RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL
+ )
+ recording.save()
+ return ok_response
+
+ if validated_data.get("status") == "failure":
+ logger.info(
+ "External process transcript failure received for recording %s",
+ job_id,
+ )
+ recording.status = models.RecordingStatusChoices.EXTERNAL_PROCESS_FAILED
+ recording.save()
+ return ok_response
+
+ logger.info(
+ "No changes to save for external process id %s and payload %s",
+ job_id,
+ validated_data,
+ )
+
+ return ok_response
+
def _auth_get_original_url(self, request):
"""
Extracts and parses the original URL from the "HTTP_X_ORIGINAL_URL" header.
diff --git a/src/backend/core/migrations/0021_recording_external_process_id_alter_recording_status.py b/src/backend/core/migrations/0021_recording_external_process_id_alter_recording_status.py
new file mode 100644
index 00000000..62a7fbd0
--- /dev/null
+++ b/src/backend/core/migrations/0021_recording_external_process_id_alter_recording_status.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.2.14 on 2026-06-22 08:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0020_alter_file_upload_state'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='recording',
+ name='external_process_id',
+ field=models.CharField(blank=True, help_text='ID of the external process associated with the recording.', max_length=255, null=True, unique=True, verbose_name='External Process ID'),
+ ),
+ migrations.AlterField(
+ model_name='recording',
+ name='status',
+ field=models.CharField(choices=[('initiated', 'Initiated'), ('active', 'Active'), ('stopped', 'Stopped'), ('saved', 'Saved'), ('aborted', 'Aborted'), ('failed_to_start', 'Failed to Start'), ('failed_to_stop', 'Failed to Stop'), ('notification_succeeded', 'Notification succeeded'), ('external_process_successful', 'External process successful'), ('external_process_failed', 'External process failed')], default='initiated', max_length=50),
+ ),
+ ]
diff --git a/src/backend/core/models.py b/src/backend/core/models.py
index 1a7ec3c9..fbfcf6be 100644
--- a/src/backend/core/models.py
+++ b/src/backend/core/models.py
@@ -60,6 +60,11 @@ class RecordingStatusChoices(models.TextChoices):
FAILED_TO_START = "failed_to_start", _("Failed to Start")
FAILED_TO_STOP = "failed_to_stop", _("Failed to Stop")
NOTIFICATION_SUCCEEDED = "notification_succeeded", _("Notification succeeded")
+ EXTERNAL_PROCESS_SUCCESSFUL = (
+ "external_process_successful",
+ _("External process successful"),
+ )
+ EXTERNAL_PROCESS_FAILED = "external_process_failed", _("External process failed")
@classmethod
def is_final(cls, status):
@@ -73,6 +78,8 @@ class RecordingStatusChoices(models.TextChoices):
cls.STOPPED,
cls.SAVED,
cls.ABORTED,
+ cls.EXTERNAL_PROCESS_SUCCESSFUL,
+ cls.EXTERNAL_PROCESS_FAILED,
cls.FAILED_TO_START,
cls.FAILED_TO_STOP,
}
@@ -598,6 +605,14 @@ class Recording(BaseModel):
verbose_name=_("Recording options"),
help_text=_("Recording options"),
)
+ external_process_id = models.CharField(
+ max_length=255,
+ null=True,
+ blank=True,
+ unique=True,
+ verbose_name=_("External Process ID"),
+ help_text=_("ID of the external process associated with the recording."),
+ )
class Meta:
db_table = "meet_recording"
@@ -653,6 +668,8 @@ class Recording(BaseModel):
return self.status in {
RecordingStatusChoices.NOTIFICATION_SUCCEEDED,
RecordingStatusChoices.SAVED,
+ RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL,
+ RecordingStatusChoices.EXTERNAL_PROCESS_FAILED,
}
@property
diff --git a/src/backend/core/recording/event/authentication.py b/src/backend/core/recording/event/authentication.py
index 28551468..a7f08522 100644
--- a/src/backend/core/recording/event/authentication.py
+++ b/src/backend/core/recording/event/authentication.py
@@ -14,9 +14,9 @@ logger = logging.getLogger(__name__)
class MachineUser:
"""Represent a non-interactive system user for automated storage operations."""
- def __init__(self) -> None:
+ def __init__(self, username: str = "storage_event_user") -> None:
self.pk = None
- self.username = "storage_event_user"
+ self.username = username
self.is_active = True
@property
@@ -91,3 +91,46 @@ class StorageEventAuthentication(BaseAuthentication):
def authenticate_header(self, request):
"""Return the WWW-Authenticate header value."""
return f"{self.TOKEN_TYPE} realm='Storage event API'"
+
+
+class RecordingProcessWebhookAuthentication(BaseAuthentication):
+ """
+ Custom authentication class for recording process webhook requests.
+ Validates the API key in the Authorization header.
+ """
+
+ AUTH_HEADER = "Authorization"
+ TOKEN_TYPE = "Bearer" # noqa S105
+
+ def authenticate(self, request):
+ """
+ Authenticate the request and return a two-tuple of (user, token).
+ """
+ required_token = settings.SUMMARY_SERVICE_WEBHOOK_API_TOKEN
+ if not required_token:
+ raise AuthenticationFailed("Webhook authentication is not configured.")
+
+ auth_header: str = request.headers.get("Authorization") or ""
+ if not auth_header.startswith("Bearer "):
+ logger.warning(
+ "Authentication failed: Invalid authorization header format (ip: %s)",
+ request.META.get("REMOTE_ADDR"),
+ )
+ raise AuthenticationFailed("Invalid authorization header format.")
+ token = auth_header[7:] # len("Bearer ") == 7
+
+ if not secrets.compare_digest(
+ token,
+ required_token,
+ ):
+ logger.warning(
+ "Authentication failed: Bad Authorization header (ip: %s)",
+ request.META.get("REMOTE_ADDR"),
+ )
+ raise AuthenticationFailed()
+
+ return MachineUser("external_process_user"), None
+
+ def authenticate_header(self, request):
+ """Return the WWW-Authenticate header value."""
+ return f"{self.TOKEN_TYPE} realm='External process webhook API'"
diff --git a/src/backend/core/recording/event/notification.py b/src/backend/core/recording/event/notification.py
index 2d389c4c..cdbc253d 100644
--- a/src/backend/core/recording/event/notification.py
+++ b/src/backend/core/recording/event/notification.py
@@ -4,6 +4,7 @@ import asyncio
import logging
import smtplib
from datetime import datetime, timezone
+from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from django.conf import settings
from django.core.mail import send_mail
@@ -17,6 +18,7 @@ from asgiref.sync import async_to_sync
from livekit import api as livekit_api
from core import models, utils
+from core.utils import generate_download_s3_url
logger = logging.getLogger(__name__)
@@ -178,8 +180,49 @@ class NotificationService:
return _ns_to_utc(file_result.started_at), _ns_to_utc(file_result.ended_at)
+ @staticmethod
+ def _generate_title(
+ *,
+ locale: str,
+ room: str,
+ recording_datetime: datetime | None,
+ owner_timezone: str | None,
+ ) -> str:
+ """Generate title from context or return default."""
+ if recording_datetime is None:
+ with override(locale):
+ return _("Transcription")
+
+ dt = recording_datetime
+ if owner_timezone:
+ try:
+ dt = recording_datetime.astimezone(ZoneInfo(owner_timezone))
+ except (KeyError, ZoneInfoNotFoundError):
+ pass # Keep the original UTC datetime
+
+ with override(locale):
+ translated_template = _(
+ 'Meeting "{room}" on {room_recording_date} at {room_recording_time}'
+ )
+ return translated_template.format(
+ room=room,
+ room_recording_date=dt.strftime("%Y-%m-%d"),
+ room_recording_time=dt.strftime("%H:%M"),
+ )
+
@staticmethod
def _notify_summary_service(recording: models.Recording):
+ if settings.SUMMARY_SERVICE_VERSION == 1:
+ return NotificationService._notify_summary_service_v1(recording)
+ if settings.SUMMARY_SERVICE_VERSION == 2:
+ return NotificationService._notify_summary_service_v2(recording)
+
+ raise NotImplementedError(
+ f"Unknown summary service version: {settings.SUMMARY_SERVICE_VERSION}"
+ )
+
+ @staticmethod
+ def _notify_summary_service_v1(recording: models.Recording):
"""Notify summary service about a new recording."""
if (
@@ -253,5 +296,119 @@ class NotificationService:
return True
+ @staticmethod
+ def _notify_summary_service_v2(recording: models.Recording):
+ """Notify summary service about a new recording."""
+
+ if (
+ not settings.SUMMARY_SERVICE_ENDPOINT
+ or not settings.SUMMARY_SERVICE_API_TOKEN
+ ):
+ logger.error("Summary service not configured")
+ return False
+
+ owner_access = (
+ models.RecordingAccess.objects.select_related("user")
+ .filter(
+ role=models.RoleChoices.OWNER,
+ recording_id=recording.id,
+ )
+ .first()
+ )
+ metadata_filename: None | str = None
+ if settings.METADATA_COLLECTOR_ENABLED and recording.options.get(
+ "collect_metadata", False
+ ):
+ output_folder = settings.METADATA_COLLECTOR_OUTPUT_FOLDER
+ metadata_filename = f"{output_folder}/{recording.id}-metadata.json"
+
+ if not owner_access:
+ logger.error("No owner found for recording %s", recording.id)
+ return False
+
+ started_at, ended_at = async_to_sync(
+ NotificationService._get_recording_timestamps
+ )(recording.worker_id)
+
+ form_base_url = settings.TRANSCRIPTION_SATISFACTION_FORM_BASE_URL
+ form_link = (
+ f"{form_base_url}?room_id={recording.room.id}"
+ if (form_base_url and metadata_filename is not None)
+ else None
+ )
+ metadata_payload = None
+ if started_at and ended_at and metadata_filename:
+ metadata_payload = {
+ "cloud_storage_url": generate_download_s3_url(
+ metadata_filename,
+ expires_in=settings.SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS,
+ override_domain=False,
+ ),
+ "started_at": started_at.isoformat(),
+ "ended_at": ended_at.isoformat(),
+ }
+
+ payload = {
+ "user_sub": owner_access.user.sub,
+ "user_email": owner_access.user.email,
+ "cloud_storage_url": generate_download_s3_url(
+ recording.key,
+ expires_in=settings.SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS,
+ override_domain=False,
+ ),
+ "language": recording.options.get(
+ "language", get_language().split("-")[0].lower()
+ ),
+ "context_language": owner_access.user.language,
+ "push_to_docs_config": {
+ "user_email": owner_access.user.email,
+ "title": NotificationService._generate_title(
+ locale=owner_access.user.language
+ or recording.options.get("language", get_language()),
+ room=recording.room.name,
+ recording_datetime=started_at,
+ owner_timezone=str(owner_access.user.timezone),
+ ),
+ "download_link": f"{get_recording_download_base_url()}/{recording.id}",
+ "form_link": form_link,
+ # For now the feature flag logic is handled on summary side
+ "auto_create_summary": True,
+ },
+ "metadata": metadata_payload,
+ }
+
+ headers = {
+ "Content-Type": "application/json",
+ "Authorization": f"Bearer {settings.SUMMARY_SERVICE_API_TOKEN}",
+ }
+
+ try:
+ response = requests.post(
+ settings.SUMMARY_SERVICE_ENDPOINT,
+ json=payload,
+ headers=headers,
+ timeout=30,
+ )
+ response.raise_for_status()
+ response_json = response.json()
+ # We do not require a job_id to avoid a breaking change
+ job_id = response_json.get("job_id")
+ if not isinstance(job_id, str):
+ raise ValueError("job_id is not a string")
+
+ recording.external_process_id = job_id
+ recording.save()
+
+ except requests.RequestException as exc:
+ logger.exception(
+ "Summary service error for recording %s. URL: %s. Exception: %s",
+ recording.id,
+ settings.SUMMARY_SERVICE_ENDPOINT,
+ exc,
+ )
+ return False
+
+ return True
+
notification_service = NotificationService()
diff --git a/src/backend/core/tests/recording/event/test_notification.py b/src/backend/core/tests/recording/event/test_notification.py
index 4198f1e4..1d50a4f1 100644
--- a/src/backend/core/tests/recording/event/test_notification.py
+++ b/src/backend/core/tests/recording/event/test_notification.py
@@ -243,3 +243,168 @@ def test_notify_user_by_email_smtp_exception(mocked_current_site, caplog):
assert result is False
assert mock_send_mail.call_count == 2
assert "notification could not be sent:" in caplog.text
+
+
+@mock.patch("core.recording.event.notification.requests.post")
+@mock.patch("core.recording.event.notification.generate_download_s3_url")
+@mock.patch.object(
+ NotificationService, "_get_recording_timestamps", new_callable=mock.AsyncMock
+)
+def test_notify_summary_service_post_args_with_metadata(
+ mock_get_recording_timestamps,
+ mock_generate_download_s3_url,
+ mock_post,
+ settings,
+):
+ """Test summary notification computed request args when metadata is enabled."""
+ settings.SUMMARY_SERVICE_VERSION = 2
+ settings.SUMMARY_SERVICE_ENDPOINT = "https://summary.test/api/v2/tasks"
+ settings.SUMMARY_SERVICE_API_TOKEN = "summary-token"
+ settings.RECORDING_DOWNLOAD_BASE_URL = "https://app.test/recordings"
+ settings.SCREEN_RECORDING_BASE_URL = None
+ settings.METADATA_COLLECTOR_ENABLED = True
+ settings.METADATA_COLLECTOR_OUTPUT_FOLDER = "recordings-metadata"
+
+ recording = factories.RecordingFactory(
+ room__name="Engineering Sync",
+ worker_id="egress-1",
+ options={"collect_metadata": True, "language": "en-us"},
+ )
+ owner = factories.UserFactory(
+ email="owner@test.com",
+ sub="owner-sub",
+ language="fr-fr",
+ timezone="Europe/Paris",
+ )
+ factories.UserRecordingAccessFactory(
+ recording=recording, role=models.RoleChoices.OWNER, user=owner
+ )
+
+ started_at = datetime.datetime(2026, 1, 2, 10, 30, tzinfo=datetime.timezone.utc)
+ ended_at = datetime.datetime(2026, 1, 2, 11, 45, tzinfo=datetime.timezone.utc)
+ mock_get_recording_timestamps.return_value = (started_at, ended_at)
+ mock_generate_download_s3_url.side_effect = [
+ "https://storage.test/metadata.json",
+ "https://storage.test/recording.ogg",
+ ]
+
+ mock_response = mock.Mock()
+ mock_response.raise_for_status.return_value = None
+ mock_response.json.return_value = {"job_id": "job-42"}
+ mock_post.return_value = mock_response
+
+ result = NotificationService._notify_summary_service(recording)
+ recording.refresh_from_db()
+
+ assert result is True
+ assert recording.external_process_id == "job-42"
+
+ metadata_filename = (
+ f"{settings.METADATA_COLLECTOR_OUTPUT_FOLDER}/{recording.id}-metadata.json"
+ )
+ expected_payload = {
+ "user_sub": owner.sub,
+ "user_email": owner.email,
+ "cloud_storage_url": "https://storage.test/recording.ogg",
+ "language": "en-us",
+ "context_language": owner.language,
+ "push_to_docs_config": {
+ "user_email": owner.email,
+ "title": 'Réunion "Engineering Sync" du 2026-01-02 à 11:30',
+ "download_link": f"{settings.RECORDING_DOWNLOAD_BASE_URL}/{recording.id}",
+ "auto_create_summary": True,
+ "form_link": None,
+ },
+ "metadata": {
+ "cloud_storage_url": "https://storage.test/metadata.json",
+ "started_at": started_at.isoformat(),
+ "ended_at": ended_at.isoformat(),
+ },
+ }
+ expected_headers = {
+ "Content-Type": "application/json",
+ "Authorization": "Bearer summary-token",
+ }
+ mock_post.assert_called_once_with(
+ "https://summary.test/api/v2/tasks",
+ json=expected_payload,
+ headers=expected_headers,
+ timeout=30,
+ )
+ assert mock_generate_download_s3_url.call_args_list == [
+ mock.call(metadata_filename, expires_in=60 * 60 * 24, override_domain=False),
+ mock.call(recording.key, expires_in=60 * 60 * 24, override_domain=False),
+ ]
+ mock_get_recording_timestamps.assert_awaited_once_with("egress-1")
+
+
+@mock.patch("core.recording.event.notification.requests.post")
+@mock.patch("core.recording.event.notification.generate_download_s3_url")
+@mock.patch.object(
+ NotificationService, "_get_recording_timestamps", new_callable=mock.AsyncMock
+)
+def test_notify_summary_service_post_args_without_metadata(
+ mock_get_recording_timestamps,
+ mock_generate_download_s3_url,
+ mock_post,
+ settings,
+):
+ """Test summary notification computed request args when metadata is not available."""
+ settings.SUMMARY_SERVICE_VERSION = 2
+ settings.SUMMARY_SERVICE_ENDPOINT = "https://summary.test/api/v2/tasks"
+ settings.SUMMARY_SERVICE_API_TOKEN = "summary-token"
+ settings.RECORDING_DOWNLOAD_BASE_URL = "https://app.test/recordings"
+ settings.SCREEN_RECORDING_BASE_URL = None
+ settings.METADATA_COLLECTOR_ENABLED = False
+
+ recording = factories.RecordingFactory(room__name="Daily")
+ owner = factories.UserFactory(
+ email="owner@test.com",
+ sub="owner-sub",
+ language="en-us",
+ timezone="UTC",
+ )
+ factories.UserRecordingAccessFactory(
+ recording=recording, role=models.RoleChoices.OWNER, user=owner
+ )
+
+ mock_get_recording_timestamps.return_value = (None, None)
+ mock_generate_download_s3_url.return_value = "https://storage.test/recording.mp4"
+
+ mock_response = mock.Mock()
+ mock_response.raise_for_status.return_value = None
+ mock_response.json.return_value = {"job_id": "job-51"}
+ mock_post.return_value = mock_response
+
+ result = NotificationService._notify_summary_service(recording)
+
+ assert result is True
+ expected_payload = {
+ "user_sub": owner.sub,
+ "user_email": owner.email,
+ "cloud_storage_url": "https://storage.test/recording.mp4",
+ "language": "en",
+ "context_language": owner.language,
+ "push_to_docs_config": {
+ "user_email": owner.email,
+ "title": "Transcription",
+ "download_link": f"{settings.RECORDING_DOWNLOAD_BASE_URL}/{recording.id}",
+ "auto_create_summary": True,
+ "form_link": None,
+ },
+ "metadata": None,
+ }
+ expected_headers = {
+ "Content-Type": "application/json",
+ "Authorization": "Bearer summary-token",
+ }
+ mock_post.assert_called_once_with(
+ "https://summary.test/api/v2/tasks",
+ json=expected_payload,
+ headers=expected_headers,
+ timeout=30,
+ )
+ mock_generate_download_s3_url.assert_called_once_with(
+ recording.key, expires_in=60 * 60 * 24, override_domain=False
+ )
+ mock_get_recording_timestamps.assert_awaited_once_with(recording.worker_id)
diff --git a/src/backend/core/tests/recording/test_api_recordings_external_process_hook.py b/src/backend/core/tests/recording/test_api_recordings_external_process_hook.py
new file mode 100644
index 00000000..71aae54d
--- /dev/null
+++ b/src/backend/core/tests/recording/test_api_recordings_external_process_hook.py
@@ -0,0 +1,134 @@
+"""
+Test recordings API endpoints: external process hook.
+"""
+
+# pylint: disable=redefined-outer-name,unused-argument
+
+import pytest
+
+from ...factories import RecordingFactory
+from ...models import RecordingStatusChoices
+
+pytestmark = pytest.mark.django_db
+
+
+@pytest.fixture
+def external_process_settings(settings):
+ """Configure authentication token for the external process webhook."""
+ settings.SUMMARY_SERVICE_WEBHOOK_API_TOKEN = "testWebhookToken"
+ return settings
+
+
+def test_external_process_event_missing_authorization_header(
+ external_process_settings, client
+):
+ """Requests without authorization must be rejected."""
+ response = client.post(
+ "/api/v1.0/recordings/external-process-hook/",
+ {"job_id": "job-1", "type": "transcript", "status": "success"},
+ )
+
+ assert response.status_code == 401
+
+
+def test_external_process_event_wrong_bearer_token(external_process_settings, client):
+ """Requests with invalid bearer token must be rejected."""
+ response = client.post(
+ "/api/v1.0/recordings/external-process-hook/",
+ {"job_id": "job-1", "type": "transcript", "status": "success"},
+ HTTP_AUTHORIZATION="Bearer wrongToken",
+ )
+
+ assert response.status_code == 401
+
+
+def test_external_process_event_missing_job_id(external_process_settings, client):
+ """Payload without job_id must fail validation."""
+ response = client.post(
+ "/api/v1.0/recordings/external-process-hook/",
+ {"type": "transcript", "status": "success"},
+ HTTP_AUTHORIZATION="Bearer testWebhookToken",
+ )
+
+ assert response.status_code == 400
+ assert response.json() == {"job_id": ["This field is required."]}
+
+
+def test_external_process_event_success_updates_recording_status(
+ external_process_settings, client
+):
+ """A successful transcript process should update recording status."""
+ recording = RecordingFactory(
+ status=RecordingStatusChoices.SAVED,
+ external_process_id="job-123",
+ )
+
+ response = client.post(
+ "/api/v1.0/recordings/external-process-hook/",
+ {"job_id": "job-123", "type": "transcript", "status": "success"},
+ HTTP_AUTHORIZATION="Bearer testWebhookToken",
+ )
+
+ assert response.status_code == 200
+ assert response.json() == {"message": "Event processed."}
+
+ recording.refresh_from_db()
+ assert recording.status == RecordingStatusChoices.EXTERNAL_PROCESS_SUCCESSFUL
+
+
+def test_external_process_event_failure_updates_recording_status(
+ external_process_settings, client
+):
+ """A failing transcript process should update recording status."""
+ recording = RecordingFactory(
+ status=RecordingStatusChoices.SAVED,
+ external_process_id="job-456",
+ )
+
+ response = client.post(
+ "/api/v1.0/recordings/external-process-hook/",
+ {"job_id": "job-456", "type": "transcript", "status": "failure"},
+ HTTP_AUTHORIZATION="Bearer testWebhookToken",
+ )
+
+ assert response.status_code == 200
+ assert response.json() == {"message": "Event processed."}
+
+ recording.refresh_from_db()
+ assert recording.status == RecordingStatusChoices.EXTERNAL_PROCESS_FAILED
+
+
+def test_external_process_event_unknown_recording_is_ignored(
+ external_process_settings, client
+):
+ """Unknown job_id should not fail the webhook processing."""
+ response = client.post(
+ "/api/v1.0/recordings/external-process-hook/",
+ {"job_id": "missing-job", "type": "transcript", "status": "success"},
+ HTTP_AUTHORIZATION="Bearer testWebhookToken",
+ )
+
+ assert response.status_code == 200
+ assert response.json() == {"message": "Event processed."}
+
+
+def test_external_process_event_non_transcript_event_does_not_change_status(
+ external_process_settings, client
+):
+ """Only transcript events should update recording status."""
+ recording = RecordingFactory(
+ status=RecordingStatusChoices.SAVED,
+ external_process_id="job-789",
+ )
+
+ response = client.post(
+ "/api/v1.0/recordings/external-process-hook/",
+ {"job_id": "job-789", "type": "thumbnail", "status": "success"},
+ HTTP_AUTHORIZATION="Bearer testWebhookToken",
+ )
+
+ assert response.status_code == 200
+ assert response.json() == {"message": "Event processed."}
+
+ recording.refresh_from_db()
+ assert recording.status == RecordingStatusChoices.SAVED
diff --git a/src/backend/core/utils.py b/src/backend/core/utils.py
index 6951a99d..8a8e09fa 100644
--- a/src/backend/core/utils.py
+++ b/src/backend/core/utils.py
@@ -459,12 +459,14 @@ def generate_upload_policy(file):
return policy
-def generate_download_file_url(file, *, expires_in: int, override_domain: bool = True):
+def generate_download_s3_url(
+ key: str, *, expires_in: int, override_domain: bool = True
+):
"""
- Generate a S3 signed download url for a given file.
+ Generate a S3 signed download url for a given key.
"""
-
- key = file.file_key
+ if not key:
+ raise ValueError("key cannot be empty")
# This setting should be used if the backend application and the frontend application
# can't connect to the object storage with the same domain. This is the case in the
diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po
index 5ca75a87..ec7e4c55 100644
--- a/src/backend/locale/de_DE/LC_MESSAGES/django.po
+++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-04-21 14:09+0000\n"
+"POT-Creation-Date: 2026-07-02 10:47+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,70 +17,92 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: core/admin.py:29
+#: core/admin.py:67
msgid "Personal info"
msgstr "Persönliche Informationen"
-#: core/admin.py:42
+#: core/admin.py:80
msgid "Permissions"
msgstr "Berechtigungen"
-#: core/admin.py:54
+#: core/admin.py:92
msgid "Important dates"
msgstr "Wichtige Daten"
-#: core/admin.py:132 core/admin.py:275
+#: core/admin.py:206
+msgid "Content"
+msgstr ""
+
+#: core/admin.py:217
+#, fuzzy
+#| msgid "Delete rooms"
+msgid "Deletion"
+msgstr "Räume löschen"
+
+#: core/admin.py:226
+msgid "Derived info"
+msgstr ""
+
+#: core/admin.py:237
+msgid "Timestamps"
+msgstr ""
+
+#: core/admin.py:240
+msgid "File preview"
+msgstr ""
+
+#: core/admin.py:300 core/admin.py:443
msgid "No owner"
msgstr "Kein Eigentümer"
-#: core/admin.py:135 core/admin.py:278
+#: core/admin.py:303 core/admin.py:446
msgid "Multiple owners"
msgstr "Mehrere Eigentümer"
-#: core/admin.py:148
+#: core/admin.py:316
msgid "Resend notification to external service"
msgstr "Benachrichtigung erneut an externen Dienst senden"
-#: core/admin.py:171
+#: core/admin.py:339
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Benachrichtigung für Aufnahme %(id)s fehlgeschlagen"
-#: core/admin.py:179
+#: core/admin.py:347
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Benachrichtigung für Aufnahme %(id)s fehlgeschlagen: %(error)s"
-#: core/admin.py:187
+#: core/admin.py:355
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Benachrichtigungen für %(count)s Aufnahme(n) erfolgreich gesendet."
-#: core/admin.py:195
+#: core/admin.py:363
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s abgelaufene Aufnahme(n) übersprungen."
-#: core/admin.py:200
+#: core/admin.py:368
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Ausgewählte Aufnahmen als ‚Fehler beim Stoppen‘ markieren"
-#: core/admin.py:218
+#: core/admin.py:386
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr "%(count)s Aufnahme(n) erfolgreich als ‚Fehler beim Stoppen‘ markiert."
-#: core/admin.py:226
+#: core/admin.py:394
#, fuzzy, python-format
#| msgid "Skipped %(count)s expired recording(s)."
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "%(count)s abgelaufene Aufnahme(n) übersprungen."
-#: core/admin.py:342
+#: core/admin.py:510
msgid "No scopes"
msgstr "Keine Scopes"
-#: core/admin.py:344
+#: core/admin.py:512
msgid "Scopes"
msgstr "Scopes"
@@ -88,17 +110,17 @@ msgstr "Scopes"
msgid "Creator is me"
msgstr "Ersteller bin ich"
-#: core/api/serializers.py:88
+#: core/api/serializers.py:89
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Sie müssen Administrator oder Eigentümer eines Raums sein, um Zugriffe "
"hinzuzufügen."
-#: core/api/serializers.py:509
+#: core/api/serializers.py:534
msgid "This file extension is not allowed."
msgstr "Diese Dateiendung ist nicht erlaubt."
-#: core/api/viewsets.py:1090
+#: core/api/viewsets.py:1222
msgid "You have reached the maximum number of files for this type."
msgstr "Sie haben die maximale Anzahl an Dateien dieses Typs erreicht."
@@ -146,51 +168,59 @@ msgstr "Stopp fehlgeschlagen"
msgid "Notification succeeded"
msgstr "Benachrichtigung erfolgreich"
-#: core/models.py:89
+#: core/models.py:65
+msgid "External process successful"
+msgstr "Externer Prozess erfolgreich"
+
+#: core/models.py:67
+msgid "External process failed"
+msgstr "Externer Prozess fehlgeschlagen"
+
+#: core/models.py:96
msgid "SCREEN_RECORDING"
msgstr "BILDSCHIRMAUFZEICHNUNG"
-#: core/models.py:90
+#: core/models.py:97
msgid "TRANSCRIPT"
msgstr "TRANSKRIPT"
-#: core/models.py:96
+#: core/models.py:103
msgid "Public Access"
msgstr "Öffentlicher Zugriff"
-#: core/models.py:97
+#: core/models.py:104
msgid "Trusted Access"
msgstr "Vertrauenswürdiger Zugriff"
-#: core/models.py:98
+#: core/models.py:105
msgid "Restricted Access"
msgstr "Eingeschränkter Zugriff"
-#: core/models.py:110
+#: core/models.py:117
msgid "id"
msgstr "ID"
-#: core/models.py:111
+#: core/models.py:118
msgid "primary key for the record as UUID"
msgstr "Primärschlüssel des Eintrags als UUID"
-#: core/models.py:117
+#: core/models.py:124
msgid "created on"
msgstr "erstellt am"
-#: core/models.py:118
+#: core/models.py:125
msgid "date and time at which a record was created"
msgstr "Datum und Uhrzeit der Erstellung eines Eintrags"
-#: core/models.py:123
+#: core/models.py:130
msgid "updated on"
msgstr "aktualisiert am"
-#: core/models.py:124
+#: core/models.py:131
msgid "date and time at which a record was last updated"
msgstr "Datum und Uhrzeit der letzten Aktualisierung eines Eintrags"
-#: core/models.py:144
+#: core/models.py:151
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -198,11 +228,11 @@ msgstr ""
"Geben Sie einen gültigen Sub ein. Dieser Wert darf nur Buchstaben, Zahlen "
"und die Zeichen @/./+/-/_ enthalten."
-#: core/models.py:150
+#: core/models.py:157
msgid "sub"
msgstr "Sub"
-#: core/models.py:152
+#: core/models.py:159
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -210,55 +240,55 @@ msgstr ""
"Optional für ausstehende Benutzer; erforderlich nach Kontoaktivierung. "
"Maximal 255 Zeichen. Nur Buchstaben, Zahlen und @/./+/-/_ Zeichen erlaubt."
-#: core/models.py:161
+#: core/models.py:168
msgid "identity email address"
msgstr "Identitäts-E-Mail-Adresse"
-#: core/models.py:166
+#: core/models.py:173
msgid "admin email address"
msgstr "Administrator-E-Mail-Adresse"
-#: core/models.py:168
+#: core/models.py:175
msgid "full name"
msgstr "Vollständiger Name"
-#: core/models.py:170
+#: core/models.py:177
msgid "short name"
msgstr "Kurzname"
-#: core/models.py:176
+#: core/models.py:183
msgid "language"
msgstr "Sprache"
-#: core/models.py:177
+#: core/models.py:184
msgid "The language in which the user wants to see the interface."
msgstr "Die Sprache, in der der Benutzer die Oberfläche sehen möchte."
-#: core/models.py:183
+#: core/models.py:190
msgid "The timezone in which the user wants to see times."
msgstr "Die Zeitzone, in der der Benutzer die Zeiten sehen möchte."
-#: core/models.py:186
+#: core/models.py:193
msgid "device"
msgstr "Gerät"
-#: core/models.py:188
+#: core/models.py:195
msgid "Whether the user is a device or a real user."
msgstr "Ob es sich um ein Gerät oder einen echten Benutzer handelt."
-#: core/models.py:191
+#: core/models.py:198
msgid "staff status"
msgstr "Mitarbeiterstatus"
-#: core/models.py:193
+#: core/models.py:200
msgid "Whether the user can log into this admin site."
msgstr "Ob der Benutzer sich bei dieser Admin-Seite anmelden kann."
-#: core/models.py:196
+#: core/models.py:203
msgid "active"
msgstr "aktiv"
-#: core/models.py:199
+#: core/models.py:206
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -266,66 +296,66 @@ msgstr ""
"Ob dieser Benutzer als aktiv behandelt werden soll. Deaktivieren Sie dies "
"anstelle des Löschens des Kontos."
-#: core/models.py:212
+#: core/models.py:219
msgid "user"
msgstr "Benutzer"
-#: core/models.py:213
+#: core/models.py:220
msgid "users"
msgstr "Benutzer"
-#: core/models.py:272
+#: core/models.py:286
msgid "Resource"
msgstr "Ressource"
-#: core/models.py:273
+#: core/models.py:287
msgid "Resources"
msgstr "Ressourcen"
-#: core/models.py:331
+#: core/models.py:345
msgid "Resource access"
msgstr "Ressourcenzugriff"
-#: core/models.py:332
+#: core/models.py:346
msgid "Resource accesses"
msgstr "Ressourcenzugriffe"
-#: core/models.py:338
+#: core/models.py:352
msgid "Resource access with this User and Resource already exists."
msgstr ""
"Ein Ressourcenzugriff mit diesem Benutzer und dieser Ressource existiert "
"bereits."
-#: core/models.py:394
+#: core/models.py:409
msgid "Visio room configuration"
msgstr "Visio-Raumkonfiguration"
-#: core/models.py:395
+#: core/models.py:410
msgid "Values for Visio parameters to configure the room."
msgstr "Werte für Visio-Parameter zur Konfiguration des Raums."
-#: core/models.py:402
+#: core/models.py:417
msgid "Room PIN code"
msgstr "PIN-Code für den Raum"
-#: core/models.py:403
+#: core/models.py:418
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Eindeutiger n-stelliger Code, der diesen Raum im Telephonmodus identifiziert."
-#: core/models.py:409 core/models.py:563
+#: core/models.py:424 core/models.py:578
msgid "Room"
msgstr "Raum"
-#: core/models.py:410
+#: core/models.py:425
msgid "Rooms"
msgstr "Räume"
-#: core/models.py:574
+#: core/models.py:589
msgid "Worker ID"
msgstr "Worker-ID"
-#: core/models.py:576
+#: core/models.py:591
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -334,141 +364,153 @@ msgstr ""
"erhalten, auch wenn der Worker stoppt, was ein einfaches Nachverfolgen "
"ermöglicht."
-#: core/models.py:584
+#: core/models.py:599
msgid "Recording mode"
msgstr "Aufzeichnungsmodus"
-#: core/models.py:585
+#: core/models.py:600
msgid "Defines the mode of recording being called."
msgstr "Definiert den aufgerufenen Aufzeichnungsmodus."
-#: core/models.py:590 core/models.py:591
+#: core/models.py:605 core/models.py:606
msgid "Recording options"
msgstr "Aufnahmeoptionen"
-#: core/models.py:597
+#: core/models.py:613
+msgid "External Process ID"
+msgstr "External Process ID"
+
+#: core/models.py:614
+msgid "ID of the external process associated with the recording."
+msgstr "ID des externen Prozesses, der mit der Aufzeichnung verknüpft ist"
+
+#: core/models.py:620
msgid "Recording"
msgstr "Aufzeichnung"
-#: core/models.py:598
+#: core/models.py:621
msgid "Recordings"
msgstr "Aufzeichnungen"
-#: core/models.py:706
+#: core/models.py:731
msgid "Recording/user relation"
msgstr "Beziehung Aufzeichnung/Benutzer"
-#: core/models.py:707
+#: core/models.py:732
msgid "Recording/user relations"
msgstr "Beziehungen Aufzeichnung/Benutzer"
-#: core/models.py:713
+#: core/models.py:738
msgid "This user is already in this recording."
msgstr "Dieser Benutzer ist bereits Teil dieser Aufzeichnung."
-#: core/models.py:719
+#: core/models.py:744
msgid "This team is already in this recording."
msgstr "Dieses Team ist bereits Teil dieser Aufzeichnung."
-#: core/models.py:725
+#: core/models.py:750
msgid "Either user or team must be set, not both."
msgstr "Entweder Benutzer oder Team muss festgelegt werden, nicht beides."
-#: core/models.py:742
+#: core/models.py:767
msgid "Create rooms"
msgstr "Räume erstellen"
-#: core/models.py:743
+#: core/models.py:768
msgid "List rooms"
msgstr "Räume auflisten"
-#: core/models.py:744
+#: core/models.py:769
msgid "Retrieve room details"
msgstr "Raumdetails abrufen"
-#: core/models.py:745
+#: core/models.py:770
msgid "Update rooms"
msgstr "Räume aktualisieren"
-#: core/models.py:746
+#: core/models.py:771
msgid "Delete rooms"
msgstr "Räume löschen"
-#: core/models.py:759
+#: core/models.py:784
msgid "Application name"
msgstr "Anwendungsname"
-#: core/models.py:760
+#: core/models.py:785
msgid "Descriptive name for this application."
msgstr "Beschreibender Name für diese Anwendung."
-#: core/models.py:770
+#: core/models.py:795
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Beim Speichern gehasht. Jetzt kopieren, wenn dies ein neues Geheimnis ist."
-#: core/models.py:781
+#: core/models.py:806
msgid "Application"
msgstr "Anwendung"
-#: core/models.py:782
+#: core/models.py:807
msgid "Applications"
msgstr "Anwendungen"
-#: core/models.py:805
+#: core/models.py:830
msgid "Enter a valid domain"
msgstr "Geben Sie eine gültige Domain ein"
-#: core/models.py:808
+#: core/models.py:833
msgid "Domain"
msgstr "Domain"
-#: core/models.py:809
+#: core/models.py:834
msgid "Email domain this application can act on behalf of."
msgstr "E-Mail-Domain, im Namen der diese Anwendung handeln kann."
-#: core/models.py:821
+#: core/models.py:846
msgid "Application domain"
msgstr "Anwendungsdomain"
-#: core/models.py:822
+#: core/models.py:847
msgid "Application domains"
msgstr "Anwendungsdomains"
-#: core/models.py:840
+#: core/models.py:865
msgid "Pending"
msgstr "Ausstehend"
-#: core/models.py:848
+#: core/models.py:866
+msgid "Analyzing"
+msgstr ""
+
+#: core/models.py:873
msgid "Ready"
msgstr "Bereit"
-#: core/models.py:854
+#: core/models.py:879
msgid "Background image"
msgstr "Hintergrundbild"
-#: core/models.py:866
+#: core/models.py:891
msgid "title"
msgstr "Titel"
-#: core/models.py:890
+#: core/models.py:915
msgid "Malware detection info when the analysis status is unsafe."
msgstr ""
"Informationen zur Malware-Erkennung, wenn der Analyse-Status unsicher ist."
-#: core/models.py:895
+#: core/models.py:920
msgid "File"
msgstr "Datei"
-#: core/models.py:896
+#: core/models.py:921
msgid "Files"
msgstr "Dateien"
-#: core/models.py:1000
+#: core/models.py:1041
msgid "This file is already hard deleted."
msgstr "Diese Datei wurde bereits endgültig gelöscht."
-#: core/models.py:1010
+#: core/models.py:1051
#, fuzzy
#| msgid "To hard delete a file, it must first be soft deleted."
msgid "To hard delete a file, it must first be soft deleted."
@@ -476,10 +518,20 @@ msgstr ""
"Um eine Datei endgültig zu löschen, muss sie zuvor weich gelöscht worden "
"sein."
-#: core/recording/event/notification.py:116
+#: core/recording/event/notification.py:123
msgid "Your recording is ready"
msgstr "Ihre Aufzeichnung ist bereit"
+#: core/recording/event/notification.py:194
+msgid "Transcription"
+msgstr "Transkription"
+
+#: core/recording/event/notification.py:204
+#, python-brace-format
+msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}"
+msgstr ""
+"Besprechung \"{room}\" am {room_recording_date} um {room_recording_time}"
+
#: core/services/invitation.py:44
#, python-brace-format
msgid "Video call in progress: {sender.email} is waiting for you to connect"
@@ -575,7 +627,7 @@ msgid "To keep this recording permanently:"
msgstr "So speichern Sie diese Aufzeichnung dauerhaft:"
#: core/templates/mail/html/screen_recording.html:208
-#: core/templates/mail/text/screen_recording.txt:13
+#, python-format
msgid "Click the \"Open\" link below "
msgstr "Klicken Sie auf den Link „Öffnen\" unten "
@@ -604,18 +656,24 @@ msgstr ""
" Wenn Sie Fragen haben oder Unterstützung benötigen, wenden Sie sich bitte "
"an unser Support-Team unter %(support_email)s. "
-#: meet/settings.py:223
+#: core/templates/mail/text/screen_recording.txt:13
+#, fuzzy, python-format
+#| msgid "Click the \"Open\" link below "
+msgid "Click the \"Open [%(link)s]\" link below "
+msgstr "Klicken Sie auf den Link „Öffnen\" unten "
+
+#: meet/settings.py:228
msgid "English"
msgstr "Englisch"
-#: meet/settings.py:224
+#: meet/settings.py:229
msgid "French"
msgstr "Französisch"
-#: meet/settings.py:225
+#: meet/settings.py:230
msgid "Dutch"
msgstr "Niederländisch"
-#: meet/settings.py:226
+#: meet/settings.py:231
msgid "German"
msgstr "Deutsch"
diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po
index 70c973b9..2f58f6d4 100644
--- a/src/backend/locale/en_US/LC_MESSAGES/django.po
+++ b/src/backend/locale/en_US/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-04-21 14:09+0000\n"
+"POT-Creation-Date: 2026-07-02 10:47+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,70 +17,92 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: core/admin.py:29
+#: core/admin.py:67
msgid "Personal info"
msgstr "Personal info"
-#: core/admin.py:42
+#: core/admin.py:80
msgid "Permissions"
msgstr "Permissions"
-#: core/admin.py:54
+#: core/admin.py:92
msgid "Important dates"
msgstr "Important dates"
-#: core/admin.py:132 core/admin.py:275
+#: core/admin.py:206
+msgid "Content"
+msgstr ""
+
+#: core/admin.py:217
+#, fuzzy
+#| msgid "Delete rooms"
+msgid "Deletion"
+msgstr "Delete rooms"
+
+#: core/admin.py:226
+msgid "Derived info"
+msgstr ""
+
+#: core/admin.py:237
+msgid "Timestamps"
+msgstr ""
+
+#: core/admin.py:240
+msgid "File preview"
+msgstr ""
+
+#: core/admin.py:300 core/admin.py:443
msgid "No owner"
msgstr "No owner"
-#: core/admin.py:135 core/admin.py:278
+#: core/admin.py:303 core/admin.py:446
msgid "Multiple owners"
msgstr "Multiple owners"
-#: core/admin.py:148
+#: core/admin.py:316
msgid "Resend notification to external service"
msgstr "Resend notification to external service"
-#: core/admin.py:171
+#: core/admin.py:339
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Failed to notify for recording %(id)s"
-#: core/admin.py:179
+#: core/admin.py:347
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Failed to notify for recording %(id)s: %(error)s"
-#: core/admin.py:187
+#: core/admin.py:355
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Successfully sent notifications for %(count)s recording(s)."
-#: core/admin.py:195
+#: core/admin.py:363
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "Skipped %(count)s expired recording(s)."
-#: core/admin.py:200
+#: core/admin.py:368
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Mark selected recordings as 'Failed to Stop'"
-#: core/admin.py:218
+#: core/admin.py:386
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr "%(count)s recording(s) successfully marked as 'Failed to Stop'."
-#: core/admin.py:226
+#: core/admin.py:394
#, fuzzy, python-format
#| msgid "Skipped %(count)s expired recording(s)."
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "Skipped %(count)s expired recording(s)."
-#: core/admin.py:342
+#: core/admin.py:510
msgid "No scopes"
msgstr "No scopes"
-#: core/admin.py:344
+#: core/admin.py:512
msgid "Scopes"
msgstr "Scopes"
@@ -88,15 +110,15 @@ msgstr "Scopes"
msgid "Creator is me"
msgstr "Creator is me"
-#: core/api/serializers.py:88
+#: core/api/serializers.py:89
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr "You must be administrator or owner of a room to add accesses to it."
-#: core/api/serializers.py:509
+#: core/api/serializers.py:534
msgid "This file extension is not allowed."
msgstr "This file extension is not allowed."
-#: core/api/viewsets.py:1090
+#: core/api/viewsets.py:1222
msgid "You have reached the maximum number of files for this type."
msgstr "You have reached the maximum number of files for this type."
@@ -144,51 +166,59 @@ msgstr "Failed to Stop"
msgid "Notification succeeded"
msgstr "Notification succeeded"
-#: core/models.py:89
+#: core/models.py:65
+msgid "External process successful"
+msgstr "External process successful"
+
+#: core/models.py:67
+msgid "External process failed"
+msgstr "External process failed"
+
+#: core/models.py:96
msgid "SCREEN_RECORDING"
msgstr "SCREEN_RECORDING"
-#: core/models.py:90
+#: core/models.py:97
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
-#: core/models.py:96
+#: core/models.py:103
msgid "Public Access"
msgstr "Public Access"
-#: core/models.py:97
+#: core/models.py:104
msgid "Trusted Access"
msgstr "Trusted Access"
-#: core/models.py:98
+#: core/models.py:105
msgid "Restricted Access"
msgstr "Restricted Access"
-#: core/models.py:110
+#: core/models.py:117
msgid "id"
msgstr "id"
-#: core/models.py:111
+#: core/models.py:118
msgid "primary key for the record as UUID"
msgstr "primary key for the record as UUID"
-#: core/models.py:117
+#: core/models.py:124
msgid "created on"
msgstr "created on"
-#: core/models.py:118
+#: core/models.py:125
msgid "date and time at which a record was created"
msgstr "date and time at which a record was created"
-#: core/models.py:123
+#: core/models.py:130
msgid "updated on"
msgstr "updated on"
-#: core/models.py:124
+#: core/models.py:131
msgid "date and time at which a record was last updated"
msgstr "date and time at which a record was last updated"
-#: core/models.py:144
+#: core/models.py:151
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -196,11 +226,11 @@ msgstr ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
-#: core/models.py:150
+#: core/models.py:157
msgid "sub"
msgstr "sub"
-#: core/models.py:152
+#: core/models.py:159
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -208,55 +238,55 @@ msgstr ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
-#: core/models.py:161
+#: core/models.py:168
msgid "identity email address"
msgstr "identity email address"
-#: core/models.py:166
+#: core/models.py:173
msgid "admin email address"
msgstr "admin email address"
-#: core/models.py:168
+#: core/models.py:175
msgid "full name"
msgstr "full name"
-#: core/models.py:170
+#: core/models.py:177
msgid "short name"
msgstr "short name"
-#: core/models.py:176
+#: core/models.py:183
msgid "language"
msgstr "language"
-#: core/models.py:177
+#: core/models.py:184
msgid "The language in which the user wants to see the interface."
msgstr "The language in which the user wants to see the interface."
-#: core/models.py:183
+#: core/models.py:190
msgid "The timezone in which the user wants to see times."
msgstr "The timezone in which the user wants to see times."
-#: core/models.py:186
+#: core/models.py:193
msgid "device"
msgstr "device"
-#: core/models.py:188
+#: core/models.py:195
msgid "Whether the user is a device or a real user."
msgstr "Whether the user is a device or a real user."
-#: core/models.py:191
+#: core/models.py:198
msgid "staff status"
msgstr "staff status"
-#: core/models.py:193
+#: core/models.py:200
msgid "Whether the user can log into this admin site."
msgstr "Whether the user can log into this admin site."
-#: core/models.py:196
+#: core/models.py:203
msgid "active"
msgstr "active"
-#: core/models.py:199
+#: core/models.py:206
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -264,63 +294,63 @@ msgstr ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
-#: core/models.py:212
+#: core/models.py:219
msgid "user"
msgstr "user"
-#: core/models.py:213
+#: core/models.py:220
msgid "users"
msgstr "users"
-#: core/models.py:272
+#: core/models.py:286
msgid "Resource"
msgstr "Resource"
-#: core/models.py:273
+#: core/models.py:287
msgid "Resources"
msgstr "Resources"
-#: core/models.py:331
+#: core/models.py:345
msgid "Resource access"
msgstr "Resource access"
-#: core/models.py:332
+#: core/models.py:346
msgid "Resource accesses"
msgstr "Resource accesses"
-#: core/models.py:338
+#: core/models.py:352
msgid "Resource access with this User and Resource already exists."
msgstr "Resource access with this User and Resource already exists."
-#: core/models.py:394
+#: core/models.py:409
msgid "Visio room configuration"
msgstr "Visio room configuration"
-#: core/models.py:395
+#: core/models.py:410
msgid "Values for Visio parameters to configure the room."
msgstr "Values for Visio parameters to configure the room."
-#: core/models.py:402
+#: core/models.py:417
msgid "Room PIN code"
msgstr "Room PIN code"
-#: core/models.py:403
+#: core/models.py:418
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr "Unique n-digit code that identifies this room in telephony mode."
-#: core/models.py:409 core/models.py:563
+#: core/models.py:424 core/models.py:578
msgid "Room"
msgstr "Room"
-#: core/models.py:410
+#: core/models.py:425
msgid "Rooms"
msgstr "Rooms"
-#: core/models.py:574
+#: core/models.py:589
msgid "Worker ID"
msgstr "Worker ID"
-#: core/models.py:576
+#: core/models.py:591
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -328,154 +358,175 @@ msgstr ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
-#: core/models.py:584
+#: core/models.py:599
msgid "Recording mode"
msgstr "Recording mode"
-#: core/models.py:585
+#: core/models.py:600
msgid "Defines the mode of recording being called."
msgstr "Defines the mode of recording being called."
-#: core/models.py:590 core/models.py:591
+#: core/models.py:605 core/models.py:606
msgid "Recording options"
msgstr "Recording options"
-#: core/models.py:597
+#: core/models.py:613
+msgid "External Process ID"
+msgstr "External Process ID"
+
+#: core/models.py:614
+msgid "ID of the external process associated with the recording."
+msgstr "ID of the external process associated with the recording."
+
+#: core/models.py:620
msgid "Recording"
msgstr "Recording"
-#: core/models.py:598
+#: core/models.py:621
msgid "Recordings"
msgstr "Recordings"
-#: core/models.py:706
+#: core/models.py:731
msgid "Recording/user relation"
msgstr "Recording/user relation"
-#: core/models.py:707
+#: core/models.py:732
msgid "Recording/user relations"
msgstr "Recording/user relations"
-#: core/models.py:713
+#: core/models.py:738
msgid "This user is already in this recording."
msgstr "This user is already in this recording."
-#: core/models.py:719
+#: core/models.py:744
msgid "This team is already in this recording."
msgstr "This team is already in this recording."
-#: core/models.py:725
+#: core/models.py:750
msgid "Either user or team must be set, not both."
msgstr "Either user or team must be set, not both."
-#: core/models.py:742
+#: core/models.py:767
#, fuzzy
#| msgid "created on"
msgid "Create rooms"
msgstr "Create rooms"
-#: core/models.py:743
+#: core/models.py:768
msgid "List rooms"
msgstr "List rooms"
-#: core/models.py:744
+#: core/models.py:769
msgid "Retrieve room details"
msgstr "Retrieve room details"
-#: core/models.py:745
+#: core/models.py:770
#, fuzzy
#| msgid "updated on"
msgid "Update rooms"
msgstr "Update rooms"
-#: core/models.py:746
+#: core/models.py:771
msgid "Delete rooms"
msgstr "Delete rooms"
-#: core/models.py:759
+#: core/models.py:784
msgid "Application name"
msgstr "Application name"
-#: core/models.py:760
+#: core/models.py:785
msgid "Descriptive name for this application."
msgstr "Descriptive name for this application."
-#: core/models.py:770
+#: core/models.py:795
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr "Hashed on Save. Copy it now if this is a new secret."
-#: core/models.py:781
+#: core/models.py:806
msgid "Application"
msgstr "Application"
-#: core/models.py:782
+#: core/models.py:807
msgid "Applications"
msgstr "Applications"
-#: core/models.py:805
+#: core/models.py:830
msgid "Enter a valid domain"
msgstr "Enter a valid domain"
-#: core/models.py:808
+#: core/models.py:833
msgid "Domain"
msgstr "Domain"
-#: core/models.py:809
+#: core/models.py:834
msgid "Email domain this application can act on behalf of."
msgstr "Email domain this application can act on behalf of."
-#: core/models.py:821
+#: core/models.py:846
msgid "Application domain"
msgstr "Application domain"
-#: core/models.py:822
+#: core/models.py:847
msgid "Application domains"
msgstr "Application domains"
-#: core/models.py:840
+#: core/models.py:865
#, fuzzy
#| msgid "Recording"
msgid "Pending"
msgstr "Pending"
-#: core/models.py:848
+#: core/models.py:866
+msgid "Analyzing"
+msgstr ""
+
+#: core/models.py:873
msgid "Ready"
msgstr "Ready"
-#: core/models.py:854
+#: core/models.py:879
msgid "Background image"
msgstr "Background image"
-#: core/models.py:866
+#: core/models.py:891
msgid "title"
msgstr "title"
-#: core/models.py:890
+#: core/models.py:915
msgid "Malware detection info when the analysis status is unsafe."
msgstr "Malware detection info when the analysis status is unsafe."
-#: core/models.py:895
+#: core/models.py:920
msgid "File"
msgstr "File"
-#: core/models.py:896
+#: core/models.py:921
msgid "Files"
msgstr "Files"
-#: core/models.py:1000
+#: core/models.py:1041
#, fuzzy
#| msgid "This user is already in this recording."
msgid "This file is already hard deleted."
msgstr "This file is already hard deleted."
-#: core/models.py:1010
+#: core/models.py:1051
msgid "To hard delete a file, it must first be soft deleted."
msgstr "To hard delete a file, it must first be soft deleted."
-#: core/recording/event/notification.py:116
+#: core/recording/event/notification.py:123
msgid "Your recording is ready"
msgstr "Your recording is ready"
+#: core/recording/event/notification.py:194
+msgid "Transcription"
+msgstr "Transcription"
+
+#: core/recording/event/notification.py:204
+#, python-brace-format
+msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}"
+msgstr "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}"
+
#: core/services/invitation.py:44
#, python-brace-format
msgid "Video call in progress: {sender.email} is waiting for you to connect"
@@ -571,7 +622,7 @@ msgid "To keep this recording permanently:"
msgstr "To keep this recording permanently:"
#: core/templates/mail/html/screen_recording.html:208
-#: core/templates/mail/text/screen_recording.txt:13
+#, python-format
msgid "Click the \"Open\" link below "
msgstr "Click the \"Open\" link below "
@@ -600,18 +651,24 @@ msgstr ""
" If you have any questions or need assistance, please contact our support "
"team at %(support_email)s. "
-#: meet/settings.py:223
+#: core/templates/mail/text/screen_recording.txt:13
+#, fuzzy, python-format
+#| msgid "Click the \"Open\" link below "
+msgid "Click the \"Open [%(link)s]\" link below "
+msgstr "Click the \"Open\" link below "
+
+#: meet/settings.py:228
msgid "English"
msgstr "English"
-#: meet/settings.py:224
+#: meet/settings.py:229
msgid "French"
msgstr "French"
-#: meet/settings.py:225
+#: meet/settings.py:230
msgid "Dutch"
msgstr "Dutch"
-#: meet/settings.py:226
+#: meet/settings.py:231
msgid "German"
msgstr "German"
diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po
index cff45d0a..d26a3db3 100644
--- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po
+++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-04-21 14:09+0000\n"
+"POT-Creation-Date: 2026-07-02 10:47+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: antoine.lebaud@mail.numerique.gouv.fr\n"
"Language-Team: LANGUAGE \n"
@@ -17,71 +17,93 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: core/admin.py:29
+#: core/admin.py:67
msgid "Personal info"
msgstr "Informations personnelles"
-#: core/admin.py:42
+#: core/admin.py:80
msgid "Permissions"
msgstr "Permissions"
-#: core/admin.py:54
+#: core/admin.py:92
msgid "Important dates"
msgstr "Dates importantes"
-#: core/admin.py:132 core/admin.py:275
+#: core/admin.py:206
+msgid "Content"
+msgstr ""
+
+#: core/admin.py:217
+#, fuzzy
+#| msgid "Delete rooms"
+msgid "Deletion"
+msgstr "Supprimer les salles"
+
+#: core/admin.py:226
+msgid "Derived info"
+msgstr ""
+
+#: core/admin.py:237
+msgid "Timestamps"
+msgstr ""
+
+#: core/admin.py:240
+msgid "File preview"
+msgstr ""
+
+#: core/admin.py:300 core/admin.py:443
msgid "No owner"
msgstr "Pas de propriétaire"
-#: core/admin.py:135 core/admin.py:278
+#: core/admin.py:303 core/admin.py:446
msgid "Multiple owners"
msgstr "Plusieurs propriétaires"
-#: core/admin.py:148
+#: core/admin.py:316
msgid "Resend notification to external service"
msgstr "Renvoyer la notification au service externe"
-#: core/admin.py:171
+#: core/admin.py:339
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Échec de la notification pour l’enregistrement %(id)s"
-#: core/admin.py:179
+#: core/admin.py:347
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Échec de la notification pour l’enregistrement %(id)s : %(error)s"
-#: core/admin.py:187
+#: core/admin.py:355
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Notifications envoyées avec succès pour %(count)s enregistrement(s)."
-#: core/admin.py:195
+#: core/admin.py:363
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s enregistrement(s) expiré(s) ignoré(s)."
-#: core/admin.py:200
+#: core/admin.py:368
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Marquer les enregistrements sélectionnés comme « Échec d’arrêt »"
-#: core/admin.py:218
+#: core/admin.py:386
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr ""
"%(count)s enregistrement(s) marqué(s) avec succès comme « Échec d’arrêt »."
-#: core/admin.py:226
+#: core/admin.py:394
#, fuzzy, python-format
#| msgid "Skipped %(count)s expired recording(s)."
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "%(count)s enregistrement(s) avec un statut inéligible ignoré(s)."
-#: core/admin.py:342
+#: core/admin.py:510
msgid "No scopes"
msgstr "Aucun scopes"
-#: core/admin.py:344
+#: core/admin.py:512
msgid "Scopes"
msgstr "Scopes"
@@ -89,17 +111,17 @@ msgstr "Scopes"
msgid "Creator is me"
msgstr "Je suis le créateur"
-#: core/api/serializers.py:88
+#: core/api/serializers.py:89
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Vous devez être administrateur ou propriétaire d'une salle pour y ajouter "
"des accès."
-#: core/api/serializers.py:509
+#: core/api/serializers.py:534
msgid "This file extension is not allowed."
msgstr "Cette extension n'est pas autorisée"
-#: core/api/viewsets.py:1090
+#: core/api/viewsets.py:1222
msgid "You have reached the maximum number of files for this type."
msgstr "Vous avez atteint le nombre maximum de fichiers de ce type"
@@ -147,53 +169,61 @@ msgstr "Échec à l'arrêt"
msgid "Notification succeeded"
msgstr "Notification réussie"
-#: core/models.py:89
+#: core/models.py:65
+msgid "External process successful"
+msgstr "Traitement externe : succès"
+
+#: core/models.py:67
+msgid "External process failed"
+msgstr "Traitement externe : erreur"
+
+#: core/models.py:96
msgid "SCREEN_RECORDING"
msgstr "ENREGISTREMENT_ÉCRAN"
-#: core/models.py:90
+#: core/models.py:97
msgid "TRANSCRIPT"
msgstr "TRANSCRIPTION"
-#: core/models.py:96
+#: core/models.py:103
msgid "Public Access"
msgstr "Accès public"
-#: core/models.py:97
+#: core/models.py:104
msgid "Trusted Access"
msgstr "Accès de confiance"
-#: core/models.py:98
+#: core/models.py:105
msgid "Restricted Access"
msgstr "Accès restreint"
-#: core/models.py:110
+#: core/models.py:117
msgid "id"
msgstr "id"
-#: core/models.py:111
+#: core/models.py:118
msgid "primary key for the record as UUID"
msgstr "clé primaire pour l'enregistrement sous forme d'UUID"
-#: core/models.py:117
+#: core/models.py:124
msgid "created on"
msgstr "créé le"
-#: core/models.py:118
+#: core/models.py:125
msgid "date and time at which a record was created"
msgstr "date et heure auxquelles un enregistrement a été créé"
-#: core/models.py:123
+#: core/models.py:130
msgid "updated on"
msgstr "mis à jour le"
-#: core/models.py:124
+#: core/models.py:131
msgid "date and time at which a record was last updated"
msgstr ""
"date et heure auxquelles un enregistrement a été mis à jour pour la dernière "
"fois"
-#: core/models.py:144
+#: core/models.py:151
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -201,11 +231,11 @@ msgstr ""
"Entrez un sub valide. Cette valeur ne peut contenir que des lettres, des "
"chiffres et les caractères @/./+/-/_."
-#: core/models.py:150
+#: core/models.py:157
msgid "sub"
msgstr "sub"
-#: core/models.py:152
+#: core/models.py:159
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -213,55 +243,55 @@ msgstr ""
"Optionnel pour les utilisateurs en attente ; requis lors de l'activation du "
"compte. 255 caractères maximum. Lettres, chiffres et @/./+/-/_ uniquement."
-#: core/models.py:161
+#: core/models.py:168
msgid "identity email address"
msgstr "adresse e-mail d'identité"
-#: core/models.py:166
+#: core/models.py:173
msgid "admin email address"
msgstr "adresse e-mail d'administrateur"
-#: core/models.py:168
+#: core/models.py:175
msgid "full name"
msgstr "nom complet"
-#: core/models.py:170
+#: core/models.py:177
msgid "short name"
msgstr "nom court"
-#: core/models.py:176
+#: core/models.py:183
msgid "language"
msgstr "langue"
-#: core/models.py:177
+#: core/models.py:184
msgid "The language in which the user wants to see the interface."
msgstr "La langue dans laquelle l'utilisateur souhaite voir l'interface."
-#: core/models.py:183
+#: core/models.py:190
msgid "The timezone in which the user wants to see times."
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
-#: core/models.py:186
+#: core/models.py:193
msgid "device"
msgstr "appareil"
-#: core/models.py:188
+#: core/models.py:195
msgid "Whether the user is a device or a real user."
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
-#: core/models.py:191
+#: core/models.py:198
msgid "staff status"
msgstr "statut du personnel"
-#: core/models.py:193
+#: core/models.py:200
msgid "Whether the user can log into this admin site."
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
-#: core/models.py:196
+#: core/models.py:203
msgid "active"
msgstr "actif"
-#: core/models.py:199
+#: core/models.py:206
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -269,65 +299,65 @@ msgstr ""
"Si cet utilisateur doit être traité comme actif. Désélectionnez cette option "
"au lieu de supprimer des comptes."
-#: core/models.py:212
+#: core/models.py:219
msgid "user"
msgstr "utilisateur"
-#: core/models.py:213
+#: core/models.py:220
msgid "users"
msgstr "utilisateurs"
-#: core/models.py:272
+#: core/models.py:286
msgid "Resource"
msgstr "Ressource"
-#: core/models.py:273
+#: core/models.py:287
msgid "Resources"
msgstr "Ressources"
-#: core/models.py:331
+#: core/models.py:345
msgid "Resource access"
msgstr "Accès aux ressources"
-#: core/models.py:332
+#: core/models.py:346
msgid "Resource accesses"
msgstr "Accès aux ressources"
-#: core/models.py:338
+#: core/models.py:352
msgid "Resource access with this User and Resource already exists."
msgstr ""
"L'accès à la ressource avec cet utilisateur et cette ressource existe déjà."
-#: core/models.py:394
+#: core/models.py:409
msgid "Visio room configuration"
msgstr "Configuration de la salle de visioconférence"
-#: core/models.py:395
+#: core/models.py:410
msgid "Values for Visio parameters to configure the room."
msgstr "Valeurs des paramètres de visioconférence pour configurer la salle."
-#: core/models.py:402
+#: core/models.py:417
msgid "Room PIN code"
msgstr "Code PIN de la salle"
-#: core/models.py:403
+#: core/models.py:418
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Code unique à n chiffres qui identifie cette salle en mode téléphonique."
-#: core/models.py:409 core/models.py:563
+#: core/models.py:424 core/models.py:578
msgid "Room"
msgstr "Salle"
-#: core/models.py:410
+#: core/models.py:425
msgid "Rooms"
msgstr "Salles"
-#: core/models.py:574
+#: core/models.py:589
msgid "Worker ID"
msgstr "ID du Worker"
-#: core/models.py:576
+#: core/models.py:591
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -335,153 +365,174 @@ msgstr ""
"Entrez un identifiant pour l'enregistrement du Worker. Cet identifiant est "
"conservé même lorsque le Worker s'arrête, permettant un suivi facile."
-#: core/models.py:584
+#: core/models.py:599
msgid "Recording mode"
msgstr "Mode d'enregistrement"
-#: core/models.py:585
+#: core/models.py:600
msgid "Defines the mode of recording being called."
msgstr "Définit le mode d'enregistrement appelé."
-#: core/models.py:590 core/models.py:591
+#: core/models.py:605 core/models.py:606
msgid "Recording options"
msgstr "Options d'enregistrement"
-#: core/models.py:597
+#: core/models.py:613
+msgid "External Process ID"
+msgstr "ID Traitement externe"
+
+#: core/models.py:614
+msgid "ID of the external process associated with the recording."
+msgstr "ID du traitement externe associé avec l'enregistrement."
+
+#: core/models.py:620
msgid "Recording"
msgstr "Enregistrement"
-#: core/models.py:598
+#: core/models.py:621
msgid "Recordings"
msgstr "Enregistrements"
-#: core/models.py:706
+#: core/models.py:731
msgid "Recording/user relation"
msgstr "Relation enregistrement/utilisateur"
-#: core/models.py:707
+#: core/models.py:732
msgid "Recording/user relations"
msgstr "Relations enregistrement/utilisateur"
-#: core/models.py:713
+#: core/models.py:738
msgid "This user is already in this recording."
msgstr "Cet utilisateur est déjà dans cet enregistrement."
-#: core/models.py:719
+#: core/models.py:744
msgid "This team is already in this recording."
msgstr "Cette équipe est déjà dans cet enregistrement."
-#: core/models.py:725
+#: core/models.py:750
msgid "Either user or team must be set, not both."
msgstr "Soit l'utilisateur, soit l'équipe doit être défini, pas les deux."
-#: core/models.py:742
+#: core/models.py:767
msgid "Create rooms"
msgstr "Créer des salles"
-#: core/models.py:743
+#: core/models.py:768
msgid "List rooms"
msgstr "Lister les salles"
-#: core/models.py:744
+#: core/models.py:769
msgid "Retrieve room details"
msgstr "Afficher les détails d’une salle"
-#: core/models.py:745
+#: core/models.py:770
msgid "Update rooms"
msgstr "Mettre à jour les salles"
-#: core/models.py:746
+#: core/models.py:771
msgid "Delete rooms"
msgstr "Supprimer les salles"
-#: core/models.py:759
+#: core/models.py:784
msgid "Application name"
msgstr "Nom de l’application"
-#: core/models.py:760
+#: core/models.py:785
msgid "Descriptive name for this application."
msgstr "Nom descriptif de cette application."
-#: core/models.py:770
+#: core/models.py:795
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Haché lors de l’enregistrement. Copiez-le maintenant s’il s’agit d’un "
"nouveau secret."
-#: core/models.py:781
+#: core/models.py:806
msgid "Application"
msgstr "Application"
-#: core/models.py:782
+#: core/models.py:807
msgid "Applications"
msgstr "Applications"
-#: core/models.py:805
+#: core/models.py:830
msgid "Enter a valid domain"
msgstr "Saisissez un domaine valide"
-#: core/models.py:808
+#: core/models.py:833
msgid "Domain"
msgstr "Domaine"
-#: core/models.py:809
+#: core/models.py:834
msgid "Email domain this application can act on behalf of."
msgstr "Domaine de messagerie au nom duquel cette application peut agir."
-#: core/models.py:821
+#: core/models.py:846
msgid "Application domain"
msgstr "Domaine d’application"
-#: core/models.py:822
+#: core/models.py:847
msgid "Application domains"
msgstr "Domaines d’application"
-#: core/models.py:840
+#: core/models.py:865
msgid "Pending"
msgstr "En attente"
-#: core/models.py:848
+#: core/models.py:866
+msgid "Analyzing"
+msgstr ""
+
+#: core/models.py:873
msgid "Ready"
msgstr "Prêt"
-#: core/models.py:854
+#: core/models.py:879
msgid "Background image"
msgstr "Image de fond"
-#: core/models.py:866
+#: core/models.py:891
msgid "title"
msgstr "Titre"
-#: core/models.py:890
+#: core/models.py:915
msgid "Malware detection info when the analysis status is unsafe."
msgstr ""
"Information concernant la détection de Malware cand le statut n'est pas sain"
-#: core/models.py:895
+#: core/models.py:920
msgid "File"
msgstr "Fichier"
-#: core/models.py:896
+#: core/models.py:921
msgid "Files"
msgstr "Fichiers"
-#: core/models.py:1000
+#: core/models.py:1041
#, fuzzy
#| msgid "This user is already in this recording."
msgid "This file is already hard deleted."
msgstr "Ce fichier a été supprimé."
-#: core/models.py:1010
+#: core/models.py:1051
msgid "To hard delete a file, it must first be soft deleted."
msgstr ""
"Pour supprimer définitivement un fichier il doit d'abord avoir été marqué "
"comme supprimé (soft delete)"
-#: core/recording/event/notification.py:116
+#: core/recording/event/notification.py:123
msgid "Your recording is ready"
msgstr "Votre enregistrement est prêt"
+#: core/recording/event/notification.py:194
+msgid "Transcription"
+msgstr "Transcription"
+
+#: core/recording/event/notification.py:204
+#, python-brace-format
+msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}"
+msgstr "Réunion \"{room}\" du {room_recording_date} à {room_recording_time}"
+
#: core/services/invitation.py:44
#, python-brace-format
msgid "Video call in progress: {sender.email} is waiting for you to connect"
@@ -577,7 +628,7 @@ msgid "To keep this recording permanently:"
msgstr "Pour conserver cet enregistrement de façon permanente :"
#: core/templates/mail/html/screen_recording.html:208
-#: core/templates/mail/text/screen_recording.txt:13
+#, python-format
msgid "Click the \"Open\" link below "
msgstr "Cliquez sur le lien \"Ouvrir\" ci-dessous "
@@ -606,18 +657,24 @@ msgstr ""
" Si vous avez des questions ou besoin d'assistance, veuillez contacter notre "
"équipe d'assistance à %(support_email)s. "
-#: meet/settings.py:223
+#: core/templates/mail/text/screen_recording.txt:13
+#, fuzzy, python-format
+#| msgid "Click the \"Open\" link below "
+msgid "Click the \"Open [%(link)s]\" link below "
+msgstr "Cliquez sur le lien \"Ouvrir\" ci-dessous "
+
+#: meet/settings.py:228
msgid "English"
msgstr "Anglais"
-#: meet/settings.py:224
+#: meet/settings.py:229
msgid "French"
msgstr "Français"
-#: meet/settings.py:225
+#: meet/settings.py:230
msgid "Dutch"
msgstr "Néerlandais"
-#: meet/settings.py:226
+#: meet/settings.py:231
msgid "German"
msgstr "Allemand"
diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po
index 78328812..236a187d 100644
--- a/src/backend/locale/nl_NL/LC_MESSAGES/django.po
+++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-04-21 14:09+0000\n"
+"POT-Creation-Date: 2026-07-02 10:47+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,70 +17,92 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: core/admin.py:29
+#: core/admin.py:67
msgid "Personal info"
msgstr "Persoonlijke informatie"
-#: core/admin.py:42
+#: core/admin.py:80
msgid "Permissions"
msgstr "Rechten"
-#: core/admin.py:54
+#: core/admin.py:92
msgid "Important dates"
msgstr "Belangrijke datums"
-#: core/admin.py:132 core/admin.py:275
+#: core/admin.py:206
+msgid "Content"
+msgstr ""
+
+#: core/admin.py:217
+#, fuzzy
+#| msgid "Delete rooms"
+msgid "Deletion"
+msgstr "Ruimtes verwijderen"
+
+#: core/admin.py:226
+msgid "Derived info"
+msgstr ""
+
+#: core/admin.py:237
+msgid "Timestamps"
+msgstr ""
+
+#: core/admin.py:240
+msgid "File preview"
+msgstr ""
+
+#: core/admin.py:300 core/admin.py:443
msgid "No owner"
msgstr "Geen eigenaar"
-#: core/admin.py:135 core/admin.py:278
+#: core/admin.py:303 core/admin.py:446
msgid "Multiple owners"
msgstr "Meerdere eigenaren"
-#: core/admin.py:148
+#: core/admin.py:316
msgid "Resend notification to external service"
msgstr "Melding opnieuw verzenden naar externe dienst"
-#: core/admin.py:171
+#: core/admin.py:339
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Melding voor opname %(id)s mislukt"
-#: core/admin.py:179
+#: core/admin.py:347
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Melding voor opname %(id)s mislukt: %(error)s"
-#: core/admin.py:187
+#: core/admin.py:355
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Meldingen succesvol verzonden voor %(count)s opname(n)."
-#: core/admin.py:195
+#: core/admin.py:363
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s verlopen opname(n) overgeslagen."
-#: core/admin.py:200
+#: core/admin.py:368
msgid "Mark selected recordings as 'Failed to Stop'"
msgstr "Geselecteerde opnames markeren als 'Mislukt bij stoppen'"
-#: core/admin.py:218
+#: core/admin.py:386
#, python-format
msgid "%(count)s recording(s) successfully marked as 'Failed to Stop'."
msgstr "%(count)s opname(s) succesvol gemarkeerd als 'Mislukt bij stoppen'."
-#: core/admin.py:226
+#: core/admin.py:394
#, fuzzy, python-format
#| msgid "Skipped %(count)s expired recording(s)."
msgid "Skipped %(count)s recording(s) with an ineligible status."
msgstr "%(count)s opname(s) met een niet-toegestane status overgeslagen."
-#: core/admin.py:342
+#: core/admin.py:510
msgid "No scopes"
msgstr "Geen scopes"
-#: core/admin.py:344
+#: core/admin.py:512
msgid "Scopes"
msgstr "Scopes"
@@ -88,16 +110,16 @@ msgstr "Scopes"
msgid "Creator is me"
msgstr "Maker ben ik"
-#: core/api/serializers.py:88
+#: core/api/serializers.py:89
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Je moet beheerder of eigenaar van een ruimte zijn om toegang toe te voegen."
-#: core/api/serializers.py:509
+#: core/api/serializers.py:534
msgid "This file extension is not allowed."
msgstr "Deze bestandsextensie is niet toegestaan."
-#: core/api/viewsets.py:1090
+#: core/api/viewsets.py:1222
msgid "You have reached the maximum number of files for this type."
msgstr "Het maximale aantal bestanden voor dit type is bereikt."
@@ -145,51 +167,59 @@ msgstr "Stoppen mislukt"
msgid "Notification succeeded"
msgstr "Notificatie geslaagd"
-#: core/models.py:89
+#: core/models.py:65
+msgid "External process successful"
+msgstr "Externe procedure succesvol"
+
+#: core/models.py:67
+msgid "External process failed"
+msgstr "Het externe proces is mislukt."
+
+#: core/models.py:96
msgid "SCREEN_RECORDING"
msgstr "SCHERM_OPNAME"
-#: core/models.py:90
+#: core/models.py:97
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
-#: core/models.py:96
+#: core/models.py:103
msgid "Public Access"
msgstr "Openbare toegang"
-#: core/models.py:97
+#: core/models.py:104
msgid "Trusted Access"
msgstr "Vertrouwde toegang"
-#: core/models.py:98
+#: core/models.py:105
msgid "Restricted Access"
msgstr "Beperkte toegang"
-#: core/models.py:110
+#: core/models.py:117
msgid "id"
msgstr "id"
-#: core/models.py:111
+#: core/models.py:118
msgid "primary key for the record as UUID"
msgstr "primaire sleutel voor het record als UUID"
-#: core/models.py:117
+#: core/models.py:124
msgid "created on"
msgstr "aangemaakt op"
-#: core/models.py:118
+#: core/models.py:125
msgid "date and time at which a record was created"
msgstr "datum en tijd waarop een record werd aangemaakt"
-#: core/models.py:123
+#: core/models.py:130
msgid "updated on"
msgstr "bijgewerkt op"
-#: core/models.py:124
+#: core/models.py:131
msgid "date and time at which a record was last updated"
msgstr "datum en tijd waarop een record voor het laatst werd bijgewerkt"
-#: core/models.py:144
+#: core/models.py:151
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -197,11 +227,11 @@ msgstr ""
"Voer een geldige sub in. Deze waarde mag alleen letters, cijfers en @/./+/-/"
"_ tekens bevatten."
-#: core/models.py:150
+#: core/models.py:157
msgid "sub"
msgstr "sub"
-#: core/models.py:152
+#: core/models.py:159
msgid ""
"Optional for pending users; required upon account activation. 255 characters "
"or fewer. Letters, numbers, and @/./+/-/_ characters only."
@@ -209,55 +239,55 @@ msgstr ""
"Optioneel voor gebruikers in afwachting; vereist bij accountactivering. "
"Maximum 255 tekens. Alleen letters, cijfers en @/./+/-/_ toegestaan."
-#: core/models.py:161
+#: core/models.py:168
msgid "identity email address"
msgstr "identiteit e-mailadres"
-#: core/models.py:166
+#: core/models.py:173
msgid "admin email address"
msgstr "beheerder e-mailadres"
-#: core/models.py:168
+#: core/models.py:175
msgid "full name"
msgstr "volledige naam"
-#: core/models.py:170
+#: core/models.py:177
msgid "short name"
msgstr "korte naam"
-#: core/models.py:176
+#: core/models.py:183
msgid "language"
msgstr "taal"
-#: core/models.py:177
+#: core/models.py:184
msgid "The language in which the user wants to see the interface."
msgstr "De taal waarin de gebruiker de interface wil zien."
-#: core/models.py:183
+#: core/models.py:190
msgid "The timezone in which the user wants to see times."
msgstr "De tijdzone waarin de gebruiker tijden wil zien."
-#: core/models.py:186
+#: core/models.py:193
msgid "device"
msgstr "apparaat"
-#: core/models.py:188
+#: core/models.py:195
msgid "Whether the user is a device or a real user."
msgstr "Of de gebruiker een apparaat is of een echte gebruiker."
-#: core/models.py:191
+#: core/models.py:198
msgid "staff status"
msgstr "personeelsstatus"
-#: core/models.py:193
+#: core/models.py:200
msgid "Whether the user can log into this admin site."
msgstr "Of de gebruiker kan inloggen op deze beheersite."
-#: core/models.py:196
+#: core/models.py:203
msgid "active"
msgstr "actief"
-#: core/models.py:199
+#: core/models.py:206
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -265,64 +295,64 @@ msgstr ""
"Of deze gebruiker als actief moet worden behandeld. Deselecteer dit in "
"plaats van accounts te verwijderen."
-#: core/models.py:212
+#: core/models.py:219
msgid "user"
msgstr "gebruiker"
-#: core/models.py:213
+#: core/models.py:220
msgid "users"
msgstr "gebruikers"
-#: core/models.py:272
+#: core/models.py:286
msgid "Resource"
msgstr "Bron"
-#: core/models.py:273
+#: core/models.py:287
msgid "Resources"
msgstr "Bronnen"
-#: core/models.py:331
+#: core/models.py:345
msgid "Resource access"
msgstr "Brontoegang"
-#: core/models.py:332
+#: core/models.py:346
msgid "Resource accesses"
msgstr "Brontoegangsrechten"
-#: core/models.py:338
+#: core/models.py:352
msgid "Resource access with this User and Resource already exists."
msgstr "Brontoegang met deze gebruiker en bron bestaat al."
-#: core/models.py:394
+#: core/models.py:409
msgid "Visio room configuration"
msgstr "Visio-ruimteconfiguratie"
-#: core/models.py:395
+#: core/models.py:410
msgid "Values for Visio parameters to configure the room."
msgstr "Waarden voor Visio-parameters om de ruimte te configureren."
-#: core/models.py:402
+#: core/models.py:417
msgid "Room PIN code"
msgstr "Pincode van de kamer"
-#: core/models.py:403
+#: core/models.py:418
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Unieke n-cijferige code die deze kamer identificeert in telefonie-modus."
-#: core/models.py:409 core/models.py:563
+#: core/models.py:424 core/models.py:578
msgid "Room"
msgstr "Ruimte"
-#: core/models.py:410
+#: core/models.py:425
msgid "Rooms"
msgstr "Ruimtes"
-#: core/models.py:574
+#: core/models.py:589
msgid "Worker ID"
msgstr "Worker ID"
-#: core/models.py:576
+#: core/models.py:591
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -330,140 +360,152 @@ msgstr ""
"Voer een identificatie in voor de worker-opname. Deze ID blijft behouden, "
"zelfs wanneer de worker stopt, waardoor eenvoudige tracking mogelijk is."
-#: core/models.py:584
+#: core/models.py:599
msgid "Recording mode"
msgstr "Opnamemodus"
-#: core/models.py:585
+#: core/models.py:600
msgid "Defines the mode of recording being called."
msgstr "Definieert de modus van opname die wordt aangeroepen."
-#: core/models.py:590 core/models.py:591
+#: core/models.py:605 core/models.py:606
msgid "Recording options"
msgstr "Opnameopties"
-#: core/models.py:597
+#: core/models.py:613
+msgid "External Process ID"
+msgstr "Externe proces-ID"
+
+#: core/models.py:614
+msgid "ID of the external process associated with the recording."
+msgstr "ID van het externe proces dat aan de opname is gekoppeld."
+
+#: core/models.py:620
msgid "Recording"
msgstr "Opname"
-#: core/models.py:598
+#: core/models.py:621
msgid "Recordings"
msgstr "Opnames"
-#: core/models.py:706
+#: core/models.py:731
msgid "Recording/user relation"
msgstr "Opname/gebruiker-relatie"
-#: core/models.py:707
+#: core/models.py:732
msgid "Recording/user relations"
msgstr "Opname/gebruiker-relaties"
-#: core/models.py:713
+#: core/models.py:738
msgid "This user is already in this recording."
msgstr "Deze gebruiker is al in deze opname."
-#: core/models.py:719
+#: core/models.py:744
msgid "This team is already in this recording."
msgstr "Dit team is al in deze opname."
-#: core/models.py:725
+#: core/models.py:750
msgid "Either user or team must be set, not both."
msgstr "Ofwel gebruiker of team moet worden ingesteld, niet beide."
-#: core/models.py:742
+#: core/models.py:767
msgid "Create rooms"
msgstr "Ruimtes aanmaken"
-#: core/models.py:743
+#: core/models.py:768
msgid "List rooms"
msgstr "Ruimtes weergeven"
-#: core/models.py:744
+#: core/models.py:769
msgid "Retrieve room details"
msgstr "Details van een ruimte ophalen"
-#: core/models.py:745
+#: core/models.py:770
msgid "Update rooms"
msgstr "Ruimtes bijwerken"
-#: core/models.py:746
+#: core/models.py:771
msgid "Delete rooms"
msgstr "Ruimtes verwijderen"
-#: core/models.py:759
+#: core/models.py:784
msgid "Application name"
msgstr "Naam van de applicatie"
-#: core/models.py:760
+#: core/models.py:785
msgid "Descriptive name for this application."
msgstr "Beschrijvende naam voor deze applicatie."
-#: core/models.py:770
+#: core/models.py:795
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr ""
"Wordt gehasht bij het opslaan. Kopieer het nu als dit een nieuw geheim is."
-#: core/models.py:781
+#: core/models.py:806
msgid "Application"
msgstr "Applicatie"
-#: core/models.py:782
+#: core/models.py:807
msgid "Applications"
msgstr "Applicaties"
-#: core/models.py:805
+#: core/models.py:830
msgid "Enter a valid domain"
msgstr "Voer een geldig domein in"
-#: core/models.py:808
+#: core/models.py:833
msgid "Domain"
msgstr "Domein"
-#: core/models.py:809
+#: core/models.py:834
msgid "Email domain this application can act on behalf of."
msgstr "E-maildomein namens welke deze applicatie kan handelen."
-#: core/models.py:821
+#: core/models.py:846
msgid "Application domain"
msgstr "Applicatiedomein"
-#: core/models.py:822
+#: core/models.py:847
msgid "Application domains"
msgstr "Applicatiedomeinen"
-#: core/models.py:840
+#: core/models.py:865
msgid "Pending"
msgstr "In afwachting"
-#: core/models.py:848
+#: core/models.py:866
+msgid "Analyzing"
+msgstr ""
+
+#: core/models.py:873
msgid "Ready"
msgstr "Klaar"
-#: core/models.py:854
+#: core/models.py:879
msgid "Background image"
msgstr "Achtergrondafbeelding"
-#: core/models.py:866
+#: core/models.py:891
msgid "title"
msgstr "Titel"
-#: core/models.py:890
+#: core/models.py:915
msgid "Malware detection info when the analysis status is unsafe."
msgstr "Informatie over malwaredetectie wanneer de analysestatus onveilig is."
-#: core/models.py:895
+#: core/models.py:920
msgid "File"
msgstr "Bestand"
-#: core/models.py:896
+#: core/models.py:921
msgid "Files"
msgstr "Bestanden"
-#: core/models.py:1000
+#: core/models.py:1041
msgid "This file is already hard deleted."
msgstr "Dit bestand is al definitief verwijderd."
-#: core/models.py:1010
+#: core/models.py:1051
#, fuzzy
#| msgid "To hard delete a file, it must first be soft deleted."
msgid "To hard delete a file, it must first be soft deleted."
@@ -471,10 +513,20 @@ msgstr ""
"Om een bestand definitief te verwijderen, moet het eerst zacht verwijderd "
"zijn."
-#: core/recording/event/notification.py:116
+#: core/recording/event/notification.py:123
msgid "Your recording is ready"
msgstr "Je opname is klaar"
+#: core/recording/event/notification.py:194
+msgid "Transcription"
+msgstr "Transcriptie"
+
+#: core/recording/event/notification.py:204
+#, python-brace-format
+msgid "Meeting \"{room}\" on {room_recording_date} at {room_recording_time}"
+msgstr ""
+"Vergadering \"{room}\" op {room_recording_date} om {room_recording_time}"
+
#: core/services/invitation.py:44
#, python-brace-format
msgid "Video call in progress: {sender.email} is waiting for you to connect"
@@ -570,7 +622,7 @@ msgid "To keep this recording permanently:"
msgstr "Om deze opname permanent te bewaren:"
#: core/templates/mail/html/screen_recording.html:208
-#: core/templates/mail/text/screen_recording.txt:13
+#, python-format
msgid "Click the \"Open\" link below "
msgstr "Klik op de \"Openen\"-link hieronder "
@@ -599,18 +651,24 @@ msgstr ""
" Als je vragen hebt of hulp nodig hebt, neem dan contact op met ons support "
"team via %(support_email)s. "
-#: meet/settings.py:223
+#: core/templates/mail/text/screen_recording.txt:13
+#, fuzzy, python-format
+#| msgid "Click the \"Open\" link below "
+msgid "Click the \"Open [%(link)s]\" link below "
+msgstr "Klik op de \"Openen\"-link hieronder "
+
+#: meet/settings.py:228
msgid "English"
msgstr "Engels"
-#: meet/settings.py:224
+#: meet/settings.py:229
msgid "French"
msgstr "Frans"
-#: meet/settings.py:225
+#: meet/settings.py:230
msgid "Dutch"
msgstr "Nederlands"
-#: meet/settings.py:226
+#: meet/settings.py:231
msgid "German"
msgstr "Duits"
diff --git a/src/backend/meet/settings.py b/src/backend/meet/settings.py
index 0de934da..1aacc705 100755
--- a/src/backend/meet/settings.py
+++ b/src/backend/meet/settings.py
@@ -748,12 +748,30 @@ class Base(Configuration):
environ_prefix=None,
)
+ SUMMARY_SERVICE_VERSION = values.PositiveIntegerValue(
+ 1, environ_name="SUMMARY_SERVICE_VERSION", environ_prefix=None
+ )
SUMMARY_SERVICE_ENDPOINT = values.Value(
None, environ_name="SUMMARY_SERVICE_ENDPOINT", environ_prefix=None
)
SUMMARY_SERVICE_API_TOKEN = SecretFileValue(
None, environ_name="SUMMARY_SERVICE_API_TOKEN", environ_prefix=None
)
+ SUMMARY_SERVICE_WEBHOOK_API_TOKEN = SecretFileValue(
+ None, environ_name="SUMMARY_SERVICE_WEBHOOK_API_TOKEN", environ_prefix=None
+ )
+ SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS = (
+ values.PositiveIntegerValue(
+ 60 * 60 * 24,
+ environ_name="SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS",
+ environ_prefix=None,
+ )
+ )
+ TRANSCRIPTION_SATISFACTION_FORM_BASE_URL = values.Value(
+ None,
+ environ_name="TRANSCRIPTION_SATISFACTION_FORM_BASE_URL",
+ environ_prefix=None,
+ )
SCREEN_RECORDING_BASE_URL = values.Value(
None, environ_name="SCREEN_RECORDING_BASE_URL", environ_prefix=None
)
diff --git a/src/frontend/src/features/recording/routes/RecordingDownload.tsx b/src/frontend/src/features/recording/routes/RecordingDownload.tsx
index e50c3296..bc129dc7 100644
--- a/src/frontend/src/features/recording/routes/RecordingDownload.tsx
+++ b/src/frontend/src/features/recording/routes/RecordingDownload.tsx
@@ -57,7 +57,9 @@ const RecordingDownload = () => {
const isSaved =
data?.status === RecordingStatus.Saved ||
data?.status === RecordingStatus.NotificationSucceed ||
- data?.status === RecordingStatus.FailedToStop
+ data?.status === RecordingStatus.FailedToStop ||
+ data?.status === RecordingStatus.ExternalProcessFailed ||
+ data?.status === RecordingStatus.ExternalProcessSuccessful
const pageTitle = useMemo(() => {
if (isError) return `${APP_TITLE} - ${t('error.title')}`
@@ -91,6 +93,8 @@ const RecordingDownload = () => {
}
if (
+ data.status !== RecordingStatus.ExternalProcessFailed &&
+ data.status !== RecordingStatus.ExternalProcessSuccessful &&
data.status !== RecordingStatus.Saved &&
data.status !== RecordingStatus.NotificationSucceed &&
data.status !== RecordingStatus.FailedToStop
diff --git a/src/frontend/src/features/recording/types.ts b/src/frontend/src/features/recording/types.ts
index d41b6090..fc450bfe 100644
--- a/src/frontend/src/features/recording/types.ts
+++ b/src/frontend/src/features/recording/types.ts
@@ -12,4 +12,6 @@ export enum RecordingStatus {
FailedToStart = 'failedToStart',
FailedToStop = 'failedToStop',
NotificationSucceed = 'notification_succeeded',
+ ExternalProcessSuccessful = 'external_process_successful',
+ ExternalProcessFailed = 'external_process_failed',
}
diff --git a/src/helm/env.d/common.yaml.gotmpl b/src/helm/env.d/common.yaml.gotmpl
index 26fbb043..0d74ad3e 100644
--- a/src/helm/env.d/common.yaml.gotmpl
+++ b/src/helm/env.d/common.yaml.gotmpl
@@ -38,7 +38,7 @@ _summaryEnvVars: &summaryEnvVars
"allowed_push_to_docs": false
},
{
- "id": "visio",
+ "id": "meet",
"api_key": "password",
"webhook_url": "https://meet.127.0.0.1.nip.io/api/v1.0/recordings/external-process-hook/",
"webhook_api_key": "webhook-password",