Skip to content

Add Community Snapshot Videos Command#2948

Merged
arkid15r merged 65 commits intoOWASP:mainfrom
rudransh-shrivastava:feature/community-snapshot-videos
Jan 13, 2026
Merged

Add Community Snapshot Videos Command#2948
arkid15r merged 65 commits intoOWASP:mainfrom
rudransh-shrivastava:feature/community-snapshot-videos

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

Release Notes

  • New Features
    • Added the ability to generate community snapshot videos with multiple content slides including sponsors, projects, chapters, and releases.
    • Integrated AI-powered audio narration to accompany video content.
    • Automated video assembly with intro and thank you slides for complete snapshot presentations.

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

Walkthrough

Adds an end-to-end OWASP community snapshot video pipeline: slide/video engine, ElevenLabs audio integration, Jinja slide templates, a Django management command, video-specific Dockerfile and build targets, consolidated Jinja loader, tests, dependency additions, and .gitignore updates.

Changes

Cohort / File(s) Summary
Video core & command
backend/apps/owasp/video.py, backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py
New Slide, SlideBuilder, VideoGenerator classes and a Django management command to build community snapshot videos from Snapshot data and write outputs to a specified directory. Review: audio/video generation, file paths, ffmpeg error handling, and ORM queries.
Video templates
backend/apps/owasp/templates/video/base.jinja, backend/apps/owasp/templates/video/slides/intro.jinja, .../sponsors.jinja, .../projects.jinja, .../chapters.jinja, .../releases.jinja, .../thank_you.jinja
New base and slide-specific Jinja templates used to render slide HTML for image rendering and transcripts. Review: CSS/layout and template variables used by SlideBuilder.
ElevenLabs client & config
backend/apps/common/eleven_labs.py, backend/.env.example
New synchronous ElevenLabs wrapper with fluent setters, generate/save methods; example env var DJANGO_ELEVENLABS_API_KEY added. Review: API key handling, timeout, and audio byte handling.
Template loader consolidation
backend/apps/common/template_loader.py, backend/apps/slack/commands/command.py, backend/apps/slack/events/event.py, removed backend/apps/slack/template_loader.py
Introduces shared Jinja environments (slack_env, video_env), updates Slack modules to import slack_env, and removes old slack loader. Review: import surfaces and template paths.
Docker & build targets
docker/backend/Dockerfile.video, docker/backend/Dockerfile, docker/backend/Dockerfile.local, backend/apps/owasp/Makefile, backend/Makefile
Adds video-specific Dockerfile, excludes video group from default builds, new Makefile target to build/run video image, and extended cleanup to remove video image. Review: Dockerfile.video packages (ffmpeg, WeasyPrint deps), build cache, and Makefile binding/volumes.
Dependencies & spellings
backend/pyproject.toml, cspell/custom-dict.txt
Adds Poetry dependency group video (elevenlabs, ffmpeg-python, pillow, pypdfium2, weasyprint) and new spellings (elevenlabs, pdfium, codec terms). Review: dependency group naming and versions.
Tests
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
New unit tests covering ElevenLabs wrapper, management command, SlideBuilder, VideoGenerator, and external integrations (ffmpeg, PDF rendering). Review: test mocks and coverage of error paths.
Generated output & ignores
.gitignore, backend/generated_videos/
Adds backend/generated_videos/ to .gitignore; Makefile mounts this path for generated outputs. Review: path ownership and host/container mount considerations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • kasya
  • ahmedxgouda
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a backend command for community snapshot video generation, which is the primary objective of this PR.
Description check ✅ Passed The description is well-structured, references the resolved issue (#2924), lists the three main changes, includes the required checklist, and provides transparency about AI usage.
Linked Issues check ✅ Passed The PR fully addresses issue #2924 objectives: implements a backend command for automated video generation, creates HTML templates for rendering slide images, uses ElevenLabs API for audio generation, and combines images with audio into a final video.
Out of Scope Changes check ✅ Passed All changes are directly related to the video generation feature: new Makefile targets, Django management command, video generation classes, ElevenLabs wrapper, templates, Docker images, dependencies, and configuration. Refactoring of template_loader is necessary for module organization.
Docstring Coverage ✅ Passed Docstring coverage is 86.21% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fd8e652 and 77e3a8c.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • backend/Makefile
  • backend/apps/owasp/Makefile
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/owasp/Makefile
🧰 Additional context used
🧠 Learnings (3)
📓 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.
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/video.py:189-215
Timestamp: 2026-01-01T18:57:15.951Z
Learning: In backend/apps/owasp/video.py, the sponsors slide intentionally includes all sponsors from Sponsor.objects.all() (including NOT_SPONSOR entries) and sorts them in-memory using the same pattern as the GraphQL query in backend/apps/owasp/api/internal/queries/sponsor.py. This is the established codebase pattern.
📚 Learning: 2025-10-26T12:50:50.512Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2429
File: backend/Makefile:30-32
Timestamp: 2025-10-26T12:50:50.512Z
Learning: The `exec-backend-e2e-command` and `exec-db-e2e-command` Makefile targets in the backend/Makefile are intended for local development and debugging only, not for CI/CD execution, so the `-it` flags are appropriate.

Applied to files:

  • backend/Makefile
📚 Learning: 2025-12-26T06:09:08.868Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 3041
File: .github/workflows/run-ci-cd.yaml:233-243
Timestamp: 2025-12-26T06:09:08.868Z
Learning: For the OWASP/Nest repository, Redis image versions should remain consistent across all environments (production, staging, local, E2E, and CI/CD E2E tests). When upgrading Redis, update all docker-compose files and CI/CD workflow configurations together to maintain environment parity.

Applied to files:

  • backend/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). (3)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend unit tests
🔇 Additional comments (1)
backend/Makefile (1)

29-31: LGTM!

The new cleanup commands for nest-backend:base and nest-snapshot-video images follow the established pattern in this target. The force removal with suppressed output and error handling (|| true) ensures the cleanup target remains idempotent.


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)

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

🤖 Fix all issues with AI agents
In @backend/apps/common/template_loader.py:
- Around line 11-12: The Slack Jinja environment currently sets autoescape=True
which escapes Slack link syntax and breaks rendering; update the Environment
instantiation that assigns slack_env (the call using
FileSystemLoader(SLACK_TEMPLATES_DIR)) to use autoescape=False so Slack Block
Kit plain-text templates render without HTML-escaping, and remove any redundant
|safe workarounds in those templates afterward.
🧹 Nitpick comments (2)
backend/tests/apps/owasp/video_test.py (1)

15-25: Consider using .jinja extension for consistency.

The fixture uses template_name="video/slides/intro.html" but the actual templates use .jinja extension (as verified in test_add_intro_slide at line 165 which asserts slide.template_name == "slides/intro.jinja"). While this doesn't affect test correctness since rendering is mocked, aligning the extension would improve consistency.

Suggested fix
     @pytest.fixture
     def slide(self, tmp_path):
         """Fixture to provide a sample slide."""
         return Slide(
             context={"title": "Test Slide"},
             name="test_slide",
             output_dir=tmp_path,
-            template_name="video/slides/intro.html",
+            template_name="video/slides/intro.jinja",
             transcript="Test transcript",
         )
backend/apps/owasp/video.py (1)

264-305: Consider optimizing the N+1 query pattern.

Lines 271-276 iterate through releases and execute a separate database query for each release to find its project. This could be optimized by prefetching the relationship or restructuring the query.

💡 Suggested optimization using prefetch_related
 def add_releases_slide(self) -> Slide | None:
     """Add a releases slide."""
-    releases = Release.objects.filter(snapshots__in=self.snapshots).distinct()
+    releases = Release.objects.filter(snapshots__in=self.snapshots).distinct().select_related('repository')
     if not releases.exists():
         return None

     project_counts: Counter = Counter()
+    # Fetch all projects with their repositories in one query
+    repository_ids = [r.repository_id for r in releases if r.repository_id]
+    project_map = {
+        repo.id: project
+        for project in Project.objects.filter(repositories__id__in=repository_ids).prefetch_related('repositories')
+        for repo in project.repositories.all()
+    }
+    
     for release in releases:
         if not release.repository:
             continue
-        project = Project.objects.filter(repositories=release.repository).first()
+        project = project_map.get(release.repository_id)
         if project:
             project_counts[project.name] += 1

Minor: Redundant defensive check on line 282.

The ternary else 1 is unreachable since line 280 already returns None when top_projects is empty.

🔧 Simplify the defensive check
-    max_count = top_projects[0][1] if top_projects else 1
+    max_count = top_projects[0][1]
📜 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 522d678 and 9bba714.

📒 Files selected for processing (13)
  • backend/apps/common/template_loader.py
  • backend/apps/owasp/templates/video/base.jinja
  • backend/apps/owasp/templates/video/slides/chapters.jinja
  • backend/apps/owasp/templates/video/slides/intro.jinja
  • backend/apps/owasp/templates/video/slides/projects.jinja
  • backend/apps/owasp/templates/video/slides/releases.jinja
  • backend/apps/owasp/templates/video/slides/sponsors.jinja
  • backend/apps/owasp/templates/video/slides/thank_you.jinja
  • backend/apps/owasp/video.py
  • backend/apps/slack/commands/command.py
  • backend/apps/slack/events/event.py
  • backend/apps/slack/template_loader.py
  • backend/tests/apps/owasp/video_test.py
💤 Files with no reviewable changes (1)
  • backend/apps/slack/template_loader.py
✅ Files skipped from review due to trivial changes (1)
  • backend/apps/owasp/templates/video/slides/sponsors.jinja
🧰 Additional context used
🧠 Learnings (6)
📓 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/templates/video/slides/chapters.jinja
  • backend/apps/owasp/templates/video/slides/releases.jinja
  • backend/apps/owasp/templates/video/slides/thank_you.jinja
  • backend/apps/owasp/templates/video/slides/intro.jinja
  • backend/tests/apps/owasp/video_test.py
  • backend/apps/owasp/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/slack/commands/command.py
  • backend/apps/slack/events/event.py
  • backend/tests/apps/owasp/video_test.py
  • backend/apps/common/template_loader.py
  • backend/apps/owasp/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/slack/commands/command.py
  • backend/apps/slack/events/event.py
  • backend/tests/apps/owasp/video_test.py
  • backend/apps/common/template_loader.py
  • backend/apps/owasp/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/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
🧬 Code graph analysis (1)
backend/apps/owasp/video.py (1)
backend/apps/common/eleven_labs.py (4)
  • ElevenLabs (18-225)
  • save (216-225)
  • set_text (151-163)
  • generate (193-214)
⏰ 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)
🔇 Additional comments (21)
backend/apps/slack/events/event.py (1)

13-13: LGTM: Centralized template loader migration.

The import change correctly migrates to the new centralized template loader while maintaining the same interface.

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

7-9: LGTM: Path configuration is correct.

The BASE_DIR calculation and template directory paths are properly configured.

backend/apps/slack/commands/command.py (1)

10-10: LGTM: Centralized template loader migration.

The import change correctly migrates to the new centralized template loader while maintaining the same interface.

backend/apps/owasp/templates/video/slides/releases.jinja (1)

1-18: LGTM!

The template structure is clean and consistent with other slide templates. The use of release.bar_width in the inline style is appropriate for numeric percentage values, and Jinja2's default autoescaping handles the text content safely.

backend/apps/owasp/templates/video/slides/chapters.jinja (1)

1-17: LGTM!

The template correctly limits display to 24 chapters with an overflow indicator. The slice syntax and length filter usage are idiomatic Jinja2.

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

1-7: LGTM!

Clean and minimal intro slide template with proper centering using flexbox utilities.

backend/apps/owasp/templates/video/slides/thank_you.jinja (1)

1-11: LGTM!

The thank you slide is appropriately simple with static content. The AI-generated audio disclosure at the bottom-right is a good practice for transparency.

backend/apps/owasp/templates/video/slides/projects.jinja (1)

1-20: LGTM!

The template follows the same consistent pattern as the chapters template, with appropriate limits (16 projects) and conditional leaders display. The structure is clean and maintainable.

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

1-252: LGTM!

The base template is well-structured for video slide rendering with fixed 1920x1080 dimensions. The inline CSS utility classes provide a self-contained styling system appropriate for PDF-to-image rendering, avoiding external dependencies. The @page rule ensures proper PDF output dimensions.

backend/tests/apps/owasp/video_test.py (3)

42-71: LGTM!

Thorough test coverage for render_and_save_image with proper mocking of the PDF rendering pipeline. The test correctly verifies template rendering, PDF generation, and resource cleanup (close calls).


123-158: LGTM!

Good use of @pytest.mark.parametrize for format_names_for_transcript covering edge cases including empty list, single name, and truncation at 3 names. The fixture setup for mock snapshots is clean.


317-394: LGTM!

Comprehensive tests for VideoGenerator covering the slide accumulation, video generation workflow, and merge operations. The exception handling tests properly verify that errors are logged before re-raising.

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

1-22: LGTM! Clean module organization.

The imports are well-organized, and the module docstring clearly describes the purpose.


24-33: LGTM! Well-defined constants.

The module-level constants are appropriately named and provide clear configuration points.


36-116: LGTM! Excellent resource management and error handling.

The Slide class demonstrates:

  • Proper cleanup of PDF/page resources in the finally block
  • Well-documented methods with Args and Raises sections
  • Appropriate error handling with logging and re-raising FFmpeg errors

118-149: LGTM! Clear initialization and formatting logic.

The SlideBuilder initialization and name formatting are well-implemented with comprehensive docstrings.


151-195: LGTM! Correct sponsor sorting implementation.

The sponsor slide correctly includes all sponsors and applies in-memory sorting by sponsor type, consistent with the established pattern.

Based on learnings, this approach is the correct pattern for sponsor lists in the OWASP backend.


197-231: LGTM! Proper QuerySet existence check.

The method correctly uses .exists() for the emptiness check (line 204), avoiding unnecessary database queries.


233-262: LGTM! Proper QuerySet existence check.

The method correctly uses .exists() for the emptiness check (line 238).


307-316: LGTM! Simple and clear.

The thank you slide implementation is straightforward.


319-388: LGTM! Well-structured video generation orchestration.

The VideoGenerator class demonstrates:

  • Clear separation of concerns (rendering, audio, video generation, merging, cleanup)
  • Comprehensive docstrings with Args, Returns, and Raises sections
  • Proper error handling with logging and re-raising FFmpeg errors
  • Safe cleanup with existence checks before unlinking files

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
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 (8)
docker/backend/Dockerfile.video (4)

1-2: Avoid :latest base tag for reproducible builds.

Using FROM nest-backend:latest can silently change dependencies/OS packages between builds; consider pinning to a digest (or at least a versioned tag) and letting the Makefile choose it.


20-24: Make WORKDIR explicit before COPY / Poetry install to avoid base-image coupling.

Right now COPY poetry.lock pyproject.toml ./ depends on whatever WORKDIR the base image set; making it explicit here reduces accidental breakage if docker/backend/Dockerfile changes.

Proposed diff
 FROM nest-backend:latest

 USER root

+WORKDIR /home/owasp
+
 ENV APK_CACHE_DIR="/home/owasp/.cache/apk-backend-video" \
     APK_SYMLINK_DIR="/etc/apk/cache" \
     OWASP_GID=1000 \
     OWASP_UID=1000 \
     PIP_CACHE_DIR="/home/owasp/.cache/pip" \
     POETRY_CACHE_DIR="/home/owasp/.cache/pypoetry" \
     POETRY_VIRTUALENVS_IN_PROJECT=true \
     PYTHONUNBUFFERED=1
@@
-COPY --chmod=444 --chown=root:root poetry.lock pyproject.toml ./
+COPY --chmod=444 --chown=root:root poetry.lock pyproject.toml ./
@@
-WORKDIR /home/owasp
-
 USER owasp

Also applies to: 32-34


17-24: Double-check venv/cache ownership when switching to USER owasp.

Because Poetry runs as root and POETRY_VIRTUALENVS_IN_PROJECT=true, .venv (and potentially some caches) may end up root-owned; usually fine for reads, but can cause runtime friction if anything needs to write (bytecode, font caches, etc.). Consider chown -R ${OWASP_UID}:${OWASP_GID} /home/owasp/.venv /home/owasp/.cache after install if you hit permission issues.


25-31: Consider dropping apk upgrade for deterministic builds (unless you explicitly want “latest packages”).

apk upgrade can change the image output over time even with the same inputs; apk add ... is usually sufficient in Docker builds unless you’re intentionally rolling forward.

backend/tests/apps/owasp/video_test.py (4)

15-121: Good external-dependency isolation; consider asserting call args where it matters.

E.g., in test_generate_and_save_audio_success, you already assert set_text(transcript); similarly, for generate_and_save_video, it may be worth asserting ffmpeg.input was called with the expected audio/image paths (and that ffmpeg.output(...).run(...) got the expected flags).


144-158: Avoid hard-coding the transcript name limit in the test.

(["OWASP A", "OWASP B", "OWASP C", "OWASP D"], "A, B, and C") implicitly assumes the cap is 3; importing and using TRANSCRIPT_NAMES_LIMIT (or constructing the expectation based on it) will keep the test stable if the limit changes.


210-227: MagicMock iteration: prefer __iter__.return_value = iter([...]) over replacing __iter__.

Replacing mock_qs.__iter__ works, but setting mock_qs.__iter__.return_value tends to behave more predictably with MagicMock and reduces surprises if other magic methods get exercised.

Also applies to: 246-260, 280-307


336-349: Strengthen generate_video assertions: verify ElevenLabs instance is passed through.

Since VideoGenerator.generate_video() calls slide.generate_and_save_audio(self.eleven_labs), it’s useful to assert slideX.generate_and_save_audio.assert_called_once_with(generator.eleven_labs) (not just “called”).

📜 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 9bba714 and fdc74ac.

📒 Files selected for processing (4)
  • backend/Makefile
  • backend/apps/owasp/Makefile
  • backend/tests/apps/owasp/video_test.py
  • docker/backend/Dockerfile.video
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/owasp/Makefile
🧰 Additional context used
🧠 Learnings (7)
📓 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-10-26T12:50:50.512Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2429
File: backend/Makefile:30-32
Timestamp: 2025-10-26T12:50:50.512Z
Learning: The `exec-backend-e2e-command` and `exec-db-e2e-command` Makefile targets in the backend/Makefile are intended for local development and debugging only, not for CI/CD execution, so the `-it` flags are appropriate.

Applied to files:

  • backend/Makefile
📚 Learning: 2025-12-26T06:09:08.868Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 3041
File: .github/workflows/run-ci-cd.yaml:233-243
Timestamp: 2025-12-26T06:09:08.868Z
Learning: For the OWASP/Nest repository, Redis image versions should remain consistent across all environments (production, staging, local, E2E, and CI/CD E2E tests). When upgrading Redis, update all docker-compose files and CI/CD workflow configurations together to maintain environment parity.

Applied to files:

  • backend/Makefile
  • docker/backend/Dockerfile.video
📚 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/video_test.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/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/video_test.py
📚 Learning: 2025-10-17T15:25:55.624Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2431
File: infrastructure/providers.tf:1-3
Timestamp: 2025-10-17T15:25:55.624Z
Learning: The infrastructure code in the OWASP/Nest repository (infrastructure/ directory) is intended for quick testing purposes only, not for production deployment.

Applied to files:

  • docker/backend/Dockerfile.video
🧬 Code graph analysis (1)
backend/tests/apps/owasp/video_test.py (1)
backend/apps/owasp/video.py (18)
  • Slide (37-115)
  • VideoGenerator (319-388)
  • audio_path (47-49)
  • image_path (52-54)
  • video_path (57-59)
  • render_and_save_image (61-79)
  • generate_and_save_audio (81-90)
  • generate_and_save_video (92-115)
  • format_names_for_transcript (133-149)
  • add_intro_slide (151-167)
  • add_sponsors_slide (169-195)
  • add_projects_slide (197-231)
  • add_chapters_slide (233-262)
  • add_releases_slide (264-305)
  • add_thank_you_slide (307-316)
  • append_slide (327-334)
  • generate_video (336-355)
  • merge_videos (357-381)
⏰ 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 frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (1)
backend/Makefile (1)

15-25: Nice cleanup addition; verify whether a named “video” container also needs removal.

Removing nest-backend and nest-video-backend images fits the new workflow, but if the video generator is ever run with a stable --name ..., clean-backend-docker should also docker container rm -f <that-name> to avoid dangling containers.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
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

🤖 Fix all issues with AI agents
In @docker/backend/Dockerfile.video:
- Around line 1-24: Poetry is unpinned and the video extras group isn’t being
installed; update the Dockerfile so the Poetry install is pinned when invoking
python -m pip install poetry (use a specific version) and change the poetry
install invocation (the line running poetry install --no-root --without dev
--without test) to explicitly include the video extras (add --with video) so the
video dependencies (elevenlabs, ffmpeg-python, pillow, pypdfium2, weasyprint)
are installed.
🧹 Nitpick comments (4)
backend/apps/owasp/Makefile (3)

30-44: Consider aligning with the established command execution pattern.

All other targets in this Makefile follow a consistent pattern: @CMD="..." $(MAKE) exec-backend-command. This new target manually orchestrates Docker image builds and container execution, which deviates from the established convention and may impact maintainability.

While the video generation workflow requires a specialized Docker image, consider whether the exec-backend-command infrastructure could be extended to support this use case, or whether a helper target could abstract the build/run logic to maintain consistency.


31-36: Consider extracting image builds to separate targets for efficiency.

The target rebuilds both Docker images on every invocation. While Docker's layer caching helps, extracting the builds into separate prerequisite targets would allow Make to track when rebuilds are actually needed and improve developer experience by avoiding unnecessary build steps.

Example refactor to separate concerns
.PHONY: build-video-images
build-video-images:
	@docker build -f docker/backend/Dockerfile backend/ -t nest-backend:base
	@docker build -f docker/backend/Dockerfile.video backend/ -t nest-video-backend

owasp-generate-community-snapshot-video: build-video-images
	@mkdir -p backend/generated_videos
	@docker run \
		--env-file backend/.env \
		--mount type=bind,src="$(PWD)/backend/generated_videos",dst=/home/owasp/generated_videos \
		--network nest-local_nest-network \
		--rm \
		nest-video-backend \
		python manage.py owasp_generate_community_snapshot_video $(snapshot_key) /home/owasp/generated_videos

30-44: Add echo statement and validate required argument for better UX.

This target lacks the @echo statement present in other targets, reducing visibility during execution. Additionally, the required $(snapshot_key) argument has no validation, which could lead to unclear errors if omitted.

Suggested improvements
 owasp-generate-community-snapshot-video:
+	@echo "Generating community snapshot video for key: $(snapshot_key)"
+	@test -n "$(snapshot_key)" || (echo "Error: snapshot_key is required" && exit 1)
 	@docker build \
 		-f docker/backend/Dockerfile backend/ \
 		-t nest-backend:base
cspell/custom-dict.txt (1)

11-12: Align custom dictionary entries with actual tokens to avoid future cspell noise.

A few additions look like they’re meant to cover WeasyPrint/ffmpeg tokens (good), but ensure the dict matches what actually appears in code/docs (e.g., libpangoft2 vs libpangoft, and any other so:lib… tokens you expect to keep). Otherwise you’ll still get cspell hits or end up over-whitelisting unrelated words.

Also applies to: 39-40, 65-66, 87-92, 107-108, 119-124, 151-152

📜 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 123a57e and 0332ee2.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • backend/apps/owasp/Makefile
  • cspell/custom-dict.txt
  • docker/backend/Dockerfile.video
🧰 Additional context used
🧠 Learnings (4)
📓 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-26T06:09:08.868Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 3041
File: .github/workflows/run-ci-cd.yaml:233-243
Timestamp: 2025-12-26T06:09:08.868Z
Learning: For the OWASP/Nest repository, Redis image versions should remain consistent across all environments (production, staging, local, E2E, and CI/CD E2E tests). When upgrading Redis, update all docker-compose files and CI/CD workflow configurations together to maintain environment parity.

Applied to files:

  • docker/backend/Dockerfile.video
📚 Learning: 2025-10-17T15:25:55.624Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2431
File: infrastructure/providers.tf:1-3
Timestamp: 2025-10-17T15:25:55.624Z
Learning: The infrastructure code in the OWASP/Nest repository (infrastructure/ directory) is intended for quick testing purposes only, not for production deployment.

Applied to files:

  • docker/backend/Dockerfile.video
📚 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
⏰ 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 e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
🔇 Additional comments (2)
backend/apps/owasp/Makefile (1)

41-41: Network name nest-local_nest-network is undefined and will cause the docker run command to fail.

The network name is hard-coded on line 41 but does not match any defined network. The docker-compose/local/compose.yaml defines a network named nest-network, not nest-local_nest-network. Change --network nest-local_nest-network to --network nest-network to align with the actual Docker network created by the local compose configuration.

⛔ Skipped due to learnings
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 3041
File: .github/workflows/run-ci-cd.yaml:233-243
Timestamp: 2025-12-26T06:09:08.868Z
Learning: For the OWASP/Nest repository, Redis image versions should remain consistent across all environments (production, staging, local, E2E, and CI/CD E2E tests). When upgrading Redis, update all docker-compose files and CI/CD workflow configurations together to maintain environment parity.
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 3205
File: docker-compose/local.yaml:32-32
Timestamp: 2026-01-05T16:20:47.532Z
Learning: In the OWASP/Nest repository, feature branches use unique volume name suffixes in docker-compose files to prevent volume clashes across parallel development efforts. For example, the feature/nest-zappa-migration branch uses `-zappa` suffix for all volume names (backend-venv-zappa, cache-data-zappa, db-data-zappa, etc.) to ensure isolated environments when switching between branches.
docker/backend/Dockerfile.video (1)

25-31: Re-check Alpine/WeasyPrint runtime deps + BuildKit requirement.

This relies on WeasyPrint system libs (Line 28-30) and BuildKit-only syntax (RUN --mount, COPY --chmod). If CI/build users don’t have BuildKit enabled, or if the WeasyPrint dependency set is incomplete for your Alpine/base-image combination, the image will fail at build/runtime.

Suggested verifications:

  • Confirm CI always builds this Dockerfile with BuildKit enabled.
  • Confirm the minimal required libs for the WeasyPrint version you’re using are present in this image (either added here or already in nest-backend:base).

@rudransh-shrivastava rudransh-shrivastava marked this pull request as ready for review January 11, 2026 19:47
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
@arkid15r arkid15r enabled auto-merge January 13, 2026 05:25
@sonarqubecloud
Copy link

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.

LGTM 👍

@arkid15r arkid15r added this pull request to the merge queue Jan 13, 2026
Merged via the queue into OWASP:main with commit 4ddce26 Jan 13, 2026
27 checks passed
@rudransh-shrivastava rudransh-shrivastava deleted the feature/community-snapshot-videos branch January 26, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create Community Snapshot Videos Task

2 participants