Refactor audio storage to MongoDB chunks and enhance cleanup settings…#262
Refactor audio storage to MongoDB chunks and enhance cleanup settings…#262AnkushMalaker wants to merge 3 commits intofix/audio-pipe-2from
Conversation
… management - Replaced the legacy AudioFile model with AudioChunkDocument for storing audio data in MongoDB, optimizing storage and retrieval. - Introduced CleanupSettings dataclass for managing soft-deletion configurations, including auto-cleanup and retention days. - Added admin API routes for retrieving and saving cleanup settings, ensuring better control over data retention policies. - Updated audio processing workflows to utilize MongoDB chunks, removing dependencies on disk-based audio files. - Enhanced tests to validate the new audio chunk storage and cleanup functionalities, ensuring robust integration with existing systems.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 📝 WalkthroughWalkthroughThe pull request introduces MongoDB-backed audio chunk storage, replacing disk-based file persistence. Audio is now stored as Opus-encoded chunks in MongoDB rather than WAV files on disk. Cleanup settings enable retention-based purging of soft-deleted conversations. Admin endpoints manage cleanup configuration. Audio upload, retrieval, and worker jobs are refactored to use the new chunk-based architecture. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant AudioController
participant AudioUtils
participant AudioChunkUtils
participant MongoDB
participant Conversation
Client->>AudioController: POST /audio/upload
AudioController->>AudioUtils: validate_and_prepare_audio()
AudioUtils-->>AudioController: audio_data, metadata
AudioController->>AudioChunkUtils: convert_audio_to_chunks()
AudioChunkUtils->>AudioChunkUtils: Split into 10s chunks
AudioChunkUtils->>AudioChunkUtils: Encode PCM to Opus
AudioChunkUtils->>MongoDB: Insert AudioChunkDocument(s)
MongoDB-->>AudioChunkUtils: Chunk IDs
AudioChunkUtils->>Conversation: Update audio_chunks_count, duration, compression_ratio
AudioChunkUtils-->>AudioController: chunk_count
AudioController->>Conversation: Create Conversation (audio_file_path=None)
AudioController-->>Client: 200 OK, conversation_id
sequenceDiagram
participant Client
participant AudioRoutes
participant AudioChunkUtils
participant MongoDB
participant Conversation
Client->>AudioRoutes: GET /audio/{conversation_id}
AudioRoutes->>AudioChunkUtils: retrieve_audio_chunks(conversation_id)
AudioChunkUtils->>MongoDB: Query AudioChunkDocument by conversation_id
MongoDB-->>AudioChunkUtils: List[AudioChunkDocument]
AudioChunkUtils->>AudioChunkUtils: concatenate_chunks_to_pcm()
AudioChunkUtils->>AudioChunkUtils: Decode Opus chunks to PCM
AudioChunkUtils->>AudioChunkUtils: build_wav_from_pcm()
AudioChunkUtils-->>AudioRoutes: WAV bytes
AudioRoutes->>Client: StreamingResponse(WAV data)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
backends/advanced/src/advanced_omi_backend/models/job.py (1)
48-52: Bug: Theraisestatement negates the fallback logic.The exception handler assigns
database = client[mongodb_database]as a fallback whenget_default_database()fails, but then immediately re-raises the exception, making the fallback useless. The code never reachesinit_beanie()in this case.This appears to be an unintended
raisethat should be removed to allow the fallback to work correctly.🐛 Proposed fix
try: database = client.get_default_database(mongodb_database) except ConfigurationError: database = client[mongodb_database] - raise - _beanie_initialized = Truebackends/advanced/src/advanced_omi_backend/controllers/websocket_controller.py (1)
873-903: Misleading error handling - chunk conversion failure will cause downstream job failures.The comment on line 891 states "Continue anyway - transcription job will handle it", but this is incorrect. Based on
transcription_jobs.py, the transcription job now reconstructs audio from MongoDB chunks viareconstruct_wav_from_conversation(). If chunk conversion fails here, the transcription job will raiseFileNotFoundError("No audio chunks found...").Consider either:
- Re-raising the exception to fail fast and avoid enqueuing jobs that will fail
- Updating the comment to accurately describe the behavior (jobs will fail, but conversation record exists for retry)
🔧 Option 1: Fail fast to avoid orphaned jobs
except Exception as chunk_error: application_logger.error( f"Failed to convert batch audio to chunks: {chunk_error}", exc_info=True ) - # Continue anyway - transcription job will handle it + # Cannot proceed without audio chunks - transcription job will fail + raise📝 Option 2: Update comment to be accurate
except Exception as chunk_error: application_logger.error( f"Failed to convert batch audio to chunks: {chunk_error}", exc_info=True ) - # Continue anyway - transcription job will handle it + # Note: Transcription will fail with FileNotFoundError, but conversation exists for debuggingbackends/advanced/src/advanced_omi_backend/workers/speaker_jobs.py (1)
121-128: Remove the unusedaudio_pathparameter.The
audio_pathparameter is never used in the function. Audio is reconstructed from MongoDB chunks (line 198), and the speaker recognition service receivestemp_wav_pathinstead (line 248). This requires updating the two call sites as well.♻️ Remove unused parameter
@async_job(redis=True, beanie=True) async def recognise_speakers_job( conversation_id: str, version_id: str, - audio_path: str, transcript_text: str, words: list, *, redis_client=None ) -> Dict[str, Any]:Update callers:
backends/advanced/src/advanced_omi_backend/controllers/conversation_controller.pyline 454: Removestr(full_audio_path),backends/advanced/src/advanced_omi_backend/controllers/queue_controller.pyline 442: Removeaudio_file_path,backends/advanced/src/advanced_omi_backend/workers/conversation_jobs.py (1)
527-534: Critical:file_pathis undefined, causing aNameErrorat runtime.The refactoring replaced
wait_for_audio_file()(which returned a file path) withwait_for_audio_chunks()(which returns a boolean), but line 530 still references the undefinedfile_pathvariable in thestart_post_conversation_jobscall.The downstream jobs (
transcribe_full_audio_jobandrecognise_speakers_job) both reconstruct audio from MongoDB chunks and do not use theaudio_file_pathparameter, so this parameter can be safely removed or set toNoneif the function signature is updated to acceptOptional[str].🐛 Suggested fix
Either remove the parameter entirely:
job_ids = start_post_conversation_jobs( conversation_id=conversation_id, audio_uuid=session_id, - audio_file_path=file_path, user_id=user_id, post_transcription=True, # Run batch transcription for streaming audio client_id=client_id # Pass client_id for UI tracking )Or update
start_post_conversation_jobssignature to acceptOptional[str]and passNone.backends/advanced/src/advanced_omi_backend/controllers/audio_controller.py (1)
134-163: Processing continues after chunk conversion failure.If
convert_audio_to_chunksfails (lines 147-151), the error is logged but execution continues to enqueue transcription jobs (lines 156-163). These downstream jobs will fail since no audio chunks were stored.Consider failing the upload for this file when chunk conversion fails:
🐛 Proposed fix
try: num_chunks = await convert_audio_to_chunks( conversation_id=conversation_id, audio_data=audio_data, sample_rate=sample_rate, channels=channels, sample_width=sample_width, ) audio_logger.info( f"📦 Converted uploaded file to {num_chunks} MongoDB chunks " f"(conversation {conversation_id[:12]})" ) except Exception as chunk_error: audio_logger.error( f"Failed to convert uploaded file to chunks: {chunk_error}", exc_info=True ) + # Delete the conversation since we can't store audio + await conversation.delete() + processed_files.append({ + "filename": file.filename, + "status": "error", + "error": f"Failed to store audio: {chunk_error}", + }) + continue
🤖 Fix all issues with AI agents
In
@backends/advanced/src/advanced_omi_backend/controllers/conversation_controller.py:
- Around line 181-216: The _soft_delete_conversation function uses
datetime.utcnow() but datetime is not imported; fix by adding the missing import
(e.g., add "from datetime import datetime") at the top of the module so calls in
_soft_delete_conversation (and its update_many payload) resolve correctly and
avoid a NameError.
In @backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py:
- Around line 321-331: The end_byte calculation can go negative for partial
final chunks because bytes_from_end assumes CHUNK_DURATION; update the logic
around start_offset_in_chunk/end_offset_in_chunk/bytes_from_end/end_byte to use
the actual retrieved chunk duration (e.g., use each chunk's stored duration
field) when converting to bytes, and then clamp the resulting end_byte to valid
bounds via end_byte = max(start_byte, min(end_byte, len(pcm_buffer))) to prevent
negative or out-of-range slices of pcm_buffer.
In @tests/infrastructure/infra_tests.robot:
- Around line 282-310: The variable ${conversation_id} may be undefined if the
FOR loop finishes without finding a job; before the loop starts initialize
${conversation_id} to a known value (e.g., Set Variable ${None}) so the final
assertion (Should Not Be Equal ${conversation_id} ${None}) fails with the
intended message; keep the rest of the logic (Get Conversation ID From Job Meta,
Close Audio Stream, Send Audio Chunks To Stream) unchanged.
🧹 Nitpick comments (23)
backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (1)
121-137: Consider annotatingindexeswithClassVarto satisfy static analysis.The static analysis tool flags mutable class attributes without
ClassVarannotation (Ruff RUF012). While this works correctly at runtime with Beanie, adding the annotation improves type safety.♻️ Suggested fix
+from typing import ClassVar, List, Optional, Union ... class Settings: """Beanie document settings.""" name = "audio_chunks" - indexes = [ + indexes: ClassVar[List[Union[str, List[tuple]]]] = [ # Primary query: Retrieve chunks in order for a conversation [("conversation_id", 1), ("chunk_index", 1)], ... ]backends/advanced/src/advanced_omi_backend/config.py (3)
150-154: Consider deferring directory creation to the save function.
get_cleanup_config_path()unconditionally creates the data directory as a side effect, whereas the analogousget_diarization_config_path()only checks existence. Creating directories in a getter can be unexpected behavior.♻️ Suggested refactor
def get_cleanup_config_path() -> Path: """Get path to cleanup settings JSON file.""" data_dir = Path(os.getenv("DATA_DIR", "/app/data")) - data_dir.mkdir(parents=True, exist_ok=True) return data_dir / "cleanup_config.json"Then ensure
save_cleanup_settings_to_filecreates the directory before writing:def save_cleanup_settings_to_file(settings: CleanupSettings) -> None: ... config_path = get_cleanup_config_path() try: + # Ensure parent directory exists + config_path.parent.mkdir(parents=True, exist_ok=True) + # Save to JSON file with open(config_path, "w") as f:
180-181: Uselogging.exceptionto capture the stack trace.When catching exceptions,
logging.exceptionautomatically includes the traceback, which aids debugging.except Exception as e: - logger.error(f"❌ Failed to load cleanup settings from {config_path}: {e}") + logger.exception(f"❌ Failed to load cleanup settings from {config_path}: {e}")
212-214: Uselogging.exceptionbefore re-raising.Since the exception is being re-raised, using
logging.exceptioncaptures the full traceback in the log before propagation.except Exception as e: - logger.error(f"❌ Failed to save cleanup settings to {config_path}: {e}") + logger.exception(f"❌ Failed to save cleanup settings to {config_path}: {e}") raisebackends/advanced/src/advanced_omi_backend/models/job.py (1)
53-60: Minor: Redundant_beanie_initialized = Trueassignment.The flag is set to
Trueat line 53 (beforeinit_beanie) and again at line 60 (after). The first assignment at line 53 should be removed since it's set prematurely before initialization completes.♻️ Suggested cleanup
- _beanie_initialized = True # Initialize Beanie await init_beanie( database=database, document_models=[User, Conversation, AudioChunkDocument], ) _beanie_initialized = Truebackends/advanced/src/advanced_omi_backend/workers/transcription_jobs.py (1)
232-237: Add exception chaining for better traceability.The
raisestatements should useraise ... from eto preserve the original exception context in the traceback. This makes debugging easier.♻️ Proposed fix with exception chaining
except ValueError as e: # No chunks found for conversation - raise FileNotFoundError(f"No audio chunks found for conversation {conversation_id}: {e}") + raise FileNotFoundError(f"No audio chunks found for conversation {conversation_id}: {e}") from e except Exception as e: logger.error(f"Failed to reconstruct audio from MongoDB: {e}", exc_info=True) - raise RuntimeError(f"Audio reconstruction failed: {e}") + raise RuntimeError(f"Audio reconstruction failed: {e}") from etests/integration/mongodb_audio_storage_tests.robot (1)
23-45: Consider replacing fixedSleep 5swith a polling mechanism.Using a fixed 5-second sleep is brittle - chunk persistence could take longer under load or complete faster. Consider using a
Wait Until Keyword Succeedspattern to poll for chunks instead of a fixed delay.♻️ Suggested improvement
- # Wait for chunks to be written to MongoDB - Sleep 5s + # Wait for chunks to be written to MongoDB (poll with timeout) + Wait Until Keyword Succeeds 30s 2s Verify Audio Chunks Exist ${conversation_id} min_chunks=5 - - # Verify chunks exist in MongoDB (expect ~6 chunks for 1-minute audio) - ${chunks}= Verify Audio Chunks Exist ${conversation_id} min_chunks=5 + ${chunks}= Get Audio Chunks For Conversation ${conversation_id}Apply similar changes to other test cases (lines 57, 79, 98).
tests/libs/mongodb_helper.py (1)
37-61: Consider using a context manager for cleaner connection handling.The current try/finally pattern works correctly, but a context manager would be slightly cleaner and could be reused across all functions.
♻️ Optional: Use contextlib for connection management
from contextlib import contextmanager @contextmanager def get_mongo_client(): """Context manager for MongoDB connections.""" client = MongoClient(get_mongodb_uri()) try: yield client[get_db_name()] finally: client.close() def get_audio_chunks(conversation_id): """Get all audio chunks for a conversation from MongoDB.""" with get_mongo_client() as db: chunks = list(db.audio_chunks.find( {"conversation_id": conversation_id}, sort=[("chunk_index", 1)] )) # ... rest of processing return chunksbackends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.py (1)
135-137: Consider usinglogger.exceptionfor better error context.When catching exceptions,
logger.exceptionautomatically includes the stack trace, which aids debugging.♻️ Improved exception logging
except Exception as e: - logger.error(f"Failed to schedule cleanup job: {e}") + logger.exception("Failed to schedule cleanup job") return Nonebackends/advanced/src/advanced_omi_backend/workers/audio_jobs.py (3)
214-214: Remove extraneousfprefix from string without placeholders.♻️ Fix
- logger.info(f"🛑 Session finalizing detected, flushing final chunks...") + logger.info("🛑 Session finalizing detected, flushing final chunks...")
228-228: Rename unused loop variable to indicate it's intentionally unused.♻️ Fix
- for stream_name, msgs in final_messages: + for _stream_name, msgs in final_messages:
353-353: Remove extraneousfprefix from string without placeholders.♻️ Fix
- logger.info(f"✅ Stream empty after END signal - stopping") + logger.info("✅ Stream empty after END signal - stopping")backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py (1)
97-107: Add exception chaining for better error traceability.Using
raise ... from epreserves the original exception context, which aids debugging.♻️ Improved exception handling
try: wav_data = await reconstruct_wav_from_conversation(conversation_id) except ValueError as e: # No chunks found for conversation - raise HTTPException(status_code=404, detail=str(e)) + raise HTTPException(status_code=404, detail=str(e)) from e except Exception as e: # Reconstruction failed - raise HTTPException( - status_code=500, - detail=f"Failed to reconstruct audio: {str(e)}" - ) + raise HTTPException( + status_code=500, + detail=f"Failed to reconstruct audio: {e!s}" + ) from ebackends/advanced/src/advanced_omi_backend/workers/speaker_jobs.py (2)
244-244: Remove extraneousfprefix from string without placeholders.♻️ Fix
- logger.info(f"🎤 Calling speaker recognition service...") + logger.info("🎤 Calling speaker recognition service...")
261-279: Consider usinglogger.exceptionfor the ValueError case.When logging exceptions,
logger.exceptionprovides the full stack trace automatically.♻️ Improved exception logging
except ValueError as e: # No chunks found for conversation - logger.error(f"No audio chunks found for conversation {conversation_id}: {e}") + logger.exception(f"No audio chunks found for conversation {conversation_id}") return {backends/advanced/src/advanced_omi_backend/controllers/audio_controller.py (1)
95-95: Remove unusedtimestampvariable.The
timestampvariable is assigned but never used. Based on static analysis hint.♻️ Proposed fix
- timestamp = int(time.time() * 1000)backends/advanced/src/advanced_omi_backend/controllers/conversation_controller.py (1)
249-297: Delete function handles both soft and permanent deletion correctly.The function properly gates permanent deletion behind
user.is_superusercheck (line 286). However, if a non-superuser passespermanent=True, the behavior silently falls through to soft delete. Consider logging a warning when a non-admin requests permanent deletion.♻️ Consider warning when permanent=True without admin rights
# Hard delete (admin only, permanent flag) if permanent and user.is_superuser: return await _hard_delete_conversation(conversation) + elif permanent: + logger.warning( + f"User {user.user_id} requested permanent deletion without admin rights, " + f"falling back to soft delete for conversation {conversation_id}" + ) # Soft delete (default) return await _soft_delete_conversation(conversation, user)backends/advanced/src/advanced_omi_backend/routers/modules/admin_routes.py (1)
86-122: Preview endpoint looks good, consider adding audit logging.The preview endpoint correctly counts conversations that would be deleted. Consider adding an audit log entry similar to
trigger_cleanupfor consistency and security audit trails.♻️ Add audit logging for preview actions
count = await Conversation.find( Conversation.deleted == True, Conversation.deleted_at < cutoff_date ).count() + logger.info(f"Admin {admin.email} previewed cleanup: {count} conversations (retention={retention_days} days)") + return { "retention_days": retention_days,backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (5)
26-114: Opus encoding implementation is solid.Good use of temp files with proper cleanup in finally block. FFmpeg command is well-documented with comments. One minor issue:
stdoutis captured but unused.♻️ Prefix unused stdout with underscore
- stdout, stderr = await process.communicate() + _stdout, stderr = await process.communicate()
117-196: Opus decoding implementation is consistent with encoding.Same solid patterns as encoding. Same minor issue with unused
stdout.♻️ Prefix unused stdout with underscore
- stdout, stderr = await process.communicate() + _stdout, stderr = await process.communicate()
507-515: Remove debug log statements before production.Lines 507, 513, and 515 contain DEBUG-level logs with the "🔍 DEBUG:" prefix. These appear to be temporary debugging aids that should be removed or converted to proper debug-level logs without the prefix.
♻️ Clean up debug statements
- logger.info(f"🔍 DEBUG: Setting metadata - chunks={chunk_index}, duration={total_duration:.2f}s, ratio={compression_ratio:.3f}") - conversation.audio_chunks_count = chunk_index conversation.audio_total_duration = total_duration conversation.audio_compression_ratio = compression_ratio - logger.info(f"🔍 DEBUG: Before save - chunks={conversation.audio_chunks_count}, duration={conversation.audio_total_duration}") await conversation.save() - logger.info(f"🔍 DEBUG: After save - metadata should be persisted")
530-676: Significant code duplication withconvert_audio_to_chunks.
convert_wav_to_chunksduplicates the chunking and storage logic fromconvert_audio_to_chunks(lines 443-527 vs 587-674). Since this function is marked as deprecated, consider refactoring it to callconvert_audio_to_chunksinternally after reading the WAV file.Also contains the same debug log statements that should be removed (lines 655, 661, 663).
♻️ Reduce duplication by calling convert_audio_to_chunks
async def convert_wav_to_chunks( conversation_id: str, wav_file_path: Path, chunk_duration: float = 10.0, ) -> int: """ Convert an existing WAV file to MongoDB audio chunks. DEPRECATED: Use convert_audio_to_chunks() instead to avoid disk I/O. ... """ if not wav_file_path.exists(): raise FileNotFoundError(f"WAV file not found: {wav_file_path}") logger.info(f"📦 Converting WAV file to MongoDB chunks: {wav_file_path}") # Read WAV file import wave with wave.open(str(wav_file_path), "rb") as wav: sample_rate = wav.getframerate() channels = wav.getnchannels() sample_width = wav.getsampwidth() total_frames = wav.getnframes() pcm_data = wav.readframes(total_frames) logger.info( f"📁 Read WAV: {len(pcm_data)} bytes PCM, " f"{sample_rate}Hz, {channels}ch, {sample_width*8}-bit" ) + # Delegate to in-memory conversion + return await convert_audio_to_chunks( + conversation_id=conversation_id, + audio_data=pcm_data, + sample_rate=sample_rate, + channels=channels, + sample_width=sample_width, + chunk_duration=chunk_duration, + ) - # ... rest of duplicated code ...
704-705: Redundant imports inside function.
timeandasyncioare imported insidewait_for_audio_chunksbutasynciois already imported at module level (line 13).timeis not imported at module level but could be.♻️ Use module-level imports
Add
timeto module-level imports:import asyncio import io import logging import tempfile +import time import waveAnd remove the function-level imports:
- import time - import asyncio
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (33)
backends/advanced/src/advanced_omi_backend/app_factory.pybackends/advanced/src/advanced_omi_backend/config.pybackends/advanced/src/advanced_omi_backend/controllers/audio_controller.pybackends/advanced/src/advanced_omi_backend/controllers/conversation_controller.pybackends/advanced/src/advanced_omi_backend/controllers/system_controller.pybackends/advanced/src/advanced_omi_backend/controllers/websocket_controller.pybackends/advanced/src/advanced_omi_backend/models/audio_chunk.pybackends/advanced/src/advanced_omi_backend/models/audio_file.pybackends/advanced/src/advanced_omi_backend/models/conversation.pybackends/advanced/src/advanced_omi_backend/models/job.pybackends/advanced/src/advanced_omi_backend/routers/api_router.pybackends/advanced/src/advanced_omi_backend/routers/modules/__init__.pybackends/advanced/src/advanced_omi_backend/routers/modules/admin_routes.pybackends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.pybackends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.pybackends/advanced/src/advanced_omi_backend/routers/modules/system_routes.pybackends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.pybackends/advanced/src/advanced_omi_backend/utils/audio_utils.pybackends/advanced/src/advanced_omi_backend/utils/conversation_utils.pybackends/advanced/src/advanced_omi_backend/utils/gdrive_audio_utils.pybackends/advanced/src/advanced_omi_backend/workers/audio_jobs.pybackends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.pybackends/advanced/src/advanced_omi_backend/workers/conversation_jobs.pybackends/advanced/src/advanced_omi_backend/workers/speaker_jobs.pybackends/advanced/src/advanced_omi_backend/workers/transcription_jobs.pybackends/advanced/tests/test_audio_persistence_mongodb.pytests/.env.testtests/endpoints/audio_upload_tests.robottests/infrastructure/infra_tests.robottests/integration/mongodb_audio_storage_tests.robottests/libs/mongodb_helper.pytests/resources/mongodb_keywords.robottests/test-requirements.txt
💤 Files with no reviewable changes (3)
- backends/advanced/src/advanced_omi_backend/utils/audio_utils.py
- backends/advanced/src/advanced_omi_backend/utils/conversation_utils.py
- backends/advanced/src/advanced_omi_backend/models/audio_file.py
🧰 Additional context used
🧬 Code graph analysis (17)
backends/advanced/src/advanced_omi_backend/models/job.py (1)
backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (1)
AudioChunkDocument(16-158)
backends/advanced/src/advanced_omi_backend/app_factory.py (1)
backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (1)
AudioChunkDocument(16-158)
backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (2)
backends/advanced/src/advanced_omi_backend/config.py (1)
get_cleanup_settings(217-228)backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (2)
get_cleanup_settings_controller(130-142)save_cleanup_settings_controller(145-191)
backends/advanced/src/advanced_omi_backend/controllers/websocket_controller.py (2)
backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (1)
convert_audio_to_chunks(398-527)backends/advanced/src/advanced_omi_backend/controllers/queue_controller.py (1)
start_post_conversation_jobs(366-515)
backends/advanced/src/advanced_omi_backend/workers/audio_jobs.py (2)
backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (2)
AudioChunkDocument(16-158)compression_ratio(140-144)backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (1)
encode_pcm_to_opus(26-114)
backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (2)
backends/advanced/src/advanced_omi_backend/models/conversation.py (1)
Settings(323-330)backends/advanced/src/advanced_omi_backend/models/user.py (1)
Settings(66-68)
backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (2)
backends/advanced/src/advanced_omi_backend/config.py (3)
get_cleanup_settings(217-228)CleanupSettings(141-144)save_cleanup_settings_to_file(189-214)backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (1)
get_cleanup_settings(61-65)
backends/advanced/src/advanced_omi_backend/workers/transcription_jobs.py (2)
backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (1)
reconstruct_wav_from_conversation(331-395)backends/advanced/src/advanced_omi_backend/services/transcription/base.py (3)
name(64-66)transcribe(45-60)transcribe(125-133)
backends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.py (4)
backends/advanced/src/advanced_omi_backend/models/conversation.py (1)
Conversation(17-330)backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (1)
AudioChunkDocument(16-158)backends/advanced/src/advanced_omi_backend/config.py (1)
load_cleanup_settings_from_file(157-186)backends/advanced/src/advanced_omi_backend/controllers/queue_controller.py (1)
get_queue(51-59)
backends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.py (1)
backends/advanced/src/advanced_omi_backend/controllers/conversation_controller.py (2)
delete_conversation(249-297)restore_conversation(300-370)
backends/advanced/src/advanced_omi_backend/routers/modules/admin_routes.py (5)
backends/advanced/src/advanced_omi_backend/config.py (2)
get_cleanup_settings(217-228)load_cleanup_settings_from_file(157-186)backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (1)
get_cleanup_settings(61-65)backends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.py (1)
purge_old_deleted_conversations(20-94)backends/advanced/src/advanced_omi_backend/controllers/queue_controller.py (1)
get_queue(51-59)backends/advanced/src/advanced_omi_backend/models/conversation.py (1)
Conversation(17-330)
backends/advanced/src/advanced_omi_backend/config.py (1)
backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (1)
get_cleanup_settings(61-65)
backends/advanced/tests/test_audio_persistence_mongodb.py (3)
backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (1)
AudioChunkDocument(16-158)backends/advanced/src/advanced_omi_backend/models/conversation.py (1)
Conversation(17-330)backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (7)
encode_pcm_to_opus(26-114)decode_opus_to_pcm(117-195)build_wav_from_pcm(198-243)retrieve_audio_chunks(246-288)concatenate_chunks_to_pcm(291-328)reconstruct_wav_from_conversation(331-395)wait_for_audio_chunks(678-739)
backends/advanced/src/advanced_omi_backend/utils/gdrive_audio_utils.py (1)
backends/advanced/src/advanced_omi_backend/models/conversation.py (1)
Conversation(17-330)
backends/advanced/src/advanced_omi_backend/controllers/audio_controller.py (3)
backends/advanced/src/advanced_omi_backend/utils/audio_utils.py (1)
validate_and_prepare_audio(33-99)backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (1)
convert_audio_to_chunks(398-527)backends/advanced/src/advanced_omi_backend/controllers/queue_controller.py (1)
start_post_conversation_jobs(366-515)
backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (2)
backends/advanced/src/advanced_omi_backend/models/audio_chunk.py (2)
AudioChunkDocument(16-158)compression_ratio(140-144)backends/advanced/src/advanced_omi_backend/models/conversation.py (1)
Conversation(17-330)
backends/advanced/src/advanced_omi_backend/workers/conversation_jobs.py (2)
backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py (1)
wait_for_audio_chunks(678-739)backends/advanced/src/advanced_omi_backend/utils/conversation_utils.py (1)
mark_conversation_deleted(518-540)
🪛 Ruff (0.14.10)
backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py
62-62: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
72-72: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
backends/advanced/src/advanced_omi_backend/workers/speaker_jobs.py
244-244: f-string without any placeholders
Remove extraneous f prefix
(F541)
258-258: Do not catch blind exception: Exception
(BLE001)
263-263: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
backends/advanced/src/advanced_omi_backend/workers/audio_jobs.py
214-214: f-string without any placeholders
Remove extraneous f prefix
(F541)
228-228: Loop control variable stream_name not used within loop body
Rename unused stream_name to _stream_name
(B007)
248-248: Do not catch blind exception: Exception
(BLE001)
353-353: f-string without any placeholders
Remove extraneous f prefix
(F541)
359-359: Do not catch blind exception: Exception
(BLE001)
backends/advanced/src/advanced_omi_backend/models/audio_chunk.py
125-137: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
backends/advanced/src/advanced_omi_backend/controllers/system_controller.py
130-130: Unused function argument: user
(ARG001)
168-168: Prefer TypeError exception for invalid type
(TRY004)
168-168: Avoid specifying long messages outside the exception class
(TRY003)
171-171: Prefer TypeError exception for invalid type
(TRY004)
171-171: Avoid specifying long messages outside the exception class
(TRY003)
174-174: Avoid specifying long messages outside the exception class
(TRY003)
tests/libs/mongodb_helper.py
102-104: Avoid specifying long messages outside the exception class
(TRY003)
backends/advanced/src/advanced_omi_backend/workers/transcription_jobs.py
229-229: Do not catch blind exception: Exception
(BLE001)
234-234: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
234-234: Avoid specifying long messages outside the exception class
(TRY003)
237-237: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
237-237: Avoid specifying long messages outside the exception class
(TRY003)
backends/advanced/src/advanced_omi_backend/workers/cleanup_jobs.py
45-45: Avoid equality comparisons to True; use Conversation.deleted: for truth checks
Replace with Conversation.deleted
(E712)
133-133: Consider moving this statement to an else block
(TRY300)
135-135: Do not catch blind exception: Exception
(BLE001)
136-136: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
backends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.py
96-96: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
105-105: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
backends/advanced/src/advanced_omi_backend/routers/modules/admin_routes.py
21-21: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
33-33: Unused function argument: admin
(ARG001)
33-33: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
49-49: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
78-78: Do not catch blind exception: Exception
(BLE001)
79-79: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
82-82: Use explicit conversion flag
Replace with conversion flag
(RUF010)
89-89: Unused function argument: admin
(ARG001)
89-89: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
106-106: Avoid equality comparisons to True; use Conversation.deleted: for truth checks
Replace with Conversation.deleted
(E712)
117-117: Do not catch blind exception: Exception
(BLE001)
118-118: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
121-121: Use explicit conversion flag
Replace with conversion flag
(RUF010)
backends/advanced/src/advanced_omi_backend/controllers/conversation_controller.py
188-188: Undefined name datetime
(F821)
196-196: Avoid equality comparisons to False; use not AudioChunkDocument.deleted: for false checks
Replace with not AudioChunkDocument.deleted
(E712)
200-200: Undefined name datetime
(F821)
292-292: Do not catch blind exception: Exception
(BLE001)
293-293: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
296-296: Use explicit conversion flag
Replace with conversion flag
(RUF010)
341-341: Avoid equality comparisons to True; use AudioChunkDocument.deleted: for truth checks
Replace with AudioChunkDocument.deleted
(E712)
365-365: Do not catch blind exception: Exception
(BLE001)
366-366: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
369-369: Use explicit conversion flag
Replace with conversion flag
(RUF010)
backends/advanced/src/advanced_omi_backend/config.py
180-180: Do not catch blind exception: Exception
(BLE001)
181-181: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
213-213: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py
101-101: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
102-102: Do not catch blind exception: Exception
(BLE001)
104-107: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
106-106: Use explicit conversion flag
Replace with conversion flag
(RUF010)
126-126: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
239-239: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
backends/advanced/tests/test_audio_persistence_mongodb.py
81-81: Unused function argument: init_db
(ARG001)
119-119: Unused method argument: clean_db
(ARG002)
136-136: Unused method argument: clean_db
(ARG002)
159-159: Unused method argument: clean_db
(ARG002)
188-188: Unused method argument: clean_db
(ARG002)
220-220: Unused method argument: clean_db
(ARG002)
258-258: Unused method argument: clean_db
(ARG002)
287-287: Unused method argument: clean_db
(ARG002)
320-320: Unused method argument: clean_db
(ARG002)
330-330: Unused method argument: clean_db
(ARG002)
365-365: Unused method argument: clean_db
(ARG002)
397-397: Unused method argument: clean_db
(ARG002)
420-420: Unused method argument: clean_db
(ARG002)
backends/advanced/src/advanced_omi_backend/controllers/audio_controller.py
95-95: Local variable timestamp is assigned to but never used
Remove assignment to unused variable timestamp
(F841)
backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py
93-93: Unpacked variable stdout is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
98-98: Avoid specifying long messages outside the exception class
(TRY003)
175-175: Unpacked variable stdout is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
180-180: Avoid specifying long messages outside the exception class
(TRY003)
371-373: Avoid specifying long messages outside the exception class
(TRY003)
515-515: f-string without any placeholders
Remove extraneous f prefix
(F541)
564-564: Avoid specifying long messages outside the exception class
(TRY003)
663-663: f-string without any placeholders
Remove extraneous f prefix
(F541)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: robot-tests
backends/advanced/src/advanced_omi_backend/controllers/conversation_controller.py
Show resolved
Hide resolved
| # Calculate byte offsets within concatenated buffer | ||
| start_offset_in_chunk = start_time - (start_chunk * CHUNK_DURATION) | ||
| end_offset_in_chunk = end_time - (end_chunk * CHUNK_DURATION) | ||
|
|
||
| start_byte = int(start_offset_in_chunk * bytes_per_second) | ||
| # Calculate end byte from the end of buffer | ||
| bytes_from_end = int((CHUNK_DURATION - end_offset_in_chunk) * bytes_per_second) | ||
| end_byte = len(pcm_buffer) - bytes_from_end | ||
|
|
||
| # Trim PCM data | ||
| trimmed_pcm = pcm_buffer[start_byte:end_byte] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the full function around these lines to understand the context
cd backends/advanced/src/advanced_omi_backend/routers/modules && wc -l audio_routes.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 91
🏁 Script executed:
# Get the function containing these lines - read a larger section
sed -n '280,360p' backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 3079
🏁 Script executed:
# Search for the function definition to understand what precedes this code
rg -n "def.*get_audio|def.*chunk" backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py -A 2Repository: SimpleOpenSoftware/chronicle
Length of output: 374
🏁 Script executed:
# Check for CHUNK_DURATION definition
rg -n "CHUNK_DURATION" backends/advanced/src/advanced_omi_backend/ -B 2 -A 2Repository: SimpleOpenSoftware/chronicle
Length of output: 4096
🏁 Script executed:
# Search for test files related to audio
find . -path "*/test*" -name "*audio*" -type fRepository: SimpleOpenSoftware/chronicle
Length of output: 364
🏁 Script executed:
# Let's examine the test file for audio persistence
cat backends/advanced/tests/test_audio_persistence_mongodb.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 14151
🏁 Script executed:
# Search for any edge case tests related to time boundaries
rg -n "end_time\|boundary\|edge" backends/advanced/tests/test_audio_persistence_mongodb.py -i -A 2Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Look for test functions that test the get_audio_chunk_range function
rg -n "test.*chunk.*range\|test.*audio.*time\|get_audio_chunk_range" backends/advanced/tests/ -iRepository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Check if there are any tests covering the specific scenario
rg -n "10\.0\|CHUNK_DURATION" backends/advanced/tests/ -A 3 -B 1Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Search for tests specifically for get_audio_chunk_range in robot test files
rg -n "get_audio_chunk_range\|boundary\|edge.*case" tests/ -i -A 3Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Check the concatenate_chunks_to_pcm function to understand buffer construction
rg -n "def concatenate_chunks_to_pcm" backends/advanced/src/ -A 20Repository: SimpleOpenSoftware/chronicle
Length of output: 2210
🏁 Script executed:
# Look for any existing edge case handling or validation in the audio routes
rg -n "end_time.*=.*CHUNK_DURATION\|boundary\|edge" backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py -i -B 2 -A 2Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Get more of the concatenate_chunks_to_pcm function
sed -n '291,330p' backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 1081
🏁 Script executed:
# Let's look at the full context of the get_audio_chunk_range function
sed -n '234,340p' backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 3959
🏁 Script executed:
# Check if there's any validation or clamping of end_time
rg -n "end_time\|num_chunks" backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py | head -20Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Let me trace through the mathematical logic with concrete values
python3 << 'EOF'
# Test case 1: end_time exactly on chunk boundary
CHUNK_DURATION = 10.0
end_time = 10.0
end_chunk = int(end_time / CHUNK_DURATION)
end_offset_in_chunk = end_time - (end_chunk * CHUNK_DURATION)
print("Case 1: end_time = 10.0 (on boundary)")
print(f" end_chunk = int({end_time}/{CHUNK_DURATION}) = {end_chunk}")
print(f" end_offset_in_chunk = {end_time} - ({end_chunk} * {CHUNK_DURATION}) = {end_offset_in_chunk}")
# Test case 2: end_time just before chunk boundary
end_time = 9.99
end_chunk = int(end_time / CHUNK_DURATION)
end_offset_in_chunk = end_time - (end_chunk * CHUNK_DURATION)
print("\nCase 2: end_time = 9.99 (just before boundary)")
print(f" end_chunk = int({end_time}/{CHUNK_DURATION}) = {end_chunk}")
print(f" end_offset_in_chunk = {end_time} - ({end_chunk} * {CHUNK_DURATION}) = {end_offset_in_chunk}")
# Test case 3: start_time=5.0, end_time=10.0
print("\nCase 3: start_time=5.0, end_time=10.0")
start_time = 5.0
end_time = 10.0
start_chunk = int(start_time / CHUNK_DURATION)
end_chunk = int(end_time / CHUNK_DURATION)
num_chunks = end_chunk - start_chunk + 1
print(f" start_chunk = {start_chunk}, end_chunk = {end_chunk}")
print(f" num_chunks = {end_chunk} - {start_chunk} + 1 = {num_chunks}")
print(f" This retrieves chunks [{start_chunk}, {end_chunk}]")
# Test case 4: Boundary analysis
print("\nBoundary analysis for different end_time values:")
for end_time in [10.0, 10.1, 19.9, 20.0, 20.1]:
end_chunk = int(end_time / CHUNK_DURATION)
end_offset_in_chunk = end_time - (end_chunk * CHUNK_DURATION)
bytes_per_second = 32000
bytes_from_end = int((CHUNK_DURATION - end_offset_in_chunk) * bytes_per_second)
print(f" end_time={end_time}: end_chunk={end_chunk}, end_offset={end_offset_in_chunk:.1f}, bytes_from_end={bytes_from_end}")
EOFRepository: SimpleOpenSoftware/chronicle
Length of output: 158
🏁 Script executed:
# Check if there's any validation or boundary condition handling
rg -n "int.*end_time\|end_chunk\|num_chunks" backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py -B 2 -A 2Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Let me do a simpler analysis of the mathematical logic
rg -n "end_chunk\|start_chunk\|num_chunks" backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Check what happens when requesting chunks
rg -n "retrieve_audio_chunks" backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py -B 5 -A 5Repository: SimpleOpenSoftware/chronicle
Length of output: 1338
🏁 Script executed:
# Let me verify the edge case logic by examining what happens with partial last chunks
# Search for how last chunks are handled
rg -n "audio_total_duration\|last.*chunk\|partial" backends/advanced/src/advanced_omi_backend/ -i -B 2 -A 2Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Let's check if there's any clipping or rounding of end_time to actual available duration
sed -n '280,295p' backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 797
🏁 Script executed:
# Look for how chunks are created and if they always have CHUNK_DURATION seconds
rg -n "audio_total_duration\|end_time.*=\|duration.*=" backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py | head -20Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Check how chunks are stored - do they always represent exactly CHUNK_DURATION?
rg -n "duration.*=\|end_time.*=" backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py -B 2 -A 2 | head -40Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Look at convert_wav_to_chunks to see how the last chunk is handled
rg -n "def convert_wav_to_chunks" backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py -A 40Repository: SimpleOpenSoftware/chronicle
Length of output: 1625
🏁 Script executed:
# Look at the actual chunk creation to see if chunks can have partial duration
sed -n '530,620p' backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 3008
🏁 Script executed:
# Check for any comments about last chunk handling
rg -n "last.*chunk\|partial\|remainder" backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.py -i -B 2 -A 2Repository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Get the rest of the chunk creation to see the stored duration
sed -n '600,650p' backends/advanced/src/advanced_omi_backend/utils/audio_chunk_utils.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 1610
🏁 Script executed:
# Search for where chunks store their actual duration
rg -n "AudioChunkDocument\|duration\|end_time" backends/advanced/src/advanced_omi_backend/models/audio_chunk.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 54
🏁 Script executed:
# Let me verify by checking the audio model to confirm chunks store actual duration
cat backends/advanced/src/advanced_omi_backend/models/audio_chunk.pyRepository: SimpleOpenSoftware/chronicle
Length of output: 4806
🏁 Script executed:
# Let me verify the exact problematic scenario with a concrete test
python3 << 'EOF'
# Critical edge case: When requesting end_time on a partial chunk boundary
# Scenario: Audio is 25 seconds total (chunks: 0-10, 10-20, 20-25)
# Request: start_time=10.0, end_time=20.0 (exactly on chunk 2 boundary, which is partial)
CHUNK_DURATION = 10.0
bytes_per_second = 32000
# Request parameters
start_time = 10.0
end_time = 20.0
# Calculate chunk indices
start_chunk = int(start_time / CHUNK_DURATION) # 1
end_chunk = int(end_time / CHUNK_DURATION) # 2
num_chunks = end_chunk - start_chunk + 1 # 2
print(f"Request: start_time={start_time}, end_time={end_time}")
print(f"Chunks retrieved: {list(range(start_chunk, end_chunk + 1))}")
print(f"Num chunks to retrieve: {num_chunks}")
# Buffer construction (chunks 1 and 2, where 2 is partial at 5 seconds)
chunk1_bytes = 10 * bytes_per_second # 320000
chunk2_bytes = 5 * bytes_per_second # 160000 (partial)
pcm_buffer_len = chunk1_bytes + chunk2_bytes # 480000
print(f"\nBuffer construction:")
print(f" Chunk 1: {chunk1_bytes} bytes (10s)")
print(f" Chunk 2: {chunk2_bytes} bytes (5s partial)")
print(f" Total buffer: {pcm_buffer_len} bytes")
# Trim calculation
start_offset_in_chunk = start_time - (start_chunk * CHUNK_DURATION) # 0
end_offset_in_chunk = end_time - (end_chunk * CHUNK_DURATION) # 0
start_byte = int(start_offset_in_chunk * bytes_per_second)
bytes_from_end = int((CHUNK_DURATION - end_offset_in_chunk) * bytes_per_second)
end_byte = pcm_buffer_len - bytes_from_end
print(f"\nTrim calculation:")
print(f" start_offset_in_chunk: {start_offset_in_chunk}")
print(f" end_offset_in_chunk: {end_offset_in_chunk}")
print(f" start_byte: {start_byte}")
print(f" bytes_from_end: {bytes_from_end}")
print(f" end_byte: {end_byte}")
print(f" Result slice: [{start_byte}:{end_byte}]")
if end_byte < start_byte:
print(f"\n⚠️ NEGATIVE SLICE! end_byte ({end_byte}) < start_byte ({start_byte})")
elif end_byte > pcm_buffer_len:
print(f"\n⚠️ OVERFLOW! end_byte ({end_byte}) > buffer length ({pcm_buffer_len})")
else:
print(f"\n✓ Slice is valid")
print("\n" + "="*60)
print("CRITICAL CASE: end_time exactly on partial chunk boundary")
print("="*60)
# More dangerous scenario
start_time = 15.0
end_time = 20.0
start_chunk = int(start_time / CHUNK_DURATION)
end_chunk = int(end_time / CHUNK_DURATION)
print(f"\nRequest: start_time={start_time}, end_time={end_time}")
print(f"Chunks: {start_chunk} to {end_chunk}")
start_offset = start_time - (start_chunk * CHUNK_DURATION)
end_offset = end_time - (end_chunk * CHUNK_DURATION)
print(f"Offsets: start={start_offset}, end={end_offset}")
bytes_from_end_calc = int((CHUNK_DURATION - end_offset) * bytes_per_second)
print(f"bytes_from_end = int(({CHUNK_DURATION} - {end_offset}) * {bytes_per_second}) = {bytes_from_end_calc}")
# If we only got chunk 2 (5 seconds = 160000 bytes)
if end_chunk == 2: # Partial chunk
buffer_len = 5 * bytes_per_second
end_byte_calc = buffer_len - bytes_from_end_calc
print(f"\nIf buffer has only chunk 2 (partial, {buffer_len} bytes):")
print(f" end_byte = {buffer_len} - {bytes_from_end_calc} = {end_byte_calc}")
if end_byte_calc < 0:
print(f" ⚠️ NEGATIVE END_BYTE! This will cause incorrect slicing!")
EOFRepository: SimpleOpenSoftware/chronicle
Length of output: 929
Add bounds checking for the end_byte calculation to handle partial final chunks.
When end_time falls exactly on a chunk boundary and the last retrieved chunk is partial (shorter than CHUNK_DURATION), the calculation of bytes_from_end will overflow, resulting in a negative end_byte. For example, requesting audio from a 25-second conversation with end_time=20.0 causes bytes_from_end = int((10.0 - 0.0) * 32000) = 320000, but the buffer only contains 160000 bytes (chunk 2 is 5 seconds), yielding end_byte = -160000. This creates an incorrect reverse slice.
The fix should account for actual chunk durations rather than assuming all chunks are exactly CHUNK_DURATION seconds. Consider using the stored duration field from retrieved chunks or clamping end_byte to valid bounds: end_byte = max(start_byte, min(end_byte, len(pcm_buffer))).
🤖 Prompt for AI Agents
In @backends/advanced/src/advanced_omi_backend/routers/modules/audio_routes.py
around lines 321 - 331, The end_byte calculation can go negative for partial
final chunks because bytes_from_end assumes CHUNK_DURATION; update the logic
around start_offset_in_chunk/end_offset_in_chunk/bytes_from_end/end_byte to use
the actual retrieved chunk duration (e.g., use each chunk's stored duration
field) when converting to bytes, and then clamp the resulting end_byte to valid
bounds via end_byte = max(start_byte, min(end_byte, len(pcm_buffer))) to prevent
negative or out-of-range slices of pcm_buffer.
| # Keep sending audio in a loop to prevent inactivity timeout while waiting for conversation | ||
| # We need to continuously send audio because SPEECH_INACTIVITY_THRESHOLD_SECONDS=2 | ||
| FOR ${i} IN RANGE 20 # Send 20 batches while waiting | ||
| # Try to get conversation job | ||
| ${conv_jobs}= Get Jobs By Type And Client open_conversation ${client_id} | ||
| ${has_job}= Evaluate len($conv_jobs) > 0 | ||
|
|
||
| IF ${has_job} | ||
| # Conversation job exists, try to get conversation_id | ||
| TRY | ||
| ${conversation_id}= Get Conversation ID From Job Meta open_conversation ${client_id} | ||
| # Got conversation_id! Close websocket immediately to trigger disconnect | ||
| Log To Console Conversation created (${conversation_id}), closing websocket NOW | ||
| Close Audio Stream ${stream_id} | ||
| BREAK | ||
| EXCEPT | ||
| # conversation_id not set yet, keep sending audio | ||
| Send Audio Chunks To Stream ${stream_id} ${TEST_AUDIO_FILE} num_chunks=50 | ||
| Sleep 1s | ||
| END | ||
| ELSE | ||
| # No conversation job yet, keep sending audio | ||
| Send Audio Chunks To Stream ${stream_id} ${TEST_AUDIO_FILE} num_chunks=50 | ||
| Sleep 1s | ||
| END | ||
| END | ||
|
|
||
| # Simulate WebSocket disconnect (Bluetooth dropout) | ||
| Close Audio Stream ${stream_id} | ||
| # Verify we got the conversation_id before loop ended | ||
| Should Not Be Equal ${conversation_id} ${None} Failed to get conversation_id within timeout |
There was a problem hiding this comment.
Bug: ${conversation_id} may be undefined when the loop exhausts iterations.
If the loop completes all 20 iterations without successfully retrieving a conversation_id, line 310's assertion will fail with a "variable not found" error rather than the intended failure message. Initialize the variable before the loop.
🐛 Proposed fix
# Keep sending audio in a loop to prevent inactivity timeout while waiting for conversation
# We need to continuously send audio because SPEECH_INACTIVITY_THRESHOLD_SECONDS=2
+ ${conversation_id}= Set Variable ${None}
FOR ${i} IN RANGE 20 # Send 20 batches while waiting📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Keep sending audio in a loop to prevent inactivity timeout while waiting for conversation | |
| # We need to continuously send audio because SPEECH_INACTIVITY_THRESHOLD_SECONDS=2 | |
| FOR ${i} IN RANGE 20 # Send 20 batches while waiting | |
| # Try to get conversation job | |
| ${conv_jobs}= Get Jobs By Type And Client open_conversation ${client_id} | |
| ${has_job}= Evaluate len($conv_jobs) > 0 | |
| IF ${has_job} | |
| # Conversation job exists, try to get conversation_id | |
| TRY | |
| ${conversation_id}= Get Conversation ID From Job Meta open_conversation ${client_id} | |
| # Got conversation_id! Close websocket immediately to trigger disconnect | |
| Log To Console Conversation created (${conversation_id}), closing websocket NOW | |
| Close Audio Stream ${stream_id} | |
| BREAK | |
| EXCEPT | |
| # conversation_id not set yet, keep sending audio | |
| Send Audio Chunks To Stream ${stream_id} ${TEST_AUDIO_FILE} num_chunks=50 | |
| Sleep 1s | |
| END | |
| ELSE | |
| # No conversation job yet, keep sending audio | |
| Send Audio Chunks To Stream ${stream_id} ${TEST_AUDIO_FILE} num_chunks=50 | |
| Sleep 1s | |
| END | |
| END | |
| # Simulate WebSocket disconnect (Bluetooth dropout) | |
| Close Audio Stream ${stream_id} | |
| # Verify we got the conversation_id before loop ended | |
| Should Not Be Equal ${conversation_id} ${None} Failed to get conversation_id within timeout | |
| # Keep sending audio in a loop to prevent inactivity timeout while waiting for conversation | |
| # We need to continuously send audio because SPEECH_INACTIVITY_THRESHOLD_SECONDS=2 | |
| ${conversation_id}= Set Variable ${None} | |
| FOR ${i} IN RANGE 20 # Send 20 batches while waiting | |
| # Try to get conversation job | |
| ${conv_jobs}= Get Jobs By Type And Client open_conversation ${client_id} | |
| ${has_job}= Evaluate len($conv_jobs) > 0 | |
| IF ${has_job} | |
| # Conversation job exists, try to get conversation_id | |
| TRY | |
| ${conversation_id}= Get Conversation ID From Job Meta open_conversation ${client_id} | |
| # Got conversation_id! Close websocket immediately to trigger disconnect | |
| Log To Console Conversation created (${conversation_id}), closing websocket NOW | |
| Close Audio Stream ${stream_id} | |
| BREAK | |
| EXCEPT | |
| # conversation_id not set yet, keep sending audio | |
| Send Audio Chunks To Stream ${stream_id} ${TEST_AUDIO_FILE} num_chunks=50 | |
| Sleep 1s | |
| END | |
| ELSE | |
| # No conversation job yet, keep sending audio | |
| Send Audio Chunks To Stream ${stream_id} ${TEST_AUDIO_FILE} num_chunks=50 | |
| Sleep 1s | |
| END | |
| END | |
| # Verify we got the conversation_id before loop ended | |
| Should Not Be Equal ${conversation_id} ${None} Failed to get conversation_id within timeout |
🤖 Prompt for AI Agents
In @tests/infrastructure/infra_tests.robot around lines 282 - 310, The variable
${conversation_id} may be undefined if the FOR loop finishes without finding a
job; before the loop starts initialize ${conversation_id} to a known value
(e.g., Set Variable ${None}) so the final assertion (Should Not Be Equal
${conversation_id} ${None}) fails with the intended message; keep the rest of
the logic (Get Conversation ID From Job Meta, Close Audio Stream, Send Audio
Chunks To Stream) unchanged.
|
| Metric | Count |
|---|---|
| ✅ Passed | 103 |
| ❌ Failed | 12 |
| 📊 Total | 115 |
📊 View Reports
GitHub Pages (Live Reports):
Download Artifacts:
- robot-test-reports-html - HTML reports
- robot-test-results-xml - XML output
…andling - Removed audio file path parameters from various functions, transitioning to audio data retrieval from MongoDB chunks. - Updated the `start_post_conversation_jobs` function to reflect changes in audio handling, ensuring jobs reconstruct audio from database chunks. - Enhanced the `transcribe_full_audio_job` and `recognise_speakers_job` to process audio directly from memory, eliminating the need for temporary files. - Improved error handling and logging for audio data retrieval, ensuring better feedback during processing. - Added a new utility function for converting PCM data to WAV format in memory, streamlining audio format handling.
- Updated methods to accept audio data as bytes instead of file paths, enhancing performance by eliminating disk I/O. - Improved logging to reflect in-memory audio processing, providing better insights during speaker identification and diarization. - Streamlined audio data handling in the `diarize_identify_match` and `diarize_and_identify` methods, ensuring consistency across the client. - Removed temporary file handling, simplifying the audio processing workflow and reducing potential file system errors.
|
| Metric | Count |
|---|---|
| ✅ Passed | 111 |
| ❌ Failed | 4 |
| 📊 Total | 115 |
📊 View Reports
GitHub Pages (Live Reports):
Download Artifacts:
- robot-test-reports-html - HTML reports
- robot-test-results-xml - XML output
… management
Summary by CodeRabbit
Release Notes
New Features
Refactor
Tests
✏️ Tip: You can customize this high-level summary in your review settings.