Skip to content

Conversation

@rudransh-shrivastava
Copy link
Collaborator

@rudransh-shrivastava rudransh-shrivastava commented Dec 17, 2025

Proposed change

Resolves #2924

  • Create the backend command to generate community snapshot videos.
  • Create a new docker image to run this command.
  • Add ElevenLabs communication class.

Checklist

  • Required: I read and followed the contributing guidelines
  • Required: I ran make check-test locally and all tests passed
  • I used AI for code, documentation, or tests in this PR
    • for html template design and tests.

@github-actions github-actions bot added backend docker Pull requests that update Docker code makefile labels Dec 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

Summary by CodeRabbit

  • New Features

    • End-to-end OWASP community snapshot video generation with multi-slide templates (intro, sponsors, projects, chapters, releases, thank-you), AI‑generated narration, and final assembled video.
    • New Make/CLI target to build and run the video generator and produce a retrievable artifact.
  • Chores

    • Added optional "video" dependency group, example env var for AI voice API key, new container image for video runtime, and ignore rule for generated videos.
  • Tests

    • Comprehensive unit tests covering the video pipeline and the new command.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds a full OWASP community snapshot video pipeline: new Slide/SlideBuilder/VideoGenerator, ElevenLabs TTS wrapper, HTML slide templates, a Django management command and Makefile target, new Dockerfile for video, poetry "video" group, tests, cspell entries, and .gitignore update.

Changes

Cohort / File(s) Summary
Core video logic
backend/apps/owasp/video.py, backend/apps/common/eleven_labs.py
New Slide, SlideBuilder, VideoGenerator classes and an ElevenLabs synchronous wrapper. Implements HTML→PDF→image rendering, TTS audio generation, per-slide video creation, merging, and cleanup. Verify ffmpeg/WeasyPrint/ElevenLabs interactions, file paths, and error handling.
CLI / orchestration
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py, backend/apps/owasp/Makefile
New Django management command and Makefile target owasp-generate-community-snapshot-video that runs a Docker image to generate videos. Check argument parsing, Snapshot filtering, output/temp directory handling, and Docker run/mount options.
Templates (slides & base)
backend/apps/owasp/templates/video/base.html, backend/apps/owasp/templates/video/slides/intro.html, backend/apps/owasp/templates/video/slides/sponsors.html, backend/apps/owasp/templates/video/slides/projects.html, backend/apps/owasp/templates/video/slides/chapters.html, backend/apps/owasp/templates/video/slides/releases.html, backend/apps/owasp/templates/video/slides/thank_you.html
New 1920×1080 base template and slide templates. Check template variables, layout classes, asset paths, and content blocks used by Slide rendering.
Dockerfiles & images
backend/docker/Dockerfile.video, backend/docker/Dockerfile, backend/docker/Dockerfile.local
New multi-stage Dockerfile.video (WeasyPrint deps, fonts, non-root user). Dockerfile and Dockerfile.local updated to add --without video to Poetry install. Review system packages and build/cache mounts.
Dependencies & env
backend/pyproject.toml, backend/.env.example
Adds tool.poetry.group.video.dependencies (elevenlabs, ffmpeg-python, pillow, pypdfium2, weasyprint) and example env DJANGO_ELEVENLABS_API_KEY. Verify versions and environment usage.
Tests
backend/tests/apps/common/eleven_labs_test.py, backend/tests/apps/owasp/video_test.py, backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
New unit tests for ElevenLabs wrapper, Slide/SlideBuilder/VideoGenerator, and management command. Extensive mocking of external deps — review for realistic assertions and brittle mocks.
Tooling / ignores / dict
cspell/custom-dict.txt, .gitignore
Added cspell entries (elevenlabs, pdfium, mkv, etc.) and ignored backend/generated_videos/. Confirm spellings and ignore placement.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • arkid15r
  • kasya

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add Community Snapshot Videos Command' clearly summarizes the main change: introducing a new backend command for generating community snapshot videos.
Description check ✅ Passed The PR description is directly related to the changeset, detailing the three main components added: the backend command, Docker image, and ElevenLabs class for video generation.
Linked Issues check ✅ Passed The PR fulfills issue #2924 requirements: implements backend command, creates HTML templates for slide rendering, integrates audio generation via ElevenLabs, and orchestrates image+audio video generation.
Out of Scope Changes check ✅ Passed All changes directly support the video generation workflow: dependency additions (ffmpeg-python, pillow, weasyprint, elevenlabs), Docker configuration for video generation, templates for slide rendering, and supporting infrastructure are in scope.
Docstring Coverage ✅ Passed Docstring coverage is 86.21% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81f9f98 and 69ab4fa.

📒 Files selected for processing (1)
  • backend/apps/owasp/Makefile
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/owasp/Makefile
⏰ 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). (7)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
backend/docker/Dockerfile.local (1)

47-53: Update the Alpine version reference in the comment.

Line 47 references Alpine 3.17, but the base image python:3.13.7-alpine likely uses a much newer Alpine version (3.20 or 3.21). While the WeasyPrint dependencies should still be correct, the comment may be misleading.

Apply this diff to make the comment version-agnostic:

-# WeasyPrint dependencies: https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#alpine-3-17
+# WeasyPrint dependencies: https://doc.courtbouillon.org/weasyprint/stable/first_steps.html
 RUN --mount=type=cache,target=${APK_CACHE_DIR} \

Alternatively, verify the actual Alpine version and update the comment accordingly.

backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)

40-40: Consider using logger instead of print statements.

While print() statements are acceptable in Django management commands (and explicitly allowed by ruff configuration line 99), using the module-level logger would provide better control over log levels and formatting for production use.

Example refactor:

-        print(f"Rendering slide image: {self.name}")
+        logger.info(f"Rendering slide image: {self.name}")

Apply similar changes to all print statements. However, if you prefer the current approach for command output, it's acceptable to keep the print statements.

Also applies to: 54-54, 58-58, 69-69, 92-92, 95-95


71-74: Reminder: Implement audio generation.

The TODO indicates audio generation is not yet implemented. The hardcoded test audio path will need to be replaced with actual audio generation logic (likely using OpenAI TTS API).

Do you want me to help generate a skeleton implementation for audio generation using OpenAI's text-to-speech API, or open a separate issue to track this task?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a34b8b6 and 6e47c58.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • backend/apps/owasp/Makefile (1 hunks)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1 hunks)
  • backend/apps/owasp/templates/video/base.html (1 hunks)
  • backend/apps/owasp/templates/video/slides/intro.html (1 hunks)
  • backend/docker/Dockerfile.local (1 hunks)
  • backend/pyproject.toml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)
backend/apps/common/open_ai.py (4)
  • OpenAi (13-105)
  • set_prompt (64-76)
  • set_input (36-48)
  • complete (78-105)
backend/apps/owasp/api/internal/queries/snapshot.py (1)
  • snapshot (14-22)
🪛 GitHub Actions: Run CI/CD
backend/docker/Dockerfile.local

[error] 51-51: Unknown word (libgobject) detected by spell checker.


[error] 51-51: Unknown word (libpango) detected by spell checker.


[error] 51-51: Unknown word (libharfbuzz) detected by spell checker.


[error] 52-52: Unknown word (libharfbuzz) detected by spell checker.


[error] 52-52: Unknown word (libpangoft) detected by spell checker.

backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py

[error] 10-10: Unknown word (pdfium) detected by spell checker.


[error] 44-44: Unknown word (pdfium) detected by spell checker.


[error] 47-47: Unknown word (pil) detected by spell checker.


[error] 50-50: Unknown word (pil) detected by spell checker.


[error] 73-73: Unknown word (rudransh) detected by spell checker.


[error] 73-73: Unknown word (shrivastava) detected by spell checker.


[error] 86-86: Unknown word (acodec) detected by spell checker.


[error] 87-87: Unknown word (vcodec) detected by spell checker.


[error] 87-87: Unknown word (libx) detected by spell checker.

⏰ 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: CodeQL (javascript-typescript)
🔇 Additional comments (5)
backend/apps/owasp/Makefile (1)

25-27: LGTM!

The new Makefile target follows the existing pattern and is correctly structured. The $(snapshot_key) variable will need to be provided by the caller, consistent with other targets like owasp-create-project-metadata-file.

backend/apps/owasp/templates/video/slides/intro.html (1)

1-7: LGTM!

The intro slide template correctly extends the base template and uses the context variables provided by SlideBuilder.create_intro_slide(). The structure is clean and appropriate for the video generation workflow.

backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)

98-119: LGTM!

The SlideBuilder class is well-structured and correctly creates slide instances with appropriate context and paths. The date formatting and file naming conventions are clear and consistent.


122-138: LGTM!

The Generator class provides a clean abstraction for managing slides and orchestrating the video generation pipeline. The sequential processing in generate_video() is appropriate for the PoC stage.


164-185: LGTM with a note on query pattern.

The command handler correctly validates input, queries for a completed snapshot, and orchestrates the video generation workflow. The use of filter().first() with case-insensitive matching is safer than get() for handling user input, as it gracefully returns None rather than raising DoesNotExist.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1)

57-74: Handle transcript generation failures more robustly.

The method catches exceptions but doesn't re-raise them, and it doesn't handle the case where open_ai.complete() returns None (as documented in the OpenAi class). If transcript generation fails, self.transcript might not be set, causing downstream failures when it's accessed.

Apply this diff to handle failures properly:

     def generate_transcript(self, open_ai: OpenAi):
         """Generate a transcript."""
         logger.info("Prompt: %s", self.input)
         prompt = """
         You are a scriptwriter for a tech presentation.
         Your task is to generate a script for a presentation slide.
         The script should be simple and direct.
         Do not add any extra words, introduction or conclusion.
         """
         open_ai.set_prompt(prompt)
         open_ai.set_input(self.input)
 
         try:
             self.transcript = open_ai.complete()
+            if self.transcript is None:
+                logger.error("Failed to generate transcript for %s", self.name)
+                raise RuntimeError(f"OpenAI API failed to generate transcript for {self.name}")
             logger.info("Generated slide transcript: %s", self.transcript)
         except Exception:
             logger.exception("Error generating transcript for %s:", self.name)
+            raise
🧹 Nitpick comments (4)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)

75-78: Placeholder audio generation needs implementation.

The hardcoded audio path will cause all slides to share the same audio file, leading to incorrect video assembly when multiple slides are processed.

Do you want me to generate a skeleton implementation that creates unique audio files per slide, or would you prefer to open an issue to track this TODO?


104-107: Add docstring for consistency.

The __init__ method is missing a docstring, while other methods have them.

Apply this diff to add the docstring:

     def __init__(self, snapshot, output_dir):
-        """Initialize SlideBuilder."""
+        """Initialize SlideBuilder.
+        
+        Args:
+            snapshot: The snapshot object containing data for video generation.
+            output_dir: Path to the directory where output files will be saved.
+        
+        """
         self.snapshot = snapshot
         self.output_dir = output_dir

128-135: Consider adding type hints for better code clarity.

The append_slide method and __init__ would benefit from parameter type hints matching the rest of the codebase.

Apply this diff to improve type annotations:

-    def __init__(self):
-        """Initialize Video Generator."""
+    def __init__(self) -> None:
+        """Initialize Video Generator.
+        
+        Creates an empty slide list and initializes the OpenAI client
+        for transcript generation.
+        
+        """
         self.slides: list[Slide] = []
         self.open_ai = OpenAi()
 
-    def append_slide(self, slide: Slide):
-        """Append a slide to list."""
+    def append_slide(self, slide: Slide) -> None:
+        """Append a slide to list.
+        
+        Args:
+            slide: The Slide instance to add to the generation queue.
+        
+        """
         self.slides.append(slide)
backend/docker/Dockerfile.local (1)

49-53: Alpine package availability confirmed; consider version pinning for reproducible builds.

All specified packages are available in Alpine Linux. The WeasyPrint documentation reference is current, and ffmpeg can be installed via apk add ffmpeg on Alpine Linux. The so: prefix notation for shared libraries is valid Alpine package syntax.

For more reproducible builds, consider pinning package versions:

  • ffmpeg=~6.1 (or a specific stable version)
  • ttf-freefont=20120212 (or current stable)

This prevents unintended package updates between builds.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e47c58 and 35ea129.

📒 Files selected for processing (4)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1 hunks)
  • backend/apps/owasp/templates/video/base.html (1 hunks)
  • backend/docker/Dockerfile.local (1 hunks)
  • cspell/custom-dict.txt (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/owasp/templates/video/base.html
🧰 Additional context used
🧬 Code graph analysis (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)
backend/apps/common/open_ai.py (4)
  • OpenAi (13-105)
  • set_prompt (64-76)
  • set_input (36-48)
  • complete (78-105)
backend/apps/owasp/api/internal/queries/snapshot.py (1)
  • snapshot (14-22)
⏰ 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: CodeQL (javascript-typescript)
🔇 Additional comments (8)
cspell/custom-dict.txt (1)

38-38: Alphabetical ordering and relevance confirmed.

All nine new dictionary entries are correctly positioned in alphabetical order and directly correspond to the video generation dependencies being introduced (FFmpeg codecs, WeasyPrint rendering libraries, Pillow, and pypdfium2). The additions are appropriate and necessary to prevent false spell-check warnings for these technical identifiers.

Also applies to: 81-85, 110-110, 114-114, 141-141

backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (7)

1-18: LGTM!

The imports are well-organized and all appear necessary for the video generation workflow.


80-100: LGTM!

The video generation logic correctly handles ffmpeg operations with proper error logging and exception propagation. The codec parameters are appropriate for standard video output.


109-124: LGTM!

The intro slide creation correctly extracts and formats snapshot data, building a properly configured Slide instance.


137-143: LGTM!

The sequential processing of slides (render → transcript → audio → video) is a clear and appropriate workflow for video generation.


169-176: LGTM!

Environment variable setup in the command handler is appropriate, and the output directory creation logic is sound.


178-184: Consider more explicit error handling for snapshot lookup.

Using filter().first() with a None check is valid but less explicit than using a try-except with Snapshot.DoesNotExist. The current pattern matches the code snippet from snapshot.py, so consistency is maintained.


186-193: LGTM!

The video generation workflow correctly initializes the builder and generator, creates the intro slide, and triggers the generation pipeline.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35ea129 and 6f4eba2.

📒 Files selected for processing (2)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1 hunks)
  • backend/apps/owasp/templates/video/base.html (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/owasp/templates/video/base.html
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧬 Code graph analysis (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)
backend/apps/common/open_ai.py (4)
  • OpenAi (13-105)
  • set_prompt (64-76)
  • set_input (36-48)
  • complete (78-105)
backend/apps/owasp/api/internal/queries/snapshot.py (1)
  • snapshot (14-22)
⏰ 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). (4)
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1)

76-81: Add explicit None check for API response.

The try-except block won't catch failures if open_ai.complete() returns None instead of raising an exception. According to the OpenAi class implementation, the complete() method returns None on error.

🔎 Add explicit None check:
         open_ai.set_prompt(prompt)
         open_ai.set_input(self.input)
 
-        try:
-            self.transcript = open_ai.complete()
-            print("Generated slide transcript:", self.transcript)
-        except Exception:
-            logger.exception("Error generating transcript for %s", self.name)
-            raise
+        self.transcript = open_ai.complete()
+        if self.transcript is None:
+            msg = f"Failed to generate transcript for {self.name}"
+            logger.error(msg)
+            raise RuntimeError(msg)
+        
+        print("Generated slide transcript:", self.transcript)
🧹 Nitpick comments (4)
backend/apps/common/eleven_labs.py (1)

43-55: Validate text parameter in set_text().

The method accepts any text value without validation. Empty or None text will cause API failures later. Consider adding validation to fail fast with a clear error message.

🔎 Add parameter validation:
     def set_text(self, text: str) -> ElevenLabs:
         """Set the text to convert to speech.
 
         Args:
             text (str): The text content.
 
         Returns:
             ElevenLabs: The current instance.
 
         """
+        if not text:
+            msg = "Text cannot be empty"
+            raise ValueError(msg)
+
         self.text = text
 
         return self
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)

42-42: Replace print() with appropriate output methods.

Based on learnings, Django management commands should use self.stdout.write() for user-facing output. However, since these helper classes don't have access to the command's stdout, consider:

  1. For the Command.handle() method (line 212): use self.stdout.write() instead of print()
  2. For helper classes (Slide, SlideBuilder, Generator): use logger.info() for structured logging

Based on learnings, Django management commands should prefer self.stdout.write() over print() for user-facing command output.

🔎 Example changes:

In the Command class:

-        print("Generating video for snapshot:", snapshot_key)
+        self.stdout.write(f"Generating video for snapshot: {snapshot_key}")

In helper classes like Slide:

-            print("Rendering slide image")
+            logger.info("Rendering slide image: %s", self.name)
-            print("Saved image to path:", self.image_output_path)
+            logger.info("Saved %s image to path: %s", self.name, self.image_output_path)

Also applies to: 54-54, 66-66, 78-78, 99-99, 120-120, 135-135, 212-212


122-124: Remove redundant logging before re-raising exception.

Logging immediately before re-raising an exception creates redundant logging. Let the caller handle logging the exception appropriately.

Based on learnings, ruff linting rules discourage logging immediately before raising exceptions.

🔎 Remove the logging call:
         except ffmpeg.Error:
-            logger.exception("Error generating video for %s:", self.name)
             raise

128-128: Add type hint for snapshot parameter.

The snapshot parameter is missing a type hint. Based on the import at line 18, it should be typed as Snapshot.

🔎 Add type hint:
-    def __init__(self, snapshot, output_dir: Path) -> None:
+    def __init__(self, snapshot: Snapshot, output_dir: Path) -> None:
         """Initialize SlideBuilder."""
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f4eba2 and f459244.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • backend/.env.example (1 hunks)
  • backend/apps/common/eleven_labs.py (1 hunks)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1 hunks)
  • backend/pyproject.toml (1 hunks)
  • backend/settings/base.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/pyproject.toml
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🪛 GitHub Actions: Run CI/CD
backend/settings/base.py

[error] 219-219: CSpell: Unknown word 'ELEVENLABS'.


[error] 219-219: CSpell: Unknown word 'ELEVENLABS'.

backend/apps/common/eleven_labs.py

[error] 9-9: CSpell: Unknown word 'elevenlabs'.


[error] 22-22: CSpell: Unknown word 'MSU'.


[error] 35-35: CSpell: Unknown word 'ELEVENLABS'.

⏰ 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: CodeQL (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)

53-53: Consider replacing print statements with structured logging.

Multiple print() statements throughout the Slide class should use structured logging for consistency. While the learning about self.stdout.write() applies to Command class methods, these helper classes don't have access to self.stdout.

Based on learnings, consider using logger.info() for progress messages, which provides better control over log levels and output destinations.

🔎 Examples of recommended changes:
-            print("Rendering slide image")
+            logger.info("Rendering slide image: %s", self.name)
-            print("Saved image to path:", self.image_output_path)
+            logger.info("Saved %s image to: %s", self.name, self.image_output_path)
-            print("Using pre-set transcript:", self.transcript)
+            logger.info("Using pre-set transcript for %s", self.name)
-        print("Generating transcript from prompt:", self.transcript_input)
+        logger.info("Generating transcript for %s", self.name)
-        print("Generated slide transcript:", self.transcript)
+        logger.info("Generated transcript for %s", self.name)
-            print("Generated audio at path:", self.audio_output_path)
+            logger.info("Generated audio for %s at: %s", self.name, self.audio_output_path)
-            print("Generated video for:", self.name)
+            logger.info("Generated video for: %s", self.name)

Also applies to: 65-65, 83-83, 89-89, 107-107, 125-125, 146-146


154-154: Add missing type hint for snapshot parameter.

The snapshot parameter in SlideBuilder.__init__ lacks a type hint. For consistency with the rest of the codebase and to improve IDE support, add the Snapshot type annotation.

🔎 Apply this diff:
-    def __init__(self, snapshot, output_dir: Path) -> None:
+    def __init__(self, snapshot: Snapshot, output_dir: Path) -> None:
         """Initialize SlideBuilder."""
         self.snapshot = snapshot
         self.output_dir = output_dir

161-161: Replace print statements in Command.handle with self.stdout.write.

Lines 161 and 238 use print() for user-facing output in contexts where the Command instance is available.

Based on learnings, Django management commands should use self.stdout.write() instead of print() for user-facing output, as it follows Django conventions and improves testability.

🔎 Apply these changes:

For line 161, pass the command's stdout writer to SlideBuilder and use it:

# In SlideBuilder.__init__, add a writer parameter
def __init__(self, snapshot: Snapshot, output_dir: Path, writer=None) -> None:
    """Initialize SlideBuilder."""
    self.snapshot = snapshot
    self.output_dir = output_dir
    self.writer = writer

# In create_intro_slide
def create_intro_slide(self) -> Slide:
    """Create an introduction slide."""
    if self.writer:
        self.writer.write("Generating introduction slide for snapshot\n")
    # ... rest of method

For line 238 in Command.handle:

-        print("Generating video for snapshot:", snapshot_key)
+        self.stdout.write(f"Generating video for snapshot: {snapshot_key}\n")

Then update the Command.handle call to SlideBuilder:

-        slide_builder = SlideBuilder(snapshot, output_dir)
+        slide_builder = SlideBuilder(snapshot, output_dir, self.stdout)

Also applies to: 238-238

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 435fdaf and 90169bb.

📒 Files selected for processing (1)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧬 Code graph analysis (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)
backend/apps/common/eleven_labs.py (3)
  • ElevenLabs (17-137)
  • set_text (43-55)
  • generate_to_file (120-137)
backend/apps/common/open_ai.py (4)
  • OpenAi (13-105)
  • set_prompt (64-76)
  • set_input (36-48)
  • complete (78-105)
backend/apps/owasp/api/internal/queries/snapshot.py (1)
  • snapshot (14-22)
⏰ 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). (4)
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
backend/apps/common/eleven_labs.py (1)

168-193: Validate text attribute before API call.

The generate() method accesses self.text without verifying it was set via set_text(). If set_text() was never called, this raises an AttributeError.

🔎 Add validation:
     def generate(self) -> bytes | None:
         """Generate audio from text.
 
         Returns:
             bytes | None: The audio bytes or None on error.
 
         """
+        if not hasattr(self, "text") or not self.text:
+            logger.error("Text not set. Call set_text() before generate().")
+            return None
+
         try:
             audio_iterator = self.client.text_to_speech.convert(
                 model_id=self.model_id,
                 output_format=self.output_format,
                 text=self.text,
                 voice_id=self.voice_id,
                 voice_settings={
                     "similarity_boost": self.similarity_boost,
                     "stability": self.stability,
                     "style": self.style,
                     "use_speaker_boost": self.use_speaker_boost,
                 },
             )
 
             return b"".join(audio_iterator)
         except Exception:
             logger.exception("An error occurred during ElevenLabs API request.")
 
         return None
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1)

53-53: Replace print() with self.stdout.write() for command output.

Multiple print() statements are used throughout the command. In Django management commands, use self.stdout.write() for user-facing output to follow Django conventions and improve testability.

Based on learnings, Django management commands should prefer self.stdout.write() for stdout output.

🔎 Example changes:

In the Slide methods, you'll need to pass the command instance or use a different approach since these are not methods of Command. Consider:

Option 1: Add logging instead of print for Slide methods:

-            print("Rendering slide image")
+            logger.info("Rendering slide image: %s", self.name)

Option 2: Pass stdout writer to Slide methods as a parameter.

For Command.handle():

-        print("Generating video for snapshot:", snapshot_key)
+        self.stdout.write(f"Generating video for snapshot: {snapshot_key}")

For SlideBuilder methods, similar approach needed (pass stdout or use logging).

Also applies to: 65-65, 83-83, 89-89, 107-107, 125-125, 146-146, 161-161, 179-179, 293-293

🧹 Nitpick comments (4)
backend/apps/common/eleven_labs.py (1)

43-46: Validate API key before initializing client.

The ElevenLabsClient is instantiated with settings.ELEVENLABS_API_KEY without checking if the key exists or is non-empty. If misconfigured, API calls will fail later with unclear errors.

🔎 Add validation:
+        api_key = settings.ELEVENLABS_API_KEY
+        if not api_key:
+            msg = "ELEVENLABS_API_KEY is not configured in settings"
+            raise ValueError(msg)
+
         self.client = ElevenLabsClient(
-            api_key=settings.ELEVENLABS_API_KEY,
+            api_key=api_key,
             timeout=30,
         )
backend/apps/owasp/templates/video/slides/projects.html (1)

8-38: Inconsistent project display between layouts.

The template switches layouts at 15 projects but has inconsistencies:

  • Dense layout (≤15): shows name, leaders, and created_at
  • Sparse layout (>15): shows only name, capped at 25 projects via slice:":25"

This means 16-24 projects are shown with less information, and project 26+ are silently omitted without indication to the viewer.

Consider:

  1. Adding a count indicator when projects are truncated: "Showing 25 of {{ project_count }} projects"
  2. Making the data shown consistent across both layouts
  3. Documenting the 15/25 thresholds or extracting them as template context variables
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)

238-250: Handle potential ffmpeg errors in video concatenation.

The _combine_videos() method calls ffmpeg operations without try-except blocks. If concatenation fails (e.g., due to incompatible formats or missing files), the exception will propagate uncaught.

🔎 Add error handling:
     def _combine_videos(self, output_path: Path) -> None:
         """Combine all slide videos into final video."""
+        try:
             inputs = [ffmpeg.input(str(slide.video_output_path)) for slide in self.slides]
     
             video_streams = [inp.video for inp in inputs]
             audio_streams = [inp.audio for inp in inputs]
     
             joined_video = ffmpeg.concat(*video_streams, v=1, a=0)
             joined_audio = ffmpeg.concat(*audio_streams, v=0, a=1)
     
             ffmpeg.output(joined_video, joined_audio, str(output_path)).overwrite_output().run(
                 capture_stdout=True, capture_stderr=True
             )
+        except ffmpeg.Error:
+            logger.exception("Error combining videos into final output")
+            raise

256-274: Add type hint for parser parameter.

The add_arguments method parameter parser lacks a type hint, reducing IDE support and type safety.

🔎 Add type hint:
+    from argparse import ArgumentParser
+
-    def add_arguments(self, parser) -> None:
+    def add_arguments(self, parser: ArgumentParser) -> None:
         """Add command-line arguments to the parser.
 
         Args:
-            parser (argparse.ArgumentParser): The argument parser instance.
+            parser: The argument parser instance.
 
         """
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90169bb and 3e87cd4.

📒 Files selected for processing (4)
  • backend/apps/common/eleven_labs.py (1 hunks)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1 hunks)
  • backend/apps/owasp/templates/video/base.html (1 hunks)
  • backend/apps/owasp/templates/video/slides/projects.html (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧬 Code graph analysis (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)
backend/apps/common/eleven_labs.py (3)
  • ElevenLabs (17-212)
  • set_text (126-138)
  • generate_to_file (195-212)
backend/apps/common/open_ai.py (4)
  • OpenAi (13-105)
  • set_prompt (64-76)
  • set_input (36-48)
  • complete (78-105)
backend/apps/owasp/api/internal/queries/snapshot.py (1)
  • snapshot (14-22)
⏰ 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). (4)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (1)
backend/apps/owasp/templates/video/base.html (1)

1-139: Base template structure looks good.

The fixed viewport (1920×1080), inline utility CSS, and Arial font choice are appropriate for WeasyPrint-based video slide rendering. The template provides a solid foundation for the video generation pipeline.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1)

201-208: Add guard against empty projects list.

Lines 204-207 format project names but don't handle the case when project_names is empty. If a snapshot has no new projects, accessing project_names[-1] will raise an IndexError.

In a previous review thread, you mentioned skipping the projects section when there are no new projects. However, the current code still calls create_projects_slide() unconditionally (line 308). You should either:

  1. Add a guard in Command.handle() to skip creating the projects slide when snapshot.new_projects.count() == 0, or
  2. Add validation here to handle the empty case gracefully.
🔎 Option 1: Skip projects slide in Command.handle() (recommended)
# In Command.handle() around line 308
generator.append_slide(slide_builder.create_intro_slide())

# Only add projects slide if there are new projects
if snapshot.new_projects.exists():
    generator.append_slide(slide_builder.create_projects_slide())
🔎 Option 2: Guard in create_projects_slide()
         project_names = [
             p.name.replace("OWASP ", "") for p in new_projects[:MAX_TRANSCRIPT_PROJECTS]
         ]
+        if not project_names:
+            raise ValueError("Cannot create projects slide: no projects available")
+        
         formatted_project_names = (
             f"{', '.join(project_names[:-1])}, and {project_names[-1]}"
             if len(project_names) > 1
             else project_names[0]
         )

Based on learnings, prefer Option 1 as it aligns with your stated intention to skip the section entirely when there are no projects.

🧹 Nitpick comments (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1)

108-115: Consider raising exception immediately when transcript generation fails.

When open_ai.complete() returns None, the method logs an error and returns early, leaving self.transcript as None. Later, generate_audio() will detect this and raise a RuntimeError. This deferred error handling makes debugging harder because the error message won't indicate that transcript generation failed.

For consistency with generate_audio() and clearer error reporting, consider raising immediately:

🔎 Proposed refactor for immediate error raising
         transcript = open_ai.complete()
         if not transcript:
             logger.error("Error generating transcript for %s", self.name)
-            return
+            raise RuntimeError(f"Failed to generate transcript for slide: {self.name}")
 
         self.transcript = transcript
         print("Generated slide transcript:", self.transcript)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e87cd4 and 3a54683.

📒 Files selected for processing (3)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1 hunks)
  • backend/apps/owasp/templates/video/base.html (1 hunks)
  • backend/apps/owasp/templates/video/slides/projects.html (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/owasp/templates/video/base.html
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧬 Code graph analysis (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (4)
backend/apps/common/eleven_labs.py (3)
  • ElevenLabs (17-212)
  • set_text (126-138)
  • generate_to_file (195-212)
backend/apps/common/open_ai.py (4)
  • OpenAi (13-105)
  • set_prompt (64-76)
  • set_input (36-48)
  • complete (78-105)
backend/apps/owasp/api/internal/queries/snapshot.py (1)
  • snapshot (14-22)
backend/apps/owasp/api/internal/nodes/snapshot.py (1)
  • new_projects (45-47)
⏰ 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). (4)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (6)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (6)

30-52: LGTM!

The dataclass is well-structured with proper type hints and validation logic. The __post_init__ method correctly enforces that exactly one of transcript or transcript_input must be provided, preventing invalid states.


53-81: LGTM!

The resource cleanup pattern is well-implemented. Initializing pdf and page to None before the try block and using guarded cleanup in the finally block ensures resources are properly released even if errors occur during rendering.


116-136: LGTM!

The error handling is robust. The method validates that a transcript exists before attempting audio generation and raises meaningful exceptions when operations fail, ensuring errors are caught early with clear context.


137-158: LGTM!

The FFmpeg integration properly handles errors by catching ffmpeg.Error, logging with context, and re-raising to ensure failures aren't silently swallowed. The video generation parameters (aac audio codec, libx264 video codec, yuv420p pixel format) are appropriate for broad compatibility.


226-260: LGTM!

The Generator orchestrates the video pipeline effectively. The sequence (render → transcript → audio → video → combine) is logical, and the FFmpeg concatenation properly handles both video and audio streams separately before merging them into the final output.


285-310: LGTM with one note.

The command structure follows Django conventions properly. The environment variable setup is correctly placed in handle() rather than at module level, and the snapshot fetching includes appropriate error handling.

Note: Line 308 unconditionally creates the projects slide. See the comment on lines 201-208 regarding handling empty projects lists.

@rudransh-shrivastava rudransh-shrivastava force-pushed the feature/community-snapshot-videos branch from 3a54683 to 9c15eda Compare December 27, 2025 11:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (5)
backend/apps/owasp/templates/video/slides/projects.html (1)

1-22: LGTM!

The template structure is clean and follows Django best practices. The previously flagged w-76 class is now properly defined in base.html (lines 154-156), resolving the earlier concern.

backend/apps/owasp/templates/video/base.html (1)

1-172: LGTM!

The base template is well-structured for video rendering with WeasyPrint. All utility classes are properly defined inline, including the custom w-76 class. The use of Arial font is appropriate for PDF/image rendering.

backend/apps/common/eleven_labs.py (2)

43-46: Validate API key before initialization.

The code accesses settings.ELEVENLABS_API_KEY without checking if it's configured. If the key is missing or empty, the ElevenLabsClient initialization may fail with an unclear error message.

🔎 Add validation before client initialization
+        api_key = settings.ELEVENLABS_API_KEY
+        if not api_key:
+            msg = "ELEVENLABS_API_KEY is not configured in settings"
+            raise ValueError(msg)
+
         self.client = ElevenLabsClient(
-            api_key=settings.ELEVENLABS_API_KEY,
+            api_key=api_key,
             timeout=30,
         )

168-193: Validate text is set before generating audio.

The generate() method accesses self.text without checking if set_text() was called first. If the text attribute doesn't exist, this will raise an AttributeError.

🔎 Add validation before API call
     def generate(self) -> bytes | None:
         """Generate audio from text.
 
         Returns:
             bytes | None: The audio bytes or None on error.
 
         """
+        if not hasattr(self, "text") or not self.text:
+            logger.error("Text not set. Call set_text() before generate().")
+            return None
+
         try:
             audio_iterator = self.client.text_to_speech.convert(
                 model_id=self.model_id,
                 output_format=self.output_format,
                 text=self.text,
                 voice_id=self.voice_id,
                 voice_settings={
                     "similarity_boost": self.similarity_boost,
                     "stability": self.stability,
                     "style": self.style,
                     "use_speaker_boost": self.use_speaker_boost,
                 },
             )
 
             return b"".join(audio_iterator)
         except Exception:
             logger.exception("An error occurred during ElevenLabs API request.")
 
         return None
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1)

201-208: Handle the case when no new projects exist.

Lines 204-208 will raise an IndexError if project_names is empty (when there are no new projects or fewer than MAX_TRANSCRIPT_PROJECTS). While you mentioned this is "quite rare," it's better to handle gracefully to avoid runtime failures.

🔎 Add validation for empty project list
         project_names = [
             p.name.replace("OWASP ", "") for p in new_projects[:MAX_TRANSCRIPT_PROJECTS]
         ]
+        if not project_names:
+            # Skip or handle the no-projects case
+            logger.warning("No projects available for transcript generation")
+            formatted_project_names = ""
+        elif len(project_names) > 1:
+            formatted_project_names = f"{', '.join(project_names[:-1])}, and {project_names[-1]}"
+        else:
+            formatted_project_names = project_names[0]
-        formatted_project_names = (
-            f"{', '.join(project_names[:-1])}, and {project_names[-1]}"
-            if len(project_names) > 1
-            else project_names[0]
-        )

Alternatively, you could skip creating the projects slide entirely in Command.handle() when snapshot.new_projects.exists() returns False, which would be cleaner.

🧹 Nitpick comments (2)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)

60-60: Consider using self.stdout.write() for command output.

The print() statements could be replaced with self.stdout.write() to align with Django management command conventions. However, since this is WIP and you've indicated these will be addressed later, this is just a reminder.

Based on learnings, Django commands should use self.stdout.write() for user-facing output instead of print().

Also applies to: 72-72


307-308: Consider checking if new projects exist before creating the slide.

To avoid the potential IndexError in create_projects_slide() when there are no new projects, you could add a conditional check here:

         generator.append_slide(slide_builder.create_intro_slide())
-        generator.append_slide(slide_builder.create_projects_slide())
+        if snapshot.new_projects.exists():
+            generator.append_slide(slide_builder.create_projects_slide())
+        else:
+            logger.info("Skipping projects slide - no new projects in snapshot")
 
         generator.generate_video(output_dir / f"{snapshot_key}_snapshot.mp4")

This pairs with the earlier suggestion to handle empty project lists and provides a cleaner solution.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a54683 and 9c15eda.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • backend/.env.example
  • backend/apps/common/eleven_labs.py
  • backend/apps/owasp/Makefile
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
  • backend/apps/owasp/templates/video/base.html
  • backend/apps/owasp/templates/video/slides/intro.html
  • backend/apps/owasp/templates/video/slides/projects.html
  • backend/docker/Dockerfile.local
  • backend/pyproject.toml
  • backend/settings/base.py
  • cspell/custom-dict.txt
🚧 Files skipped from review as they are similar to previous changes (6)
  • backend/docker/Dockerfile.local
  • backend/.env.example
  • backend/settings/base.py
  • backend/apps/owasp/templates/video/slides/intro.html
  • backend/pyproject.toml
  • cspell/custom-dict.txt
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
⏰ 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). (4)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (5)
backend/apps/owasp/Makefile (1)

25-27: LGTM!

The new Makefile target follows the established pattern and correctly invokes the Django management command with the required --snapshot-key parameter.

backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (4)

30-52: LGTM!

The Slide dataclass is well-structured with proper validation in __post_init__ to ensure exactly one of transcript or transcript_input is provided. Type hints are clear and complete.


82-114: LGTM!

The transcript generation logic properly handles the case where open_ai.complete() returns None, with appropriate error logging and early return. The validation logic for pre-set transcripts and transcript input is also correct.


116-157: LGTM!

Both generate_audio() and generate_video() methods have proper validation and error handling. The audio generation checks for transcript existence before proceeding, and video generation properly re-raises exceptions after logging. FFmpeg parameters are appropriate for video encoding.


226-259: LGTM!

The Generator class orchestrates the video generation pipeline correctly. The slide processing loop and video concatenation logic using ffmpeg are well-structured. Type hints are properly included.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)

212-212: Remove or utilize unused OpenAi instance.

self.open_ai is instantiated but never used anywhere in the Generator class. Since transcripts are currently hardcoded in SlideBuilder methods, this instance serves no purpose and should either be removed to reduce unnecessary dependencies, or integrated if dynamic transcript generation is planned.

🔎 If not needed, remove it
 class Generator:
     def __init__(self) -> None:
         """Initialize Video Generator."""
         self.eleven_labs = ElevenLabs(stability=ELEVENLABS_STABILITY, style=ELEVENLABS_STYLE)
-        self.open_ai = OpenAi()
         self.slides: list[Slide] = []

285-285: Clarify output directory behavior and consider cleanup of intermediate files.

Line 285 uses OUTPUT_DIR (global temp directory) for slide intermediate files, while the user-provided output_dir is only used for the final merged video (line 295). This means:

  • User's --output-dir affects only the final .mp4 location
  • Intermediate files (images, audio, per-slide videos) remain in the system temp directory indefinitely

While using temp for intermediates is reasonable to avoid cluttering the user's directory, these files are never cleaned up after generate_video() completes. Consider either documenting this behavior or implementing cleanup of intermediate files after the merge succeeds.

🔎 Optional: Clean up intermediate files after merge
     def handle(self, *args, **options) -> None:
         """Handle the command execution."""
         # Required to cache fonts
         os.environ["XDG_CACHE_HOME"] = str(Path(tempfile.gettempdir()) / "cache")
 
         snapshot_key = options["snapshot_key"]
         output_dir = options["output_dir"]
         output_dir.mkdir(parents=True, exist_ok=True)
+        
+        # Create unique temp directory for intermediate files
+        import tempfile
+        temp_dir = Path(tempfile.mkdtemp(prefix=f"snapshot_{snapshot_key}_"))
 
         snapshot = Snapshot.objects.filter(
             key__iexact=snapshot_key, status=Snapshot.Status.COMPLETED
         ).first()
 
         if not snapshot:
             logger.error("No completed snapshot found for key: %s", snapshot_key)
             return
 
         print("Generating video for snapshot:", snapshot_key)
 
-        slide_builder = SlideBuilder(snapshot, OUTPUT_DIR)
+        slide_builder = SlideBuilder(snapshot, temp_dir)
         generator = Generator()
 
         generator.append_slide(slide_builder.create_intro_slide())
         generator.append_slide(slide_builder.create_sponsors_slide())
         if snapshot.new_projects.exists():
             generator.append_slide(slide_builder.create_projects_slide())
         else:
             print("Skipping projects slide - No new projects in snapshot.")
 
         generator.generate_video(output_dir / f"{snapshot_key}_snapshot.mp4")
+        
+        # Cleanup intermediate files
+        import shutil
+        shutil.rmtree(temp_dir, ignore_errors=True)

This creates a unique temporary directory per run (avoiding conflicts) and cleans it up after the final video is generated.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17dc5f1 and 74f09e6.

📒 Files selected for processing (1)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
⏰ 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). (4)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (1)

244-244: Add type hint for the parser parameter.

The parser parameter lacks a type annotation. Adding it improves type safety and IDE support.

🔎 Proposed improvement
+from argparse import ArgumentParser
+
 # ... existing imports ...

-    def add_arguments(self, parser) -> None:
+    def add_arguments(self, parser: ArgumentParser) -> None:
         """Add command-line arguments to the parser.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74f09e6 and f9d2330.

📒 Files selected for processing (1)
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧬 Code graph analysis (1)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)
backend/apps/common/eleven_labs.py (3)
  • ElevenLabs (17-212)
  • set_text (126-138)
  • generate_to_file (195-212)
backend/apps/owasp/api/internal/queries/snapshot.py (1)
  • snapshot (14-22)
backend/apps/owasp/api/internal/queries/sponsor.py (1)
  • sponsors (14-26)
⏰ 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). (4)
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 1, 2026
@rudransh-shrivastava rudransh-shrivastava marked this pull request as ready for review January 1, 2026 19:02
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Some comments to address:


return None

def generate_to_file(self, file_path: Path) -> bool:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's separate concerns here

Suggested change
def generate_to_file(self, file_path: Path) -> bool:
def save(self, contents: bytes, file_path: Path):

)

return b"".join(audio_iterator)
except Exception:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's allow it fail if no content was generated? Normally you don't need that wide exception handling at all.

self.image_path.parent.mkdir(parents=True, exist_ok=True)
pil_image.save(self.image_path, IMAGE_FORMAT)

except Exception:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here -- can we avoid wide exception catching?

return cleaned[0]
return ""

def create_intro_slide(self) -> Slide:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use add_ prefix for slides adding.

return Slide(
context={
"sponsors": sponsors,
"title": "Our Sponsors",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"title": "Our Sponsors",
"title": "OWASP Foundation Sponsors",

return Slide(
context={
"projects": projects_data,
"title": f"New Projects ({project_count})",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the count first and pluralize titles for all entities.


chapters = [
{
"location": f"{c.region}, {c.country}" if c.region else c.country,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use suggested_location here.

output_dir=self.output_dir,
name="thank_you",
template_name="video/slides/thank_you.html",
transcript="Thanks for tuning in!... Visit Nest at Nest dot O-WASP dot org "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
transcript="Thanks for tuning in!... Visit Nest at Nest dot O-WASP dot org "
transcript="Thanks for tuning in!... Visit Nest dot O-WASP dot org "

strawberry-graphql-django = "^0.72.0"
thefuzz = "^0.22.1"
pyparsing = "^3.2.3"
weasyprint = "^67.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it right from the start -- I don't think we want to add these deps into the backend image. Let's create a new image just for the snapshot video generation based on the backend image.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, added it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py (1)

76-119: Remove redundant import.

Line 113 imports Path from pathlib, but this is already imported at line 4 of the file. This redundant import should be removed.

🔎 Proposed fix
         assert mock_generator_instance.append_slide.call_count == 4
-        from pathlib import Path

         mock_generator_instance.generate_video.assert_called_once()
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fedd71b and 1b1b4d0.

📒 Files selected for processing (6)
  • backend/apps/common/eleven_labs.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
  • backend/apps/owasp/video.py
  • backend/tests/apps/common/eleven_labs_test.py
  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/tests/apps/owasp/video_test.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/apps/common/eleven_labs.py
  • backend/tests/apps/common/eleven_labs_test.py
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:296-296
Timestamp: 2025-12-28T15:20:59.650Z
Learning: In `backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py`, the SlideBuilder is intentionally initialized with the module constant OUTPUT_DIR (temp directory) rather than the user-specified output_dir flag. This is by design: intermediate files (images, audio, per-slide videos) are generated in temp, while the final merged video is saved to the user-specified output directory.
📚 Learning: 2025-12-28T15:20:59.650Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:296-296
Timestamp: 2025-12-28T15:20:59.650Z
Learning: In `backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py`, the SlideBuilder is intentionally initialized with the module constant OUTPUT_DIR (temp directory) rather than the user-specified output_dir flag. This is by design: intermediate files (images, audio, per-slide videos) are generated in temp, while the final merged video is saved to the user-specified output directory.

Applied to files:

  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/apps/owasp/video.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
  • backend/tests/apps/owasp/video_test.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/apps/owasp/video.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
  • backend/tests/apps/owasp/video_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/apps/owasp/video.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
  • backend/tests/apps/owasp/video_test.py
📚 Learning: 2026-01-01T18:57:05.007Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/video.py:189-215
Timestamp: 2026-01-01T18:57:05.007Z
Learning: In the OWASP backend area, maintain the established pattern: when dealing with sponsors, include all entries from Sponsor.objects.all() (including NOT_SPONSOR) and perform in-memory sorting using the same criteria/pattern used by the GraphQL sponsor query implemented in backend/apps/owasp/api/internal/queries/sponsor.py. Apply this behavior consistently to files in backend/apps/owasp (not just video.py), and ensure code paths that render sponsor lists follow this in-code sorting approach rather than pre-filtering NOT_SPONSOR entries before sorting.

Applied to files:

  • backend/apps/owasp/video.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-06-18T20:00:23.899Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1634
File: frontend/src/app/api/auth/[...nextauth]/route.ts:30-55
Timestamp: 2025-06-18T20:00:23.899Z
Learning: The OWASP Nest application has logging disabled, so avoid suggesting console.log, console.error, or any other logging statements in code review suggestions.

Applied to files:

  • backend/apps/owasp/video.py
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-09-21T17:04:48.154Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/app/snapshots/[id]/page.tsx:0-0
Timestamp: 2025-09-21T17:04:48.154Z
Learning: User rudransh-shrivastava confirmed that suggested type safety improvements during Apollo Client migration were no longer relevant, reinforcing their preference to keep migration PRs focused on core migration changes rather than additional improvements.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧬 Code graph analysis (2)
backend/apps/owasp/video.py (2)
backend/apps/common/eleven_labs.py (4)
  • ElevenLabs (18-218)
  • save (209-218)
  • set_text (144-156)
  • generate (186-207)
backend/apps/owasp/models/sponsor.py (1)
  • SponsorType (21-27)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)
backend/apps/owasp/api/internal/queries/snapshot.py (2)
  • snapshot (14-22)
  • snapshots (25-31)
backend/apps/owasp/video.py (11)
  • SlideBuilder (125-325)
  • VideoGenerator (328-397)
  • add_intro_slide (158-174)
  • add_sponsors_slide (176-202)
  • add_projects_slide (204-239)
  • add_chapters_slide (241-271)
  • add_releases_slide (273-314)
  • add_thank_you_slide (316-325)
  • append_slide (336-343)
  • generate_video (345-364)
  • cleanup (392-397)
backend/tests/apps/owasp/video_test.py (3)
  • slide_builder (133-135)
  • generator (316-319)
  • slide (17-25)
⏰ 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). (3)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
🔇 Additional comments (22)
backend/tests/apps/owasp/video_test.py (6)

1-13: LGTM - Imports and module setup.

The imports are appropriate for the test suite. The module correctly imports the public API surface from apps.owasp.video including constants and classes under test.


15-68: LGTM - TestSlide fixture and property/render tests.

The fixture provides a well-structured sample Slide instance. The property tests correctly verify path construction using the module constants. The render_and_save_image_success test properly mocks the PDF rendering pipeline and verifies resource cleanup calls.


70-117: LGTM - Audio and video generation tests.

Good coverage for both success and failure paths. The exception tests correctly import the real ffmpeg.Error to verify exception propagation, and the logger mock properly asserts that exceptions are logged.


120-154: LGTM - SlideBuilder fixtures and format_names_for_transcript tests.

Parameterized tests effectively cover edge cases: single name, two names, three names, more than limit (truncated to 3), and empty list. This aligns with the TRANSCRIPT_NAMES_LIMIT = 3 constant.


156-311: LGTM - Slide creation method tests.

The tests properly mock ORM objects and verify slide properties. The tests for add_projects_slide, add_chapters_slide, and add_releases_slide correctly validate both the None return case (no data) and the successful slide creation case.


314-391: LGTM - VideoGenerator tests.

The tests properly mock the ElevenLabs client during initialization and verify the slide lifecycle: appending slides, generating per-slide assets, and merging. The exception handling test for merge_videos correctly verifies logging behavior.

backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)

1-14: LGTM - Module setup and imports.

Clean module docstring, appropriate imports, and well-structured logger setup. The DOCKER_CONTAINER_NAME constant provides clear guidance for users.


17-31: LGTM - Command class and argument configuration.

The help text is clear and the argument definition properly documents the expected format with examples.


33-72: LGTM - Command handle method implementation.

The implementation follows Django management command best practices:

  • Uses self.stdout.write() for user-facing output
  • Uses self.style.SUCCESS() for styled success message
  • Properly filters None slides before appending
  • Calls cleanup() to remove intermediate files
  • Provides helpful docker cp guidance

One minor note: Line 41 uses if not snapshots: for the empty check. Based on learnings, while this works correctly (QuerySets support boolean evaluation), using snapshots.exists() would be slightly more efficient as it avoids fetching results. This is acceptable given the context.

backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py (4)

1-23: LGTM - Test setup and fixtures.

The sys.modules["weasyprint"] = MagicMock() on line 10 is correctly placed before the command import to avoid WeasyPrint initialization during tests. The command fixture properly mocks stdout for output verification.


25-42: LGTM - Command interface tests.

Good coverage of the command's public interface: help text, inheritance from BaseCommand, and argument configuration.


44-75: LGTM - No snapshots handling test.

The test correctly verifies that when no completed snapshots are found, the command logs an error and does not instantiate SlideBuilder or VideoGenerator.


121-178: LGTM - Additional handle method tests.

Good coverage for:

  • Filtering out None slides (verifies only 2 slides appended when 4 return None)
  • Docker cp command output verification

The tests effectively validate the command's behavior with various slide configurations.

backend/apps/owasp/video.py (9)

1-38: LGTM - Module setup and constants.

The XDG_CACHE_HOME environment variable (line 12) is correctly set before the WeasyPrint import to enable font caching. The constants are well-organized and appropriately named.

Note: Setting os.environ["XDG_CACHE_HOME"] at module level affects the global environment when this module is imported. This is documented as required for WeasyPrint and is acceptable given the module's purpose.


41-65: LGTM - Slide dataclass and path properties.

Clean dataclass design with appropriate fields. The path properties correctly use the module constants for file extensions.


66-87: LGTM - render_and_save_image implementation.

Proper resource management using try/finally to ensure PDF document and page are closed even if an exception occurs. The pre-initialization of page and pdf to None (lines 68-69) ensures the finally block handles partial initialization correctly.


88-123: LGTM - Audio and video generation methods.

The generate_and_save_audio method has a clean interface delegating to the ElevenLabs client. The generate_and_save_video method properly handles ffmpeg errors with logging and re-raises for caller handling.


125-157: LGTM - SlideBuilder initialization and format_names_for_transcript.

The static method format_names_for_transcript handles all edge cases correctly:

  • Empty list returns empty string
  • Single name returns the name
  • Multiple names use proper comma-separation with "and" before the last

The docstrings are comprehensive with proper Args and Returns documentation.


158-202: LGTM - Intro and sponsors slide creation.

The add_intro_slide correctly handles both single and multi-snapshot scenarios for naming. The add_sponsors_slide follows the established codebase pattern for sponsor sorting (matching the GraphQL query in backend/apps/owasp/api/internal/queries/sponsor.py). Based on learnings, this in-memory sorting approach is intentional.


204-271: LGTM - Projects and chapters slide creation.

Both methods correctly:

  • Use .exists() for efficient empty check (per learnings)
  • Return None when no data exists
  • Format names using the shared helper method
  • Include appropriate context for template rendering

273-325: LGTM - Releases and thank you slide creation.

The add_releases_slide method includes a defensive check for empty top_projects (lines 288-289), returning None to skip the slide gracefully. This addresses the edge case discussed in past reviews. The add_thank_you_slide provides a simple closing slide.


328-397: LGTM - VideoGenerator class.

Well-structured orchestration class:

  • Proper initialization with ElevenLabs client and slides list
  • generate_video coordinates the full pipeline (render → audio → video → merge)
  • merge_videos has proper error handling with logging
  • cleanup safely removes intermediate files, checking existence before unlinking

The docstrings are comprehensive with proper Args, Returns, and Raises documentation.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 2, 2026
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 2, 2026

Please retry analysis of this Pull-Request directly on SonarQube Cloud

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
backend/apps/common/eleven_labs.py (2)

193-214: Consider adding a check for self.text before API call.

The generate() method assumes set_text() was called. If not, an AttributeError will occur. While the current usage pattern is safe (based on the codebase review), a defensive check would make the API more robust.

🔎 Optional: Add defensive validation
 def generate(self) -> bytes:
     """Generate audio from text.

     Returns:
         bytes: The audio bytes.

     """
+    if not hasattr(self, "text") or not self.text:
+        msg = "Text not set. Call set_text() before generate()."
+        raise ValueError(msg)
+
     audio_iterator = self.client.text_to_speech.convert(
         model_id=self.model_id,
         output_format=self.output_format,
         text=self.text,
         voice_id=self.voice_id,
         voice_settings=VoiceSettings(
             similarity_boost=self.similarity_boost,
             speed=self.speed,
             stability=self.stability,
             style=self.style,
             use_speaker_boost=self.use_speaker_boost,
         ),
     )

     return b"".join(audio_iterator)

21-66: Consider validating parameter ranges in constructor.

The docstrings specify valid ranges (e.g., similarity_boost: 0.0-1.0, speed: 0.25-4.0), but there's no runtime validation. Invalid values will be caught by the ElevenLabs API, but early validation would provide clearer error messages.

🔎 Example validation for numeric parameters
 def __init__(
     self,
     model_id: str = "eleven_multilingual_v2",
     output_format: str = "mp3_44100_128",
     similarity_boost: float = 0.75,
     speed: float = 1.0,
     stability: float = 0.5,
     style: float = 0.0,
     voice_id: str = "1SM7GgM6IMuvQlz2BwM3",  # cspell:disable-line
     *,
     use_speaker_boost: bool = True,
 ) -> None:
     """ElevenLabs constructor.

     Args:
         model_id (str): The model to use.
         output_format (str): Audio output format.
         similarity_boost (float): Voice consistency (0.0-1.0).
         speed (float): Speech speed (0.25-4.0, default 1.0).
         stability (float): Voice stability (0.0-1.0).
         style (float): Style exaggeration (0.0-1.0).
         use_speaker_boost (bool): Enable speaker clarity boost.
         voice_id (str): The voice ID to use.

     Raises:
         ValueError: If the ElevenLabs API key is not set.

     """
     if not (api_key := os.getenv("DJANGO_ELEVENLABS_API_KEY")):
         error_msg = "DJANGO_ELEVENLABS_API_KEY environment variable not set"
         raise ValueError(error_msg)
+
+    if not 0.0 <= similarity_boost <= 1.0:
+        msg = f"similarity_boost must be between 0.0 and 1.0, got {similarity_boost}"
+        raise ValueError(msg)
+    if not 0.25 <= speed <= 4.0:
+        msg = f"speed must be between 0.25 and 4.0, got {speed}"
+        raise ValueError(msg)
+    if not 0.0 <= stability <= 1.0:
+        msg = f"stability must be between 0.0 and 1.0, got {stability}"
+        raise ValueError(msg)
+    if not 0.0 <= style <= 1.0:
+        msg = f"style must be between 0.0 and 1.0, got {style}"
+        raise ValueError(msg)

     self.client = ElevenLabsClient(
         api_key=api_key,
         timeout=30,
     )
backend/docker/Dockerfile.video (1)

68-68: Container has no default command.

The Dockerfile ends without a CMD or ENTRYPOINT. While the Makefile provides an explicit command, this means:

  • Running the container without arguments will fail
  • No self-documenting default behavior
  • Less convenient for manual testing
🔎 Consider adding a helpful default command
 USER owasp
+
+CMD ["python", "manage.py", "help", "owasp_generate_community_snapshot_video"]

This would display help text when run without arguments, making the container more self-documenting.

backend/apps/owasp/Makefile (1)

25-34: Add validation for required snapshot_key parameter.

The target uses $(snapshot_key) without validation. If not provided, the container will run with an incomplete command, causing a late failure with unclear errors.

🔎 Add pre-flight validation
 owasp-generate-community-snapshot-video:
+	@if [ -z "$(snapshot_key)" ]; then \
+		echo "Error: snapshot_key parameter is required"; \
+		echo "Usage: make owasp-generate-community-snapshot-video snapshot_key=<key>"; \
+		exit 1; \
+	fi
 	@docker build -f backend/docker/Dockerfile.video backend/ \
 		-t nest-video-backend
 	@docker run \
 		--mount type=bind,src="$(PWD)",dst=/home/owasp/generated_videos \
 		--env-file backend/.env \
 		--network nest-local_nest-network \
 		--rm nest-video-backend \
 		python manage.py owasp_generate_community_snapshot_video $(snapshot_key) /home/owasp/generated_videos
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b1b4d0 and 7ed5252.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • .gitignore
  • backend/apps/common/eleven_labs.py
  • backend/apps/owasp/Makefile
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
  • backend/apps/owasp/video.py
  • backend/docker/Dockerfile
  • backend/docker/Dockerfile.local
  • backend/docker/Dockerfile.video
  • backend/pyproject.toml
  • backend/tests/apps/common/eleven_labs_test.py
  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • backend/tests/apps/common/eleven_labs_test.py
  • backend/pyproject.toml
  • backend/docker/Dockerfile.local
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:296-296
Timestamp: 2025-12-28T15:20:59.650Z
Learning: In `backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py`, the SlideBuilder is intentionally initialized with the module constant OUTPUT_DIR (temp directory) rather than the user-specified output_dir flag. This is by design: intermediate files (images, audio, per-slide videos) are generated in temp, while the final merged video is saved to the user-specified output directory.
📚 Learning: 2025-12-28T15:20:59.650Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:296-296
Timestamp: 2025-12-28T15:20:59.650Z
Learning: In `backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py`, the SlideBuilder is intentionally initialized with the module constant OUTPUT_DIR (temp directory) rather than the user-specified output_dir flag. This is by design: intermediate files (images, audio, per-slide videos) are generated in temp, while the final merged video is saved to the user-specified output directory.

Applied to files:

  • backend/apps/owasp/Makefile
  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/apps/owasp/video.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/apps/common/eleven_labs.py
  • backend/apps/owasp/video.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py
  • backend/apps/common/eleven_labs.py
  • backend/apps/owasp/video.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-08-19T15:37:57.180Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/settings/base.py:34-37
Timestamp: 2025-08-19T15:37:57.180Z
Learning: In django-configurations, IS_GOOGLE_AUTH_ENABLED computed from Value/SecretValue descriptors works correctly at runtime despite descriptors being objects at class definition time, as evidenced by extensive test coverage using override_settings that successfully validates both enabled/disabled states.

Applied to files:

  • backend/apps/common/eleven_labs.py
📚 Learning: 2026-01-01T18:57:05.007Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/video.py:189-215
Timestamp: 2026-01-01T18:57:05.007Z
Learning: In the OWASP backend area, maintain the established pattern: when dealing with sponsors, include all entries from Sponsor.objects.all() (including NOT_SPONSOR) and perform in-memory sorting using the same criteria/pattern used by the GraphQL sponsor query implemented in backend/apps/owasp/api/internal/queries/sponsor.py. Apply this behavior consistently to files in backend/apps/owasp (not just video.py), and ensure code paths that render sponsor lists follow this in-code sorting approach rather than pre-filtering NOT_SPONSOR entries before sorting.

Applied to files:

  • backend/apps/owasp/video.py
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-06-18T20:00:23.899Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1634
File: frontend/src/app/api/auth/[...nextauth]/route.ts:30-55
Timestamp: 2025-06-18T20:00:23.899Z
Learning: The OWASP Nest application has logging disabled, so avoid suggesting console.log, console.error, or any other logging statements in code review suggestions.

Applied to files:

  • backend/apps/owasp/video.py
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-09-21T17:04:48.154Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/app/snapshots/[id]/page.tsx:0-0
Timestamp: 2025-09-21T17:04:48.154Z
Learning: User rudransh-shrivastava confirmed that suggested type safety improvements during Apollo Client migration were no longer relevant, reinforcing their preference to keep migration PRs focused on core migration changes rather than additional improvements.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🧬 Code graph analysis (3)
backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py (2)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (3)
  • Command (17-78)
  • add_arguments (20-36)
  • handle (38-78)
backend/apps/owasp/video.py (9)
  • add_intro_slide (153-169)
  • add_sponsors_slide (171-197)
  • add_projects_slide (199-234)
  • add_chapters_slide (236-266)
  • add_releases_slide (268-309)
  • add_thank_you_slide (311-320)
  • append_slide (331-338)
  • generate_video (340-359)
  • cleanup (387-392)
backend/apps/owasp/video.py (2)
backend/apps/common/eleven_labs.py (4)
  • ElevenLabs (18-225)
  • save (216-225)
  • set_text (151-163)
  • generate (193-214)
backend/apps/owasp/models/sponsor.py (1)
  • SponsorType (21-27)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)
backend/apps/owasp/api/internal/queries/snapshot.py (2)
  • snapshot (14-22)
  • snapshots (25-31)
backend/apps/owasp/video.py (6)
  • SlideBuilder (120-320)
  • VideoGenerator (323-392)
  • append_slide (331-338)
  • video_path (57-59)
  • generate_video (340-359)
  • cleanup (387-392)
⏰ 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). (7)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run Code Scan
  • GitHub Check: CodeQL (python)
  • GitHub Check: CodeQL (javascript-typescript)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (25)
.gitignore (1)

31-31: LGTM!

The ignore pattern correctly targets generated video output directories and is appropriately placed after related build artifacts.

backend/apps/common/eleven_labs.py (1)

49-51: LGTM! Environment variable validation is now explicit.

The constructor properly validates the API key at initialization using os.getenv and raises a clear error message, ensuring fail-fast behavior. This addresses the earlier concern about configuration validation.

backend/docker/Dockerfile (1)

36-36: LGTM! Video dependencies appropriately excluded.

The --without video flag correctly excludes video-specific dependencies from the main image, reducing size and maintaining clean separation with the dedicated video Dockerfile.

backend/docker/Dockerfile.video (1)

9-9: Different APK cache directory name from main Dockerfile.

Line 9 uses /home/owasp/.cache/apk-backend-video while the main Dockerfile uses /home/owasp/.cache/apk. This difference may be intentional for cache isolation, but it means Docker cache layers won't be shared between the two builds.

Is the different cache directory name intentional? If cache isolation isn't needed, using the same path would improve build times through shared cache layers.

backend/tests/apps/owasp/management/commands/owasp_generate_community_snapshot_video_test.py (4)

1-23: LGTM!

The module-level weasyprint mock and fixture setup are correctly implemented. Mocking weasyprint before importing the command avoids system dependency issues during test execution, and the noqa: E402 is appropriately used.


25-42: LGTM!

The command surface tests appropriately verify help text, inheritance, and argument parser configuration. The assertion on call_count is sufficient for verifying that both required arguments are registered.


44-77: LGTM!

The test correctly verifies early-exit behavior when no completed snapshots are found. The class-level patches are efficiently applied, and the mock assertions confirm that the command doesn't proceed with slide generation when the snapshot query returns empty.


78-183: LGTM!

The tests comprehensively cover the handle method's behavior:

  • Valid snapshots with mixed None/non-None slides
  • None-slide filtering logic
  • Success message output with correct path information

The assertions verify that only non-None slides are appended to the generator and that the final video path is correctly constructed.

backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (5)

1-16: LGTM!

Imports are clean and well-organized. The logger and constant are appropriately defined for the command's needs.


17-37: LGTM!

The command interface is well-defined with clear help text and properly documented arguments. The examples in the help strings guide users on expected input formats.


38-49: LGTM!

The snapshot query correctly filters by key prefix and completion status. The early-return pattern prevents unnecessary processing when no snapshots exist. The boolean evaluation of the QuerySet is valid and triggers the appropriate database query.


51-60: LGTM!

The directory management correctly separates temporary intermediate files (images, audio, per-slide videos) from the final output. The SlideBuilder initialization with temp_output_dir is intentional per the established design pattern.

Based on learnings, this separation ensures intermediate files are isolated in temp while the final merged video goes to the user-specified output directory.


62-78: LGTM!

The slide generation workflow is well-structured:

  • Slides are created in logical order
  • None slides are filtered before being appended to the generator
  • The final video is correctly saved to video_output_dir (user-specified location)
  • Cleanup removes intermediate files after generation
  • Success message uses styled output following Django conventions
backend/apps/owasp/video.py (12)

1-34: LGTM!

Imports and constants are well-organized. The configuration values (speed, limits, formats) are appropriately defined as module-level constants for easy adjustment.


36-82: LGTM!

The Slide dataclass is well-structured with clean property accessors. The render_and_save_image method properly handles resource management by initializing page and pdf to None before the try block and conditionally closing them in the finally block, preventing potential NameError exceptions.


83-118: LGTM!

Audio and video generation methods are well-implemented. The error handling in generate_and_save_video properly logs failures with context and re-raises exceptions to propagate errors to callers.


120-152: LGTM!

The SlideBuilder initialization and format_names_for_transcript helper are well-implemented. The formatting logic gracefully handles empty lists, single names, and multiple names with proper punctuation.


153-169: LGTM!

The intro slide creation correctly handles both single-month and multi-month (year) snapshots. The command's early-exit ensures this method is never called with empty snapshots.


171-197: LGTM!

The sponsors slide correctly follows the established pattern of including all sponsors (including NOT_SPONSOR) and sorting them in-memory by tier priority. This aligns with the GraphQL sponsor query implementation.

Based on learnings, this in-code sorting approach is the standard for sponsor lists in the OWASP backend.


199-234: LGTM!

The projects slide creation properly handles the no-projects case by returning None. The query and data transformation are well-structured, and the transcript formatting is clear.


236-266: LGTM!

The chapters slide follows the same pattern as projects, correctly handling empty data by returning None. The use of suggested_location provides useful geographic context.


268-309: LGTM!

The releases slide correctly handles empty data cases at both the releases level and the top-projects level. The N+1 query pattern (lines 274-280) is acceptable for this local command context as previously discussed.

The bar width calculation and transcript formatting are well-implemented.


311-320: LGTM!

The thank you slide is appropriately simple, with an empty context and a friendly closing transcript.


323-359: LGTM!

The VideoGenerator class is well-structured:

  • Initializes ElevenLabs client with appropriate speed setting
  • append_slide method is straightforward
  • generate_video orchestrates the full pipeline: render images, generate audio, create per-slide videos, merge, and return the final path

361-392: LGTM!

The merge_videos and cleanup methods complete the video generation pipeline:

  • merge_videos properly concatenates all slide videos and audio streams with appropriate error handling
  • The ffmpeg error logging and re-raising ensures failures are visible
  • cleanup efficiently removes all intermediate files after the final video is produced

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py (2)

47-49: Consider using .exists() for more efficient existence check.

The boolean evaluation if not snapshots: is functionally correct but triggers a full database query. Using snapshots.exists() would run a more efficient EXISTS query without fetching objects.

Based on learnings, while the current approach works, .exists() is preferred for performance when you only need to know whether any rows exist.

🔎 Suggested improvement
-    if not snapshots:
+    if not snapshots.exists():
         logger.error("No completed snapshots found for key: %s", snapshot_key)
         return

75-76: Add try-finally block to ensure cleanup runs even on errors.

Currently, cleanup() is called after video generation without error handling. If generate_video() raises an exception, cleanup won't execute, potentially leaving temporary files behind.

🔎 Suggested improvement
-        video_path = generator.generate_video(video_output_dir, f"{snapshot_key}_snapshot")
-        generator.cleanup()
+        try:
+            video_path = generator.generate_video(video_output_dir, f"{snapshot_key}_snapshot")
+        finally:
+            generator.cleanup()

This ensures cleanup runs regardless of whether video generation succeeds or fails.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 582dd4e and b85438e.

📒 Files selected for processing (3)
  • .gitignore
  • backend/apps/owasp/Makefile
  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/owasp/Makefile
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:296-296
Timestamp: 2025-12-28T15:20:59.650Z
Learning: In `backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py`, the SlideBuilder is intentionally initialized with the module constant OUTPUT_DIR (temp directory) rather than the user-specified output_dir flag. This is by design: intermediate files (images, audio, per-slide videos) are generated in temp, while the final merged video is saved to the user-specified output directory.
📚 Learning: 2025-12-28T15:20:59.650Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:296-296
Timestamp: 2025-12-28T15:20:59.650Z
Learning: In `backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py`, the SlideBuilder is intentionally initialized with the module constant OUTPUT_DIR (temp directory) rather than the user-specified output_dir flag. This is by design: intermediate files (images, audio, per-slide videos) are generated in temp, while the final merged video is saved to the user-specified output directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-08-22T06:36:42.593Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2089
File: backend/apps/nest/models/google_account_authorization.py:61-62
Timestamp: 2025-08-22T06:36:42.593Z
Learning: In the OWASP/Nest project, ruff linting rules discourage using logger.error() immediately before raising exceptions as it creates redundant logging. The preferred approach is to remove the logging call and let the caller handle logging the exception appropriately.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2026-01-01T18:57:05.007Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/video.py:189-215
Timestamp: 2026-01-01T18:57:05.007Z
Learning: In the OWASP backend area, maintain the established pattern: when dealing with sponsors, include all entries from Sponsor.objects.all() (including NOT_SPONSOR) and perform in-memory sorting using the same criteria/pattern used by the GraphQL sponsor query implemented in backend/apps/owasp/api/internal/queries/sponsor.py. Apply this behavior consistently to files in backend/apps/owasp (not just video.py), and ensure code paths that render sponsor lists follow this in-code sorting approach rather than pre-filtering NOT_SPONSOR entries before sorting.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-09-21T17:04:48.154Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2178
File: frontend/src/app/snapshots/[id]/page.tsx:0-0
Timestamp: 2025-09-21T17:04:48.154Z
Learning: User rudransh-shrivastava confirmed that suggested type safety improvements during Apollo Client migration were no longer relevant, reinforcing their preference to keep migration PRs focused on core migration changes rather than additional improvements.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
⏰ 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). (3)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
🔇 Additional comments (1)
.gitignore (1)

30-30: ✅ Appropriate ignore pattern for generated video artifacts.

Adding backend/generated_videos/ correctly excludes generated video output from version control. The placement alongside other backend artifacts (backup*, staticfiles) is logical and maintains readability.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 2, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 2, 2026
@rudransh-shrivastava
Copy link
Collaborator Author

one more change, I'll use --mount instead of --volume.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 2, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend backend-tests docker Pull requests that update Docker code makefile

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create Community Snapshot Videos Task

2 participants