Compare commits

..

2 Commits

Author SHA1 Message Date
lebaudantoine 2c678087b3 🔖(addons) remove the beta tag
Promote the addon to a stable v1 release.
2026-07-21 10:25:27 +02:00
lebaudantoine b6a57b5545 (addon) show add-in tools when creating meetings in shared calendars
The existing manifest was not exposing the add-in tools when
creating a meeting in a shared calendar. Amend the manifest with the
missing instructions so the add-in shows up in that context as well.

Manifest changes generated with Claude's assistance.
2026-07-21 10:16:14 +02:00
23 changed files with 120 additions and 153 deletions
-15
View File
@@ -8,20 +8,6 @@ and this project adheres to
## [Unreleased]
## Fixed
- 🐛(transcription) fix silent bug in speaker assignment
### Changed
- ⬆️(frontend) upgrade @mediapipe/tasks-vision from 0.10.14 to 0.10.35
- ⬆️(frontend) upgrade i18next from 26.3.1 to 26.3.2
- ⬆️(frontend) upgrade posthog-js from 1.391.2 to 1.395.0
- ⬆️(frontend) upgrade @tanstack/react-query from 5.101.0 to 5.101.1
- ⬆️(frontend) upgrade livekit-client from 2.19.2 to 2.20.0
## [1.24.0] - 2026-07-21
### Added
- ✨(backend) allow searching the recording admin table by owner email
@@ -48,7 +34,6 @@ and this project adheres to
- 🩹(backend) identify externally provisioned users to PostHog
- 🐛(backend) fix info panel crash for unregistered rooms
- ♿️(frontend) focus side panel container on open #1452
- 🐛(summary) whisper call error handling
## [1.23.0] - 2026-07-08
-1
View File
@@ -164,7 +164,6 @@ echo " - src/backend/pyproject.toml"
echo " - src/backend/uv.lock"
echo " - src/summary/pyproject.toml"
echo " - src/agents/pyproject.toml"
echo " - src/agents/uv.lock"
echo " - CHANGELOG.md"
echo ""
print_warning "Next steps:"
+4 -4
View File
@@ -11,14 +11,14 @@ There are two ways to customize LaSuite Meet:
### How to Use
To use this feature, simply set the `FRONTEND_CUSTOM_CSS_URL` environment variable (of the **backend** service) to the URL of your custom CSS file. For example:
To use this feature, simply set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. For example:
```javascript
FRONTEND_CUSTOM_CSS_URL=https://example.com/custom-style.css
FRONTEND_CSS_URL=https://example.com/custom-style.css
```
> [!TIP]
> If you serve your CSS file on the same domain as LaSuite Meet, paths are supported, i.e. `FRONTEND_CUSTOM_CSS_URL=/custom/style.css` will load `https://your-domain.com/custom/style.css`.
> If you serve your CSS file on the same domain as LaSuite Meet, paths are supported, i.e. `FRONTEND_CSS_URL=/custom/style.css` will load `https://your-domain.com/custom/style.css`.
Setting this variable makes the app load your CSS at runtime, adding a `<link>` to `<head>` so you can override CSS variables and customize the frontend without rebuilding.
@@ -37,7 +37,7 @@ Let's say you want to change the font of our application to a custom font. You c
}
```
Then, set the `FRONTEND_CUSTOM_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the default font to the one you specified.
Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the default font to the one you specified.
> [!IMPORTANT]
> You can override any CSS token—semantic or palette. See [panda.config.ts](../src/frontend/panda.config.ts) for all defined semantic tokens.
+4 -4
View File
@@ -10,7 +10,7 @@
"license": "MIT",
"dependencies": {
"core-js": "3.49.0",
"i18next": "^26.3.2",
"i18next": "26.3.1",
"i18next-browser-languagedetector": "8.2.1",
"regenerator-runtime": "0.14.1"
},
@@ -9364,9 +9364,9 @@
}
},
"node_modules/i18next": {
"version": "26.3.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.2.tgz",
"integrity": "sha512-QQkXAM1sPDHqhxMQuBeHVMUn6mJchF+wdpOoQerciLAFqO3ZYdxO0EUbeEhruyutnNwpUQIITDVzLjwnNL0T1w==",
"version": "26.3.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.1.tgz",
"integrity": "sha512-txQqd5EULsqEh9OJqRH15aCaOuy/nLJyhw5EHCSKLKJE1aBbb3Zve2+uQIxgWhPm1QqUQoWyQBm2kfmmIrzkcQ==",
"funding": [
{
"type": "individual",
+1 -1
View File
@@ -27,7 +27,7 @@
},
"dependencies": {
"core-js": "3.49.0",
"i18next": "26.3.2",
"i18next": "26.3.1",
"i18next-browser-languagedetector": "8.2.1",
"regenerator-runtime": "0.14.1"
},
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "agents"
version = "1.24.0"
version = "1.23.0"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.6.4",
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]]
name = "agents"
version = "1.24.0"
version = "1.23.0"
source = { virtual = "." }
dependencies = [
{ name = "livekit-agents" },
+40 -27
View File
@@ -423,31 +423,6 @@ def detect_mimetype(file_buffer: bytes, filename: str | None = None) -> str:
return mimetype_from_content or "application/octet-stream"
def _get_s3_client(*, override_domain: bool = True):
"""Return an S3 client, honoring the AWS_S3_DOMAIN_REPLACE endpoint override.
AWS_S3_DOMAIN_REPLACE is used when the backend and frontend reach object
storage under different domains (this is the case in the docker compose stack
used in development: the frontend connects to the object storage on localhost
while the backend uses the object storage service name declared in the stack).
The domain name is used to compute the signature, so it can't be changed
dynamically by the frontend; we build a dedicated boto3 client pointed at that
endpoint. Otherwise we reuse the default storage client.
"""
if settings.AWS_S3_DOMAIN_REPLACE and override_domain:
return boto3.client(
"s3",
aws_access_key_id=settings.AWS_S3_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_S3_SECRET_ACCESS_KEY,
endpoint_url=settings.AWS_S3_DOMAIN_REPLACE,
config=botocore.client.Config(
region_name=settings.AWS_S3_REGION_NAME,
signature_version=settings.AWS_S3_SIGNATURE_VERSION,
),
)
return default_storage.connection.meta.client
def generate_upload_policy(file):
"""
Generate a S3 upload policy for a given file.
@@ -458,7 +433,26 @@ def generate_upload_policy(file):
key = file.temporary_file_key
s3_client = _get_s3_client()
# This settings 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
# docker compose stack used in development. The frontend application will use localhost
# to connect to the object storage while the backend application will use the object storage
# service name declared in the docker compose stack.
# This is needed because the domain name is used to compute the signature. So it can't be
# changed dynamically by the frontend application.
if settings.AWS_S3_DOMAIN_REPLACE:
s3_client = boto3.client(
"s3",
aws_access_key_id=settings.AWS_S3_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_S3_SECRET_ACCESS_KEY,
endpoint_url=settings.AWS_S3_DOMAIN_REPLACE,
config=botocore.client.Config(
region_name=settings.AWS_S3_REGION_NAME,
signature_version=settings.AWS_S3_SIGNATURE_VERSION,
),
)
else:
s3_client = default_storage.connection.meta.client
# Generate the policy
policy = s3_client.generate_presigned_url(
@@ -479,7 +473,26 @@ def generate_download_s3_url(
if not key:
raise ValueError("key cannot be empty")
s3_client = _get_s3_client(override_domain=override_domain)
# 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
# docker compose stack used in development. The frontend application will use localhost
# to connect to the object storage while the backend application will use the object storage
# service name declared in the docker compose stack.
# This is needed because the domain name is used to compute the signature. So it can't be
# changed dynamically by the frontend application.
if settings.AWS_S3_DOMAIN_REPLACE and override_domain:
s3_client = boto3.client(
"s3",
aws_access_key_id=settings.AWS_S3_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_S3_SECRET_ACCESS_KEY,
endpoint_url=settings.AWS_S3_DOMAIN_REPLACE,
config=botocore.client.Config(
region_name=settings.AWS_S3_REGION_NAME,
signature_version=settings.AWS_S3_SIGNATURE_VERSION,
),
)
else:
s3_client = default_storage.connection.meta.client
return s3_client.generate_presigned_url(
ClientMethod="get_object",
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project]
name = "meet"
version = "1.24.0"
version = "1.23.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
+1 -1
View File
@@ -1187,7 +1187,7 @@ wheels = [
[[package]]
name = "meet"
version = "1.24.0"
version = "1.23.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+26 -32
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "1.24.0",
"version": "1.23.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "1.24.0",
"version": "1.23.0",
"dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
"@fontsource-variable/lexend": "5.2.11",
@@ -15,11 +15,11 @@
"@livekit/components-react": "2.9.21",
"@livekit/components-styles": "1.2.0",
"@livekit/track-processors": "0.7.2",
"@mediapipe/tasks-vision": "0.10.35",
"@mediapipe/tasks-vision": "0.10.14",
"@pandacss/preset-panda": "1.11.3",
"@react-types/overlays": "3.10.0",
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.1",
"@tanstack/react-query": "5.101.0",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"derive-valtio": "0.2.0",
@@ -29,8 +29,8 @@
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.20.0",
"posthog-js": "1.395.0",
"livekit-client": "2.19.2",
"posthog-js": "1.391.2",
"react": "18.3.1",
"react-aria": "3.50.0",
"react-aria-components": "1.19.0",
@@ -1028,9 +1028,9 @@
"license": "Apache-2.0"
},
"node_modules/@livekit/protocol": {
"version": "1.46.6",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.46.6.tgz",
"integrity": "sha512-upzlHP1vi/kZ/QqALZTFskQ0ifqc2f15RKucHYOsIHJsaXvEYanG75mAb7o+Yomfs4XhQ4BaRsdY+TFHXpaqrg==",
"version": "1.45.8",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.45.8.tgz",
"integrity": "sha512-Q+l57E7w/xxOBFVWzdX5rkAZO7ffyF+rlDzNUYq2SU114+5aTyCq+PK4unaEVDNd4952Af7wteKr3sOgasGuaA==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^1.10.0"
@@ -1049,18 +1049,12 @@
"livekit-client": "^1.12.0 || ^2.1.0"
}
},
"node_modules/@livekit/track-processors/node_modules/@mediapipe/tasks-vision": {
"node_modules/@mediapipe/tasks-vision": {
"version": "0.10.14",
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.14.tgz",
"integrity": "sha512-vOifgZhkndgybdvoRITzRkIueWWSiCKuEUXXK6Q4FaJsFvRJuwgg++vqFUMlL0Uox62U5aEXFhHxlhV7Ja5e3Q==",
"license": "Apache-2.0"
},
"node_modules/@mediapipe/tasks-vision": {
"version": "0.10.35",
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.35.tgz",
"integrity": "sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==",
"license": "Apache-2.0"
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
@@ -2421,9 +2415,9 @@
}
},
"node_modules/@tanstack/query-core": {
"version": "5.101.1",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.1.tgz",
"integrity": "sha512-Y6Y92dkXtNqx67m2pMSxUsA3zOCwv862JexZRP8/EPwvKXMPu9m8rv43spiXWzOUIggQ3SQApttALStzhA8B4g==",
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz",
"integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2442,12 +2436,12 @@
}
},
"node_modules/@tanstack/react-query": {
"version": "5.101.1",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.1.tgz",
"integrity": "sha512-ZnONUuQKJe1bJMStXUL1s5uKN9FcfC28j5cK+iDZcdSHtUv1wtin1cGc/Oewhf2Oc4eKY7lggtpvT/AbMmhHew==",
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz",
"integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.101.1"
"@tanstack/query-core": "5.101.0"
},
"funding": {
"type": "github",
@@ -8125,13 +8119,13 @@
"license": "MIT"
},
"node_modules/livekit-client": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.20.0.tgz",
"integrity": "sha512-RIJcpvBmOmwz3jTj3rmdY6Dzr55HrhcaJjMgY+HSmoEM+yIRyA40m7r8UKv0hnZWM3z/AYhP1q8C8ciz5UWFKQ==",
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.19.2.tgz",
"integrity": "sha512-Kvk07QYDWRAbmYNLRll04ZIuxMQobW/oLPYnmR1kCy8GGHpU0gqyHf704Rz+29zfy8IJZRjKqeVbzGSKn9sumw==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/mutex": "1.1.1",
"@livekit/protocol": "1.46.6",
"@livekit/protocol": "1.45.8",
"events": "^3.3.0",
"jose": "^6.1.0",
"loglevel": "^1.9.2",
@@ -9136,13 +9130,13 @@
"license": "MIT"
},
"node_modules/posthog-js": {
"version": "1.395.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.395.0.tgz",
"integrity": "sha512-5iTb00CGt2eQUUiBQysQiX89RAbCN6wK2sDNzvs9zv0alaY8mJ0ZySrUD3LQ+XyLhgM5pCpacBuUwChqiYDLDw==",
"version": "1.391.2",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.391.2.tgz",
"integrity": "sha512-q0DZN6ljchSnAFJIXf+sQFTPlsLjTlRa+TvrL+QRb6413BGtib/MNiQy1bnwLKt8KR+f6xJYvkqdLyty9s4Aww==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@posthog/core": "^1.38.0",
"@posthog/types": "^1.391.1",
"@posthog/core": "^1.35.3",
"@posthog/types": "^1.390.2",
"core-js": "^3.38.1",
"dompurify": "^3.3.2",
"fflate": "^0.4.8",
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "1.24.0",
"version": "1.23.0",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
@@ -22,11 +22,11 @@
"@livekit/components-react": "2.9.21",
"@livekit/components-styles": "1.2.0",
"@livekit/track-processors": "0.7.2",
"@mediapipe/tasks-vision": "0.10.35",
"@mediapipe/tasks-vision": "0.10.14",
"@pandacss/preset-panda": "1.11.3",
"@react-types/overlays": "3.10.0",
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.1",
"@tanstack/react-query": "5.101.0",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"derive-valtio": "0.2.0",
@@ -36,8 +36,8 @@
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.20.0",
"posthog-js": "1.395.0",
"livekit-client": "2.19.2",
"posthog-js": "1.391.2",
"react": "18.3.1",
"react-aria": "3.50.0",
"react-aria-components": "1.19.0",
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.27
version: 0.0.26
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "1.24.0",
"version": "1.23.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "1.24.0",
"version": "1.23.0",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "1.24.0",
"version": "1.23.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sdk",
"version": "1.24.0",
"version": "1.23.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "1.24.0",
"version": "1.23.0",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "1.24.0",
"version": "1.23.0",
"author": "",
"license": "ISC",
"description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "1.24.0"
version = "1.23.0"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",
+3 -18
View File
@@ -18,12 +18,7 @@ from requests import exceptions
from summary.core.analytics import MetadataManager, get_analytics
from summary.core.config import get_settings
from summary.core.docs_service import create_document_in_lasuite_docs
from summary.core.file_service import (
CorruptedAudioFile,
FileService,
FileServiceException,
TranscribeError,
)
from summary.core.file_service import FileService, FileServiceException, TranscribeError
from summary.core.llm_service import LLMException, LLMObservability, LLMService
from summary.core.locales import get_locale
from summary.core.models import (
@@ -152,20 +147,10 @@ def transcribe_audio(
# Mimic OpenAI's timeout settings
timeout=(60, 10 * 60),
)
if res.status_code == 400:
logger.info(
"WhisperX transcription failed, "
"likely due to a corrupted audio file: %s",
res.text,
)
raise CorruptedAudioFile("WhisperX coudln't decode the audio file.")
try:
res.raise_for_status()
except requests.exceptions.HTTPError:
logger.exception("WhisperX transcription failed")
# We reraise the error so that it can be retried by celery
raise
except requests.exceptions.HTTPError as e:
raise RuntimeError("WhisperX transcription failed, %s", res.text) from e
transcription_json: dict[str, Any] = res.json()
# We remove the "usage" key from the transcription_json dictionary
-6
View File
@@ -41,12 +41,6 @@ class NoAudioInFileError(TranscribeError):
error_code = "no_audio_in_file"
class CorruptedAudioFile(TranscribeError):
"""Raised when a media file does not contain any audio."""
error_code = "corrupted_audio_file"
def _get_duration_from_packets(local_path: Path) -> float:
"""Estimate duration from audio packet timestamps."""
# Run ffprobe to inspect the first audio stream in the file.
+1 -7
View File
@@ -96,13 +96,7 @@ class TranscribeWebhookFailurePayload(BaseWebhook):
# we authorized any other string than the one in the literal
# to avoid causing a breaking change on the client side
error_code: (
Literal[
"unknown_error",
"no_audio_in_file",
"media_duration_too_long",
"corrupted_audio_file",
]
| str
Literal["unknown_error", "no_audio_in_file", "media_duration_too_long"] | str
) = Field(title="Error code", description="The error code.")
+1 -1
View File
@@ -275,7 +275,7 @@ def _build_participant_timelines(
def _build_speaker_timelines(transcription: Any) -> dict[str, list[Interval]]:
"""Build interval timelines from WhisperX transcription segments."""
intervals: dict[str, list[Interval]] = {}
segments = transcription.get("segments") or []
segments = transcription.segments if hasattr(transcription, "segments") else []
max_word_duration = settings.resolve_speaker_identities_max_word_duration
for segment in segments:
+23 -20
View File
@@ -1,6 +1,7 @@
"""Tests for the speaker-to-user assignment service."""
import math
from dataclasses import dataclass, field
from datetime import datetime
from summary.core import user_assign
@@ -16,9 +17,11 @@ from summary.core.user_assign import (
)
def make_transcription(segments: list | None = None) -> dict:
"""Build a WhisperX transcription dict, matching WhisperXResponse.model_dump()."""
return {"segments": segments or []}
@dataclass
class FakeTranscription:
"""Mimics the OpenAI Transcription pydantic model for testing."""
segments: list = field(default_factory=list)
RECORDING_START = datetime.fromisoformat("2026-03-17T15:30:33.000001")
@@ -65,7 +68,7 @@ METADATA_SINGLE_USER = {
],
}
DIARIZATION_SINGLE_SPEAKER = make_transcription(
DIARIZATION_SINGLE_SPEAKER = FakeTranscription(
segments=[
{
"start": 1.363,
@@ -179,7 +182,7 @@ class TestBuildSpeakerTimelines:
def test_segment_without_words_falls_back_to_segment_bounds(self):
"""Segments missing a `words` key use the segment start/end as one interval."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[{"start": 1.5, "end": 3.5, "speaker": "SPEAKER_00"}],
)
result = _build_speaker_timelines(transcription)
@@ -187,7 +190,7 @@ class TestBuildSpeakerTimelines:
def test_segment_with_only_none_word_timestamps_falls_back(self):
"""If every word has None start/end, fall back to segment bounds."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 1.0,
@@ -205,7 +208,7 @@ class TestBuildSpeakerTimelines:
def test_short_words_only_uses_segment_start_and_last_word_end(self):
"""With no overly long words, the interval runs segment start to end."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 1.0,
@@ -228,7 +231,7 @@ class TestBuildSpeakerTimelines:
max_word_duration = (
user_assign.settings.resolve_speaker_identities_max_word_duration
)
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -249,7 +252,7 @@ class TestBuildSpeakerTimelines:
def test_long_word_in_middle_splits_segment(self):
"""Short words around a long word produce two intervals (before-cap + after)."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -272,7 +275,7 @@ class TestBuildSpeakerTimelines:
def test_tail_word_is_capped_at_max_duration(self):
"""The trailing word's end is capped at word.start + max_word_duration."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -299,7 +302,7 @@ class TestBuildSpeakerTimelines:
update={"resolve_speaker_identities_enable_split_on_words": False},
),
)
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -319,7 +322,7 @@ class TestBuildSpeakerTimelines:
def test_multiple_speakers_keep_separate_timelines(self):
"""Segments from different speakers populate independent timeline entries."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -399,7 +402,7 @@ class TestResolveSpeakerIdentities:
],
"participants": [{"participantId": "user-a", "name": "Shared Mic"}],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.0, "end": 3.0, "speaker": "SPEAKER_00"},
{"start": 5.0, "end": 7.0, "speaker": "SPEAKER_01"},
@@ -442,7 +445,7 @@ class TestResolveSpeakerIdentities:
{"participantId": "user-b", "name": "Bob"},
],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.5, "end": 3.5, "speaker": "SPEAKER_00"},
{"start": 5.5, "end": 7.5, "speaker": "SPEAKER_01"},
@@ -490,7 +493,7 @@ class TestResolveSpeakerIdentities:
{"participantId": "user-b", "name": "Bob"},
],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.5, "end": 5.5, "speaker": "SPEAKER_00"},
{"start": 4.0, "end": 7.5, "speaker": "SPEAKER_01"},
@@ -526,7 +529,7 @@ class TestResolveSpeakerIdentities:
],
"participants": [{"participantId": "user-a", "name": "Brief User"}],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.0, "end": 10.0, "speaker": "SPEAKER_00"},
],
@@ -558,7 +561,7 @@ class TestResolveSpeakerIdentities:
],
"participants": [{"participantId": "user-a", "name": "Early User"}],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 0.0, "end": 3.0, "speaker": "SPEAKER_00"},
],
@@ -573,7 +576,7 @@ class TestResolveSpeakerIdentities:
"""No segments produces empty result."""
result = resolve_speaker_identities(
METADATA_SINGLE_USER,
make_transcription(segments=[]),
FakeTranscription(segments=[]),
RECORDING_START,
RECORDING_END,
)
@@ -581,7 +584,7 @@ class TestResolveSpeakerIdentities:
def test_segment_without_speaker_ignored(self):
"""Segments missing speaker key are skipped."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.0, "end": 3.0, "text": "no speaker"},
],
@@ -605,7 +608,7 @@ class TestResolveSpeakerIdentities:
],
"participants": [{"participantId": "user-a", "name": "Still Talking"}],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 2.0, "end": 9.0, "speaker": "SPEAKER_00"},
],