Skip to content

[Feature] Implemented video upload and streaming functionality#18

Merged
AmanatAliPanhwer merged 38 commits into
mainfrom
feat/10-implement-video-upload-and-streaming-functionality
Nov 16, 2025
Merged

[Feature] Implemented video upload and streaming functionality#18
AmanatAliPanhwer merged 38 commits into
mainfrom
feat/10-implement-video-upload-and-streaming-functionality

Conversation

@AmanatAliPanhwer

@AmanatAliPanhwer AmanatAliPanhwer commented Nov 2, 2025

Copy link
Copy Markdown
Owner

📌 Summary

This pull request introduces a comprehensive video upload, processing, and streaming feature to the blog platform. Administrators can now attach video files to posts, which are then processed in the background and made available for streaming with adaptive quality selection.

This new functionality enhances the richness of the blog's content by allowing for multimedia posts. The implementation focuses on maintaining application responsiveness by offloading heavy transcoding tasks to a background worker and an external service.


🔧 Type of Change

  • 🚀 New feature
  • 🧰 Code refactor
  • 🎨 UI/UX enhancement

🧩 Related Issues

Closes #10


🧠 Changes Introduced

  • Added worker.py: A new background worker module using APScheduler to handle asynchronous video processing.
  • Updated app.py:
    • Integrated the new worker to handle video uploads in the /new and /edit routes.
    • Modified post-retrieval logic to fetch and pass associated video data (URL, status, etc.) to the frontend templates.
  • Updated Database Schema:
    • Introduced a new videos table to store video metadata, including filename, filepath, and processing status.
    • Added a video_id foreign key to the posts table.
  • Updated Frontend Templates:
    • new.html & edit.html: Added a drag-and-drop UI for video uploads.
    • index.html & post.html: Created a Video player to display the videos. The player is dynamically configured based on the video's processing status.
    • layout.html: Added required dependencies for hls.js and the quality levels plugin.
  • Added Frontend JavaScript:
    • static/js/uploader.js: Enhanced to support the video upload UI.
    • static/js/video.js: New script to initialise the Video player, handle HLS streaming for processed videos, and implement a dynamic quality selection menu.

📸 Screenshots

1. New Video Upload Component in New Post & Editor:

Post:

Before After
post-before-video post-after-video

Edit:

Before After
edit-before-video edit-after-video

2. Video Player on a Blog Post:
image


✅ Checklist

  • Code follows the project’s style guide
  • Tested locally without errors
  • Documentation updated (if needed)
  • Commits are descriptive and meaningful

🧾 Additional Notes

  • This implementation relies on an external service (https://ffmpeg.pythonanywhere.com) for video transcoding.
  • A new Supabase storage bucket, blog_videos, is required for this feature to function correctly.
  • The background worker ensures that the main application remains responsive during the time-consuming video processing stage.

Summary by CodeRabbit

  • New Features

    • Video upload, background processing and HLS streaming.
    • In-page video player with quality, speed, volume, fullscreen and Picture-in-Picture controls.
    • Drag-and-drop uploader for images and videos with toast notifications.
  • Style

    • Improved form layout, responsive video UI and refined control styling.
  • Chores

    • CI/workflow adjustments and dependency addition.
    • Expanded end-to-end tests and increased test timeouts for reliability.

@AmanatAliPanhwer AmanatAliPanhwer linked an issue Nov 2, 2025 that may be closed by this pull request
@vercel

vercel Bot commented Nov 2, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
blog Ready Ready Preview Comment Nov 16, 2025 4:26am

@coderabbitai

coderabbitai Bot commented Nov 2, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request adds comprehensive video upload, processing, and streaming capabilities to the blog platform. It introduces a background worker component for asynchronous video transcoding via APScheduler, extends the Flask app to handle video file uploads and metadata tracking, implements drag-and-drop upload UI, adds a client-side video player with HLS.js support, updates the database schema to track video files, and includes end-to-end tests for video workflows. Environment configuration separates image and video bucket names.

Changes

Cohort / File(s) Summary
Workflow & Ignore & Dependencies
.github/workflows/python-app.yml, .gitignore, pyproject.toml
Workflow job renamed from build to test; replaced SUPERBASE_S3_BUCKET_NAME with SUPERBASE_IMAGES_BUCKET_NAME and SUPERBASE_VIDEOS_BUCKET_NAME; added uploads/, outputs/, tmp/, .pytest_cache/ to .gitignore; added apscheduler>=3.11.0 dependency.
Backend - Worker
worker.py
New Worker class: handles temp uploads, Supabase client, APScheduler scheduling, save_file(), queue_file(), _process_file(), and _upload_folder_to_supabase() for async HLS processing workflow and DB status updates.
Backend - App
app.py
Added video support: extended ALLOWED_EXTENSIONS, new BLOG_VIDEOS_BUCKET and worker usage, uploaded_file(filename) route, propagate video_id through create/edit/view flows, fetch videodata from videos table, and enable threaded server mode.
Frontend - Upload UI
static/js/uploader.js, templates/new.html, templates/edit.html
New drag-and-drop and click-to-upload UI for images and videos; uploader.js implements file validation, drag state, placeholder updates and showToast() notifications; templates replace plain inputs with upload boxes and include the script.
Frontend - Video Player UI & Assets
templates/layout.html, templates/components/video_player.html, static/js/video_player.js, static/js/hls.min.js, static/css/video_controls.css, static/css/style.css
Added video_player macro and player assets (Hls.js, video_player.js, video_controls.css); comprehensive per-container video controls (play/pause, progress, volume, quality, speed, PiP, fullscreen), HLS integration, CSS for uploader/player and responsive controls.
Templates - Rendering
templates/index.html, templates/post.html
Conditional rendering of the video player when post video metadata exists; minor template formatting adjustments.
Tests - Timeouts & Video E2E
tests/conftest.py, tests/test_blog.py, tests/test_edit_post.py, tests/test_viewer.py, tests/test_video.py, tests/test_video_player.py
Increased Playwright timeouts to 600000ms in many tests/fixtures; added video-focused test modules with helpers and end-to-end scenarios for upload, processing polling, edit flows, and player control assertions (volume, fullscreen).
Docs
PROJECT_DESCRIPTION.md, README.md, ffmpeg_microservice.md
Documented the new video architecture, worker-based transcoding, HLS playback flow, FFmpeg microservice example, and added development doc links.

Sequence Diagram(s)

sequenceDiagram
    participant User as Admin User
    participant Frontend as Frontend (Browser)
    participant Flask as Flask App
    participant Worker as Background Worker
    participant Supabase as Supabase Storage
    participant FFmpeg as FFmpeg Service
    participant Database as Database

    rect rgba(200,150,255,0.12)
    note over Frontend: Video Upload Flow
    User->>Frontend: Select & upload video
    Frontend->>Flask: POST /new or /edit (video file)
    end

    rect rgba(150,200,255,0.12)
    note over Flask: Backend Processing
    Flask->>Supabase: Upload raw video file
    Supabase-->>Flask: File stored, path returned
    Flask->>Database: Insert video record (status: pending)
    Flask->>Worker: Queue processing job (video_id)
    Flask-->>Frontend: Return response with video_id
    end

    rect rgba(150,255,150,0.12)
    note over Worker: Async Transcoding
    Worker->>Database: Retrieve video metadata
    Worker->>Supabase: Download uploaded video
    Supabase-->>Worker: Video file
    Worker->>FFmpeg: Send for HLS transcoding
    FFmpeg-->>Worker: Return .m3u8 & .ts files
    Worker->>Supabase: Upload HLS manifest & segments
    Worker->>Database: Update video record (status: processed, filepath)
    end

    rect rgba(255,200,150,0.12)
    note over Frontend: Video Playback
    User->>Frontend: Navigate to post with video
    Frontend->>Flask: GET /post/{id}
    Flask->>Database: Fetch post with video_id
    Flask->>Database: Fetch video metadata (m3u8 URL)
    Flask-->>Frontend: Render post with video player
    Frontend->>Frontend: Initialize Hls.js with .m3u8 URL
    Frontend->>Supabase: Stream video segments
    Supabase-->>Frontend: HLS segments (.ts files)
    Frontend->>User: Play video with adaptive bitrate
    end
Loading
sequenceDiagram
    participant UI as Upload UI
    participant Input as File Input
    participant Uploader as uploader.js
    participant Toast as Toast Notif.
    participant FormData as FormData

    rect rgba(255,200,200,0.12)
    note over UI: Click-to-Upload Flow
    UI->>Input: User clicks upload box
    Input->>Uploader: Change event triggered
    Uploader->>UI: Update placeholder with filename
    end

    rect rgba(200,255,200,0.12)
    note over UI: Drag-and-Drop Flow
    UI->>Uploader: dragover event
    Uploader->>UI: Add visual drag state (highlight)
    UI->>Uploader: drop event with files
    Uploader->>Uploader: Validate MIME type
    alt Valid file type
        Uploader->>Input: Set to file input
        Uploader->>UI: Update placeholder
    else Invalid file type
        Uploader->>Toast: Show warning toast
    end
    Uploader->>UI: Remove drag state (dragleave)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • worker.py: Supabase interactions, scheduler/job uniqueness, temp file lifecycle, error paths, and HTTP interaction with FFmpeg service.
  • app.py: Correct propagation and transactional safety of video_id, routes serving uploaded files, and integration with worker API.
  • static/js/video_player.js & hls integration: manifest parsing, quality menu behavior, event listener cleanup, and cross-browser fallbacks.
  • Tests: E2E polling timeouts and CI flakiness; cleanup of created test artifacts.
  • Templates/CSS: z-index and responsive overlay interactions for video controls and uploader drag states.

Poem

🐇 I hopped a file into the queue,

ffmpeg hummed and stitched it true,
Segments danced in m3u8 light,
The player sang at day and night,
Hops and streams — a coder's delight.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.49% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the primary change: implementing video upload and streaming functionality for the blog platform.
Description check ✅ Passed The description comprehensively covers the pull request purpose, type of change, related issues, changes introduced, screenshots, and checklists following the template structure.
Linked Issues check ✅ Passed The implementation addresses all core requirements from issue #10: video uploads via frontend, backend processing via worker, HLS streaming, database schema updates, video player integration, and async processing.
Out of Scope Changes check ✅ Passed All code changes are directly related to implementing video upload and streaming functionality as specified in issue #10; no unrelated or out-of-scope modifications detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/10-implement-video-upload-and-streaming-functionality

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.

@AmanatAliPanhwer

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 3, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app.py (1)

198-262: Propagate video metadata in the filter route.

home() now returns (id, title, Markup(content), image, formatted_timestamp, videodata) for every post. filter_posts() still emits only five tuple elements and never hydrates videodata. As soon as templates rely on post.video (or tuple index 5) after this PR, any filtered view breaks—either the tuple unpacking fails or the player never gets the HLS manifest. We need to select video_id and build the same metadata block here.

Apply this diff to align both code paths:

-    select_fields = f"id, title, content, image, {TIMESTAMP_FIELD}" if TIMESTAMP_FIELD else "id, title, content, image"
+    select_fields = (
+        f"id, title, content, image, {TIMESTAMP_FIELD}, video_id"
+        if TIMESTAMP_FIELD
+        else "id, title, content, image, video_id"
+    )
...
-        posts.append((post.get('id'), post.get('title'), Markup(post.get('content', '')), post.get('image'), formatted_ts))
+        videodata = None
+        video_id = post.get('video_id')
+        if video_id:
+            try:
+                video_resp = (
+                    supabase_client.table('videos')
+                    .select('filepath', 'filename', 'status')
+                    .eq('id', video_id)
+                    .single()
+                    .execute()
+                )
+                video_record = video_resp.data
+                if video_record:
+                    videodata = {
+                        'id': video_id,
+                        'filename': video_record.get('filename'),
+                        'filepath': video_record.get('filepath'),
+                        'status': video_record.get('status'),
+                        'url': video_record.get('filepath'),
+                    }
+            except Exception as e:
+                print(f"Error fetching video info for video_id={video_id}: {e}")
+
+        posts.append(
+            (
+                post.get('id'),
+                post.get('title'),
+                Markup(post.get('content', '')),
+                post.get('image'),
+                formatted_ts,
+                videodata,
+            )
+        )
🧹 Nitpick comments (5)
templates/index.html (1)

63-77: Clarify unusual <a> tag wrapping without href attribute.

The video player is wrapped in an <a> tag (line 63) but no href is present. This is semantically odd—it should either be a <div> or the <a> should have an href to maintain accessibility. If the intent is to prevent the post link click, the onclick="event.stopPropagation()" on the inner div should suffice without wrapping in <a>.

-            <a>
-                <div class="player" onclick="event.stopPropagation();">
+            <div class="player" onclick="event.stopPropagation();">

Remove the wrapper </a> tag on line 77 as well.

templates/edit.html (1)

5-9: Move inline CSS to static stylesheet.

The inline <style> block for .vjs-tech padding should be moved to the main CSS file (static/css/style.css) to keep concerns separated and avoid inline styles. The !important flag can typically be replaced with proper CSS specificity.

-<style>
-    .vjs-tech {
-        padding-top: 0px !important;
-    }
-</style>

Add to static/css/style.css:

.vjs-tech {
    padding-top: 0 !important;  /* Required to override Video.js defaults for proper aspect ratio */
}
.github/workflows/python-app.yml (1)

21-22: Update workflow to use preferred environment variable names.

The workflow defines SUPERBASE_IMAGES_BUCKET_NAME and SUPERBASE_VIDEOS_BUCKET_NAME, but app.py prefers BLOG_IMAGES_BUCKET and BLOG_VIDEOS_BUCKET (checking these first with fallback to the SUPERBASE_* names). While the current configuration works through fallback handling, updating the workflow to use the preferred variable names improves consistency and clarity. Change lines 21-22 to use BLOG_IMAGES_BUCKET and BLOG_VIDEOS_BUCKET.

pyproject.toml (1)

9-9: Consider using a version constraint that prevents unexpected major version bumps.

The constraint >=3.11.0 allows APScheduler 4.x (if released), which could introduce breaking changes. APScheduler 3.11.0 has breaking changes including dropped support for Python 3.6/3.7 and migration from pytz to ZoneInfo. Use ~=3.11.0 (equivalent to >=3.11.0,<4.0.0) to avoid surprises and review the migration notes if you upgrade from earlier versions.

templates/post.html (1)

17-31: Consider explicit escaping for video data attributes; Flask's autoescape already protects these, but |tojson is a defensive best practice.

Flask enables Jinja2 autoescape by default for .html templates, so the video data attributes ({{ post.video.url }}, etc.) are already HTML-escaped. However, using the |tojson filter would make escaping explicit and is a safer, more portable practice:

-            data-id="{{ post.video.id }}"
-            data-filename="{{ post.video.filename }}"
-            data-filepath="{{ post.video.filepath }}"
-            data-status="{{ post.video.status }}"
-            data-url="{{ post.video.url }}"
+            data-id="{{ post.video.id | tojson }}"
+            data-filename="{{ post.video.filename | tojson }}"
+            data-filepath="{{ post.video.filepath | tojson }}"
+            data-status="{{ post.video.status | tojson }}"
+            data-url="{{ post.video.url | tojson }}"

Verify that autoescape remains enabled in production and no custom Jinja2 configuration disables it globally.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between def9f69 and 7a0d430.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • .github/workflows/python-app.yml (1 hunks)
  • .gitignore (1 hunks)
  • app.py (14 hunks)
  • pyproject.toml (1 hunks)
  • static/css/style.css (3 hunks)
  • static/js/uploader.js (1 hunks)
  • static/js/video.js (1 hunks)
  • templates/edit.html (2 hunks)
  • templates/index.html (2 hunks)
  • templates/layout.html (1 hunks)
  • templates/new.html (2 hunks)
  • templates/post.html (2 hunks)
  • tests/test_video.py (1 hunks)
  • worker.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.3)
worker.py

9-9: Probable insecure usage of temporary file or directory: "/tmp"

(S108)


19-19: Avoid specifying long messages outside the exception class

(TRY003)


59-59: Do not catch blind exception: Exception

(BLE001)


65-65: Avoid specifying long messages outside the exception class

(TRY003)


67-67: Local variable filepath is assigned to but never used

Remove assignment to unused variable filepath

(F841)


76-76: Probable use of requests call without timeout

(S113)


81-81: Do not catch blind exception: Exception

(BLE001)


83-83: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


83-83: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_video.py

14-14: Avoid specifying long messages outside the exception class

(TRY003)

app.py

129-129: Do not catch blind exception: Exception

(BLE001)


160-160: Do not catch blind exception: Exception

(BLE001)


164-164: Unsafe use of markupsafe.Markup detected

(S704)


392-392: Do not catch blind exception: Exception

(BLE001)


500-500: Do not catch blind exception: Exception

(BLE001)


530-530: Do not use bare except

(E722)


587-587: Do not catch blind exception: Exception

(BLE001)


593-593: Unsafe use of markupsafe.Markup detected

(S704)

🔇 Additional comments (5)
.gitignore (1)

6-10: LGTM!

The restructured ignores correctly support the new upload and processing workflow at the repository root level.

templates/layout.html (2)

17-17: Verify local video.js script for security and functionality.

The script at static/js/video.js is not provided in this review. Ensure it properly initializes Video.js players, handles HLS streams safely (no XSS via data attributes), and implements quality selection securely. This script should be reviewed separately before merge.


14-19: Consider adding Subresource Integrity (SRI) hashes to external CDN resources as a security best practice.

While SRI is a W3C specification recommended as a best-practice whenever libraries are loaded from a third-party source, note that the official Video.js documentation itself does not demonstrate SRI usage for vjs.zencdn.net resources. If you choose to implement SRI:

  • For the quality-levels plugin from jsDelivr, SRI is well-supported and recommended.
  • For Video.js from vjs.zencdn.net, you would need to verify with the maintainers whether SRI hashes are available and supported, since the official documentation does not include them.
  • The CDN must support Cross-Origin Resource Sharing (CORS), enforced via the crossorigin attribute.

Generate hashes using tools like the SRI Hash Generator (srihash.org) or curl, and update the integrity attributes when dependencies are upgraded.

templates/new.html (1)

15-24: Verify uploader.js for secure file handling and CSRF protection.

The drag-and-drop upload boxes lack client-side file validation (e.g., size limits, format checks beyond accept attribute). Ensure uploader.js implements proper validation and that form submissions include CSRF tokens. Additionally, verify the backend enforces file size and type restrictions.

templates/edit.html (1)

30-45: The review comment is incorrect—no XSS vulnerability exists here.

Jinja2 enables autoescape by default for .html files, which automatically escapes HTML special characters (<, >, &, ", ') in attribute values. The data attributes in edit.html and post.html are already protected. Additionally:

  • index.html does not contain the same video data attributes (the review claim is inaccurate)
  • video.js reads these values safely into variables and passes them to the Video.js API, not directly into the DOM
  • Applying |tojson would be incorrect—it would add extra quotes and break the attribute syntax (e.g., data-id=""test"")

No changes are needed.

Comment thread static/js/uploader.js
Comment thread templates/index.html
Comment thread tests/test_video.py
Comment thread tests/test_video.py Outdated
Comment thread worker.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
    - f-string quoting syntax error
Handle:
    - processing failures and add a request timeout
    - Apply |tojson filter to video data attributes to prevent XSS via attribute breakout.
@AmanatAliPanhwer AmanatAliPanhwer added enhancement New feature or request help wanted Extra attention is needed labels Nov 10, 2025
@AmanatAliPanhwer

AmanatAliPanhwer commented Nov 15, 2025

Copy link
Copy Markdown
Owner Author
  • Add the docs for the ffmpeg thing

@AmanatAliPanhwer AmanatAliPanhwer marked this pull request as ready for review November 15, 2025 16:43
@AmanatAliPanhwer

Copy link
Copy Markdown
Owner Author

The PiP button is not working

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
worker.py (1)

72-72: Missing timeout on requests.post call.

Despite a previous review comment flagging this issue, the requests.post call still lacks a timeout parameter. This can cause the scheduler thread to hang indefinitely if the external service is slow or unresponsive.

Apply this diff:

             with open(video_file_path, 'rb') as video:
-                res = requests.post(f'https://ffmpeg.pythonanywhere.com/upload/{file_id}', files={'file': video})
+                res = requests.post(
+                    f'https://ffmpeg.pythonanywhere.com/upload/{file_id}',
+                    files={'file': video},
+                    timeout=300  # 5 minutes for video processing
+                )
🧹 Nitpick comments (11)
static/css/video_controls.css (1)

90-103: Add keyboard-visible focus styles and avoid hover-only volume control

Buttons and sliders remove outlines and rely on hover (outline: none;, hover-only volume expansion). This makes focus hard to see and the volume slider hard to reach for keyboard-only or touch users.

Consider:

  • Adding :focus-visible styles for .controls-bar button, .progress-bar, and .volume-slider with clear outlines/glows.
  • Making the volume slider discoverable without requiring :hover (e.g., always a minimal width, larger on hover).

Also applies to: 173-199

PROJECT_DESCRIPTION.md (1)

23-27: Fix unordered list indentation to satisfy markdownlint (MD007)

Static analysis reports MD007 (ul-indent) on several list items in this file. The nested bullet items under “Video Upload and Streaming”, “Supabase”, and “JavaScript” are indented with 4 spaces instead of the expected 2.

To keep CI green and consistent with the rest of the doc, adjust those sub-items to use the same indentation style as other lists (typically 2 spaces under their parent *).

Also applies to: 41-43, 48-53

static/js/video_player.js (2)

107-127: Improve fullscreen compatibility with vendor-prefixed APIs

The current implementation uses only the standard requestFullscreen() / exitFullscreen() pair, which works on modern browsers but fails on older ones. For cross-browser support, vendor-prefixed fallbacks are recommended for older browsers (webkit, moz, ms).

Update toggleFullscreen() to check for vendor-prefixed methods:

function toggleFullscreen() {
    const request =
        container.requestFullscreen ||
        container.webkitRequestFullscreen ||
        container.mozRequestFullScreen ||
        container.msRequestFullscreen;

    const exit =
        document.exitFullscreen ||
        document.webkitExitFullscreen ||
        document.mozCancelFullScreen ||
        document.msExitFullscreen;

    if (!document.fullscreenElement && !document.webkitFullscreenElement) {
        request && request.call(container);
    } else if (exit) {
        exit.call(document);
    }
}

Also update updateFullscreenButton() to check the vendor-prefixed document.webkitFullscreenElement when determining state.


214-275: Adding a window.Hls guard is not recommended by hls.js documentation

The current hls.js documentation recommends the pattern if (Hls.isSupported()) without an explicit guard for the global. While your concern about script load failures is valid from a defensive coding standpoint, the official hls.js pattern does not include this check.

If script load failures are a concern for your application, adding window.Hls && is a reasonable defensive practice, but it's not required per the official documentation.

tests/test_edit_post.py (1)

8-9: Extract timeout constant and reconsider excessive 600-second default across all tests

Playwright's recommended approach uses global defaults (30–120 seconds for E2E tests), but the codebase currently hard-codes timeout=600000 (10 minutes) across 123+ test locations, including fixtures in conftest.py. This pattern:

  • Masks slow or hung operations (10 min before failure detection)
  • Duplicates a magic literal throughout the suite
  • Diverges from established best practices

Recommended actions:

  1. Define a shared constant (e.g., E2E_TIMEOUT = 600_000) in conftest.py or a dedicated config module, and apply it throughout (test_edit_post.py, test_video.py, test_video_player.py, test_viewer.py, test_blog.py, and the existing fixtures).
  2. Evaluate whether a lower timeout (60–120 seconds) is sufficient for non-video flows in test_edit_post.py, test_viewer.py, and test_blog.py; reserve the longer timeout only for test_video.py and test_video_player.py where video processing genuinely requires it.
  3. Consider using browser_context.page.set_default_timeout() or Playwright test config to apply global timeouts consistently, then override per-call only for justified slow operations.
tests/test_viewer.py (1)

10-107: Consider reducing the 600000ms timeout to a more reasonable value.

The 10-minute timeout applied across all navigation and assertion operations is excessive for most test scenarios. Standard Playwright operations typically complete within 30-60 seconds. While video processing may require extended wait times, applying this uniformly could mask performance issues or allow tests to hang unnecessarily.

Consider:

  • Using shorter timeouts (30000-60000ms) for basic navigation and UI assertions
  • Reserving longer timeouts only for video processing-specific operations
  • Using explicit polling loops with progress indicators for long-running operations
tests/conftest.py (1)

64-72: Excessive timeout for login/logout operations.

Login and logout are fast operations that should complete within seconds. The 600000ms (10-minute) timeout is unnecessary and may allow tests to hang if authentication services are slow or unresponsive.

Recommend using 30000-60000ms for these operations to ensure tests fail fast.

tests/test_blog.py (1)

10-156: Reconsider the uniform 600000ms timeout across all test operations.

While some operations (like video processing) may justify extended timeouts, standard blog operations such as page navigation, post creation, and UI interactions typically complete in seconds. Applying a 10-minute timeout uniformly:

  • Masks performance degradation
  • Delays test feedback when failures occur
  • May hide flakiness or infrastructure issues

Recommend differentiating timeout values based on operation type.

tests/test_video_player.py (1)

7-15: Simplify the error message per static analysis guidance.

The static analysis tool flags the long error message string. Consider defining a shorter message or using a custom exception class.

Apply this diff:

     if not os.path.exists(source_filepath):
-        raise FileNotFoundError(f"Test video file not found at {source_filepath}. Please ensure it exists.")
+        raise FileNotFoundError(f"Test video not found: {source_filepath}")
worker.py (1)

9-9: Consider using tempfile.mkdtemp() for temporary directory creation.

The hardcoded /tmp path is flagged as a potential security issue. While it may work for your deployment environment, using Python's tempfile module is more secure and cross-platform.

+import tempfile
+
 class Worker:
     def __init__(self, SUPABASE_KEY, SUPABASE_URL, videos_bucket='video'):
-        self.upload_folder = os.path.join("/tmp", 'uploads')
+        self.upload_folder = tempfile.mkdtemp(prefix='video_uploads_')
         os.makedirs(self.upload_folder, exist_ok=True)
tests/test_video.py (1)

54-62: Extract duplicate polling logic to a helper function.

The video processing polling logic appears twice in this file (lines 54-62 and lines 111-119) with nearly identical code. Extracting this pattern to a reusable helper would improve maintainability and reduce duplication.

Consider adding a helper function like this at the module level:

def poll_video_processing(page: Page, post_url: str, timeout_seconds: int = 120) -> bool:
    """
    Polls the video player status on a post page until processing completes.
    Returns True if processing completed within timeout, False otherwise.
    """
    for i in range(timeout_seconds):
        page.goto(post_url, timeout=600000)
        player = page.locator(".video-player")
        status = player.get_attribute("data-status")
        if status == "processed":
            return True
        time.sleep(1)
        print(f"Polling video status... Current: {status} (Attempt {i+1}/{timeout_seconds})")
    return False

Then replace both polling blocks:

# In test_create_post_with_video (lines 54-64)
processing_complete = poll_video_processing(page, post_url)
assert processing_complete, "Video processing did not complete within the timeout period."

# In test_edit_post_to_add_video (lines 111-121)
processing_complete = poll_video_processing(page, f"{flask_app_url}/post/{post_id}")
assert processing_complete, "Video processing did not complete within the timeout period after edit."

Also applies to: 111-119

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b17a56b and 1360433.

⛔ Files ignored due to path filters (10)
  • static/js/hls.min.js is excluded by !**/*.min.js
  • static/svg/fullscreen-exit.svg is excluded by !**/*.svg
  • static/svg/fullscreen.svg is excluded by !**/*.svg
  • static/svg/gear.svg is excluded by !**/*.svg
  • static/svg/pause.svg is excluded by !**/*.svg
  • static/svg/pip.svg is excluded by !**/*.svg
  • static/svg/play.svg is excluded by !**/*.svg
  • static/svg/volume-mute.svg is excluded by !**/*.svg
  • static/svg/volume-up.svg is excluded by !**/*.svg
  • tests/assets/test_video.mp4 is excluded by !**/*.mp4
📒 Files selected for processing (18)
  • .github/workflows/python-app.yml (2 hunks)
  • .gitignore (1 hunks)
  • PROJECT_DESCRIPTION.md (3 hunks)
  • static/css/style.css (4 hunks)
  • static/css/video_controls.css (1 hunks)
  • static/js/video_player.js (1 hunks)
  • templates/components/video_player.html (1 hunks)
  • templates/edit.html (2 hunks)
  • templates/index.html (2 hunks)
  • templates/layout.html (1 hunks)
  • templates/post.html (3 hunks)
  • tests/conftest.py (1 hunks)
  • tests/test_blog.py (3 hunks)
  • tests/test_edit_post.py (4 hunks)
  • tests/test_video.py (1 hunks)
  • tests/test_video_player.py (1 hunks)
  • tests/test_viewer.py (6 hunks)
  • worker.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • .gitignore
  • templates/index.html
  • templates/edit.html
  • .github/workflows/python-app.yml
🧰 Additional context used
🧬 Code graph analysis (5)
tests/test_blog.py (1)
tests/conftest.py (3)
  • page (54-59)
  • flask_app_url (16-51)
  • admin_logged_in_page (63-72)
tests/test_video.py (1)
tests/conftest.py (3)
  • admin_logged_in_page (63-72)
  • flask_app_url (16-51)
  • page (54-59)
tests/test_edit_post.py (1)
tests/conftest.py (3)
  • page (54-59)
  • flask_app_url (16-51)
  • admin_logged_in_page (63-72)
tests/test_video_player.py (1)
tests/conftest.py (3)
  • admin_logged_in_page (63-72)
  • flask_app_url (16-51)
  • page (54-59)
tests/test_viewer.py (2)
tests/conftest.py (2)
  • page (54-59)
  • flask_app_url (16-51)
static/js/script.js (1)
  • viewer (90-95)
🪛 markdownlint-cli2 (0.18.1)
PROJECT_DESCRIPTION.md

24-24: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


25-25: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


26-26: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


41-41: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


42-42: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


49-49: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


50-50: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


51-51: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


52-52: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🪛 Ruff (0.14.4)
tests/test_video.py

12-12: Avoid specifying long messages outside the exception class

(TRY003)

worker.py

9-9: Probable insecure usage of temporary file or directory: "/tmp"

(S108)


18-18: Avoid specifying long messages outside the exception class

(TRY003)


55-55: Do not catch blind exception: Exception

(BLE001)


61-61: Avoid specifying long messages outside the exception class

(TRY003)


72-72: Probable use of requests call without timeout

(S113)


77-77: Abstract raise to an inner function

(TRY301)


77-77: Avoid specifying long messages outside the exception class

(TRY003)


80-80: Do not catch blind exception: Exception

(BLE001)


82-82: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


82-82: Avoid specifying long messages outside the exception class

(TRY003)

tests/test_video_player.py

13-13: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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: test
🔇 Additional comments (7)
templates/layout.html (1)

10-19: LGTM! Video player assets properly integrated.

The addition of video-related CSS and JavaScript files to the base layout is correct and follows the existing pattern. The assets are loaded using url_for, ensuring proper URL generation.

static/css/style.css (2)

28-28: Good refactoring with CSS variables and flexbox.

The introduction of the --input-margin variable and conversion of the form to flexbox layout improves maintainability and consistency.

Also applies to: 144-146, 153-153


630-723: Comprehensive video player and uploader styling.

The new styles for video player controls, quality menu, and drag-and-drop uploader are well-structured and consistent with the existing design system. Good use of CSS variables for theming.

tests/test_video_player.py (2)

43-56: Good polling pattern for video processing.

The 2-minute polling loop with 1-second intervals is appropriate for waiting on asynchronous video processing. Clear progress messages help with debugging.


64-103: Comprehensive volume and mute testing.

The test properly validates mute/unmute behavior, volume slider interactions, and icon state changes. Good use of JS property assertions.

worker.py (1)

69-82: Good error handling and status tracking.

The try-except block properly:

  • Updates status to 'processing' before starting
  • Handles failures by setting status to 'failed'
  • Cleans up uploaded files
  • Re-raises with context

The fallback to original filepath when processing fails (line 76) ensures the video remains accessible even if transcoding fails.

templates/components/video_player.html (1)

1-59: LGTM! Video player component with all dependencies verified.

The video player macro is well-designed with proper data attributes, accessibility labels, and comprehensive controls. All required SVG icon dependencies have been verified to exist in static/svg/.

Comment thread static/js/video_player.js
Comment thread templates/post.html
Comment thread tests/test_video.py
Comment thread tests/test_video.py Outdated
@AmanatAliPanhwer

Copy link
Copy Markdown
Owner Author

@coderabbitai generate docstrings

@coderabbitai

coderabbitai Bot commented Nov 16, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai

coderabbitai Bot commented Nov 16, 2025

Copy link
Copy Markdown
Contributor

Warning

Docstrings generation - IN PROGRESS

Generating docstrings for this pull request

@coderabbitai

coderabbitai Bot commented Nov 16, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
ffmpeg_microservice.md (1)

37-40: Optional: Address markdown linting issues.

The documentation is excellent and comprehensive. For improved consistency with markdown standards, consider:

  1. List indentation (MD007): Nested list items use 4-space indentation; markdownlint expects 2 spaces. Affects lines 37-40, 53, 55, 57-58, 69-70, and 82-83.

  2. Missing code fence language (MD040): The .env example at line 102 should specify a language identifier:

-```
+```env
 SUPABASE_URL=https://your-project-ref.supabase.co
 SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key
 BLOG_VIDEOS_BUCKET=blog_videos

Also applies to: 53-58, 69-70, 82-83, 102-106

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1360433 and 342e820.

📒 Files selected for processing (5)
  • README.md (1 hunks)
  • ffmpeg_microservice.md (1 hunks)
  • static/js/video_player.js (1 hunks)
  • templates/post.html (3 hunks)
  • tests/test_video.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • README.md
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_video.py (1)
tests/conftest.py (3)
  • admin_logged_in_page (63-72)
  • flask_app_url (16-51)
  • page (54-59)
🪛 markdownlint-cli2 (0.18.1)
ffmpeg_microservice.md

37-37: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


38-38: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


39-39: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


40-40: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


53-53: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


55-55: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


57-57: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


58-58: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


69-69: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


70-70: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


82-82: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


83-83: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


102-102: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.4)
tests/test_video.py

12-12: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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: test
🔇 Additional comments (3)
templates/post.html (1)

17-20: LGTM! Video rendering implementation looks solid.

The conditional video player rendering is cleanly implemented. The macro import and invocation pattern follows best practices, and the logic correctly guards against rendering when post.video is absent.

static/js/video_player.js (1)

1-359: Excellent implementation of the video player!

The video player implementation is comprehensive and robust:

  • HLS support: Properly detects Hls.js availability, handles manifest parsing, quality switching, and gracefully falls back to native HLS (Safari) or direct sources.
  • Error handling: Covers network and media errors with appropriate recovery strategies.
  • Controls: Full set of playback controls (play/pause, seek, volume, fullscreen, quality, speed, PiP) with proper state management.
  • UX: Auto-hide controls, hover interactions, and visual feedback enhance usability.
  • PiP: The previously flagged null-dereference issue has been correctly resolved with proper feature detection.
tests/test_video.py (1)

6-134: Comprehensive end-to-end video tests!

The test suite provides excellent coverage of video workflows:

  • test_create_post_with_video: Validates the full lifecycle from upload through processing to final HLS playback, with appropriate polling and timeout handling.
  • test_edit_post_to_add_video: Tests the edit path, ensuring videos can be added to existing posts and processed correctly.
  • Polling strategy: The 120-second polling loops (lines 56-64, 113-122) are reasonable for async video processing and include helpful debug logging.
  • Assertions: Clear, descriptive messages explain expected behavior at each step.
  • Cleanup: Proper cleanup ensures test isolation.

All previously flagged issues (docstring, f-string quoting, assertion messages) have been resolved.

@AmanatAliPanhwer AmanatAliPanhwer merged commit 0255bbf into main Nov 16, 2025
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Implement Video Upload and Streaming Functionality

1 participant