Conversation
- Changed preset executable size from 8MB to 10MB in README and build.py - Added encoder priority configuration for HEVC and H264 in videocompress.py - Implemented a dynamic encoder selection based on OS and codec type - Created a GitHub Actions workflow for deploying the architecture page - Added index.html with a responsive design and interactive features for the Video Compression Architecture Explorer
Updated instructions for video compression usage and added links.
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the video compression tool by introducing an intelligent autoscaling feature that dynamically adjusts video parameters to preserve visual quality even when targeting very small file sizes. The build process has been refined to support multiple codecs (HEVC and H.264) and includes a more robust hardware encoder detection and prioritization system across different operating systems. Additionally, a new web-based architectural overview has been added to provide users with a deeper understanding of the tool's internal workings and logic. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Ignored Files
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant 'autoscale' feature that intelligently adjusts video resolution and framerate to maintain quality while meeting target file sizes. It also adds support for H.264 alongside HEVC, making the tool more versatile. A new interactive HTML page has been added to document the architecture, which is a great addition. My review focuses on a few critical bugs related to the new scaling logic, particularly for NVENC, and some medium-severity improvements for robustness and correctness.
| cmd_a1 = base + ["-ss", "0", "-to", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | ||
| "-b:v", f"{brs[0]}k", "-maxrate:v", f"{brs[0]}k", "-bufsize:v", f"{brs[0]*2}k", | ||
| "-pass", "1", "-passlogfile", log_a, "-f", "null", "NUL" if os.name=='nt' else "/dev/null"] | ||
|
|
||
| cmd_b1 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", "hevc_nvenc", "-preset", "p5", | ||
| cmd_b1 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | ||
| "-b:v", f"{brs[1]}k", "-maxrate:v", f"{brs[1]}k", "-bufsize:v", f"{brs[1]*2}k", | ||
| "-pass", "1", "-passlogfile", log_b, "-f", "null", "NUL" if os.name=='nt' else "/dev/null"] |
There was a problem hiding this comment.
The vf_args list, which contains the crucial scaling and FPS filters, is not being applied to the pass 1 encoding commands for NVENC. This is a critical bug as it means the auto-scaling feature will not work for NVENC encodes. You need to add + vf_args to the command construction for both cmd_a1 and cmd_b1.
| cmd_a1 = base + ["-ss", "0", "-to", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | |
| "-b:v", f"{brs[0]}k", "-maxrate:v", f"{brs[0]}k", "-bufsize:v", f"{brs[0]*2}k", | |
| "-pass", "1", "-passlogfile", log_a, "-f", "null", "NUL" if os.name=='nt' else "/dev/null"] | |
| cmd_b1 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", "hevc_nvenc", "-preset", "p5", | |
| cmd_b1 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | |
| "-b:v", f"{brs[1]}k", "-maxrate:v", f"{brs[1]}k", "-bufsize:v", f"{brs[1]*2}k", | |
| "-pass", "1", "-passlogfile", log_b, "-f", "null", "NUL" if os.name=='nt' else "/dev/null"] | |
| cmd_a1 = base + ["-ss", "0", "-to", str(split_time), "-i", input_path] + vf_args + ["-c:v", active_encoder, "-preset", "p5", | |
| "-b:v", f"{brs[0]}k", "-maxrate:v", f"{brs[0]}k", "-bufsize:v", f"{brs[0]*2}k", | |
| "-pass", "1", "-passlogfile", log_a, "-f", "null", "NUL" if os.name=='nt' else "/dev/null"] | |
| cmd_b1 = base + ["-ss", str(split_time), "-i", input_path] + vf_args + ["-c:v", active_encoder, "-preset", "p5", | |
| "-b:v", f"{brs[1]}k", "-maxrate:v", f"{brs[1]}k", "-bufsize:v", f"{brs[1]*2}k", | |
| "-pass", "1", "-passlogfile", log_b, "-f", "null", "NUL" if os.name=='nt' else "/dev/null"] |
| cmd_a2 = base + ["-ss", "0", "-to", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | ||
| "-b:v", f"{brs[0]}k", "-maxrate:v", f"{brs[0]}k", "-bufsize:v", f"{brs[0]*2}k", | ||
| "-pass", "2", "-passlogfile", log_a, "-c:a", "copy", str(p1_path)] | ||
| "-pass", "2", "-passlogfile", log_a] | ||
|
|
||
| cmd_b2 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", "hevc_nvenc", "-preset", "p5", | ||
| cmd_b2 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | ||
| "-b:v", f"{brs[1]}k", "-maxrate:v", f"{brs[1]}k", "-bufsize:v", f"{brs[1]*2}k", | ||
| "-pass", "2", "-passlogfile", log_b, "-c:a", "copy", str(p2_path)] | ||
| "-pass", "2", "-passlogfile", log_b] |
There was a problem hiding this comment.
Similar to pass 1, the vf_args containing the scaling filters are not being applied to the pass 2 encoding commands for NVENC. This needs to be corrected for the auto-scaling feature to function properly.
| cmd_a2 = base + ["-ss", "0", "-to", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | |
| "-b:v", f"{brs[0]}k", "-maxrate:v", f"{brs[0]}k", "-bufsize:v", f"{brs[0]*2}k", | |
| "-pass", "2", "-passlogfile", log_a, "-c:a", "copy", str(p1_path)] | |
| "-pass", "2", "-passlogfile", log_a] | |
| cmd_b2 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", "hevc_nvenc", "-preset", "p5", | |
| cmd_b2 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | |
| "-b:v", f"{brs[1]}k", "-maxrate:v", f"{brs[1]}k", "-bufsize:v", f"{brs[1]*2}k", | |
| "-pass", "2", "-passlogfile", log_b, "-c:a", "copy", str(p2_path)] | |
| "-pass", "2", "-passlogfile", log_b] | |
| cmd_a2 = base + ["-ss", "0", "-to", str(split_time), "-i", input_path] + vf_args + ["-c:v", active_encoder, "-preset", "p5", | |
| "-b:v", f"{brs[0]}k", "-maxrate:v", f"{brs[0]}k", "-bufsize:v", f"{brs[0]*2}k", | |
| "-pass", "2", "-passlogfile", log_a] | |
| cmd_b2 = base + ["-ss", str(split_time), "-i", input_path] + vf_args + ["-c:v", active_encoder, "-preset", "p5", | |
| "-b:v", f"{brs[1]}k", "-maxrate:v", f"{brs[1]}k", "-bufsize:v", f"{brs[1]*2}k", | |
| "-pass", "2", "-passlogfile", log_b] |
|
|
||
| v_stream = res['streams'][0] | ||
| width = int(v_stream.get('width', 0)) | ||
| height = int(v_stream.get('height', 0)) |
There was a problem hiding this comment.
There's a potential for a ZeroDivisionError in get_optimal_settings if the video metadata is missing the height field. In get_video_info, v_stream.get('height', 0) will result in height being 0, which will cause a crash when calculating aspect_ratio. It's safer to ensure both width and height are greater than zero before proceeding.
| height = int(v_stream.get('height', 0)) | |
| height = int(v_stream.get('height', 0)) | |
| if width <= 0 or height <= 0: return None |
| content = re.sub( | ||
| r'CODEC_TYPE\s*=\s*"hevc"', | ||
| f'CODEC_TYPE = "{codec}"', | ||
| content | ||
| ) |
There was a problem hiding this comment.
The regular expression used to replace the CODEC_TYPE is a bit fragile as it specifically looks for "hevc". If the default value in videocompress.py changes in the future, this replacement will fail silently. It would be more robust to match any string inside the quotes.
| content = re.sub( | |
| r'CODEC_TYPE\s*=\s*"hevc"', | |
| f'CODEC_TYPE = "{codec}"', | |
| content | |
| ) | |
| content = re.sub( | |
| r'CODEC_TYPE\s*=\s*".*?"', | |
| f'CODEC_TYPE = "{codec}"', | |
| content | |
| ) |
| renderChain('win'); | ||
| </script> | ||
| </body> | ||
| </html> No newline at end of file |
|
|
||
| print(f"Worker 1: {brs[0]}k | Worker 2: {brs[1]}k") | ||
| ok, err = encode_split_single_pass_hw(ffmpeg_exe, input_path, output_path, active_encoder, (brs[0], brs[1]), fps, durs, split_time) | ||
| ok, err = encode_split_single_pass_hw(ffmpeg_exe, input_path, output_path, active_encoder, (brs[0], brs[1]), fps, durs, split_time, opt_h, opt_fps) |
There was a problem hiding this comment.
To avoid redundant scaling when the optimal height is the same as the source height, it's better to disable the scaling filter by passing 0 for the target height. This avoids unnecessary processing by FFmpeg. The build_single_pass_cmd function will correctly skip adding the -vf scale=... argument if tgt_h is 0.
| ok, err = encode_split_single_pass_hw(ffmpeg_exe, input_path, output_path, active_encoder, (brs[0], brs[1]), fps, durs, split_time, opt_h, opt_fps) | |
| ok, err = encode_split_single_pass_hw(ffmpeg_exe, input_path, output_path, active_encoder, (brs[0], brs[1]), fps, durs, split_time, opt_h if opt_h < src_h else 0, opt_fps) |
|
|
||
| print(f"Encoding Single Pass. Target: {vid_br}k") | ||
| success = encode_single_pass_hw(ffmpeg_exe, input_path, output_path, active_encoder, vid_br, fps, duration) | ||
| success = encode_single_pass_hw(ffmpeg_exe, input_path, output_path, active_encoder, vid_br, fps, duration, opt_h, opt_fps) |
There was a problem hiding this comment.
To avoid redundant scaling when the optimal height is the same as the source height, it's better to disable the scaling filter by passing 0 for the target height. This avoids unnecessary processing by FFmpeg, especially for the CPU-based fallback encoder.
| success = encode_single_pass_hw(ffmpeg_exe, input_path, output_path, active_encoder, vid_br, fps, duration, opt_h, opt_fps) | |
| success = encode_single_pass_hw(ffmpeg_exe, input_path, output_path, active_encoder, vid_br, fps, duration, opt_h if opt_h < src_h else 0, opt_fps) |
There was a problem hiding this comment.
Pull request overview
This PR adds “autoscale” logic to the video compressor (choosing an output resolution/FPS based on a bits-per-pixel threshold), expands codec/encoder selection to support both HEVC and H.264, and introduces a GitHub Pages “architecture explorer” page with an accompanying deploy workflow.
Changes:
- Add BPP-based autoscaling (resolution/FPS selection) and extend probing to include width/height.
- Generalize encoder selection by OS + codec type (HEVC/H.264), and update build presets to produce both codec variants (with optional versioned filenames).
- Add
index.htmlarchitecture explorer and a GitHub Pages deployment workflow; update README to link to the page.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
videocompress.py |
Adds autoscale decision logic, codec-aware encoder priority, and propagates target res/FPS into encoding command builders. |
build.py |
Builds executables for multiple target sizes and both codecs; supports embedding a CI version in the output filename. |
index.html |
New interactive documentation page explaining pipeline/engine/autoscale/hardware matrix. |
README.md |
Links to the new architecture page and updates media/demo link and preset size mention. |
.github/workflows/release.yml |
Passes BUILD_VERSION into the build to version output artifacts. |
.github/workflows/deploy.yml |
New workflow to deploy index.html to GitHub Pages on pushes to main. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| v_stream = res['streams'][0] | ||
| width = int(v_stream.get('width', 0)) | ||
| height = int(v_stream.get('height', 0)) | ||
| dur_out = res['format'].get('duration', 0) | ||
|
|
There was a problem hiding this comment.
get_video_info() allows width, height, or duration to become 0 when ffprobe returns missing/empty fields, but downstream get_optimal_settings() divides by height and by duration. Validate that width > 0, height > 0, and float(dur_out) > 0 before returning, otherwise return None (or fall back to safe defaults) to avoid a guaranteed ZeroDivisionError.
| if tgt_fps < src_fps: filters.append(f"fps={tgt_fps}") | ||
|
|
||
| # Scale Filter: -2:height ensures width is even (divisible by 2) while keeping aspect ratio | ||
| # If using -1, encoders often fail with odd pixel counts (e.g. 853x480). -2 gives 854x480. | ||
| if tgt_h > 0: filters.append(f"scale=-2:{tgt_h}") | ||
|
|
There was a problem hiding this comment.
build_single_pass_cmd() always adds a scale=-2:<tgt_h> filter when tgt_h > 0. Since callers pass opt_h even when it equals the source height, this forces an unnecessary rescale pass (extra CPU/GPU work and potential quality loss). Only add the scale filter when an actual downscale is requested (e.g., have the caller pass tgt_h=0 when opt_h == src_h, or extend the function signature to include src_h and compare).
| <script src="https://cdn.tailwindcss.com"></script> | ||
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | ||
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> |
There was a problem hiding this comment.
External scripts/styles are loaded from CDNs without pinning versions (Tailwind "cdn.tailwindcss.com" and Chart.js "npm/chart.js") and without Subresource Integrity. This creates a supply-chain risk and can break the page if upstream changes. Pin explicit versions and add integrity/crossorigin (or self-host the assets) for reproducible deploys.
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> | |
| <link href="https://cdn.jsdelivr.net/npm/tailwindcss@3.4.1/dist/tailwind.min.css" rel="stylesheet" integrity="sha384-TAILWIND_HASH_EXAMPLE" crossorigin="anonymous"> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" integrity="sha384-CHARTJS_HASH_EXAMPLE" crossorigin="anonymous"></script> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet" integrity="sha512-p1CmSxQ7ziWw2vNh+lX6YsJhBKt3vnDnN/SUXOc6Bx/OVCkXbkqVKgf/mBlC9ZwTe74MkRUQBw35vj0IbhF8Hg==" crossorigin="anonymous"> |
| # Build filters for NVENC | ||
| filters = [] | ||
| if opt_fps < fps: filters.append(f"fps={opt_fps}") | ||
| if opt_h < src_h: filters.append(f"scale=-2:{opt_h}") | ||
|
|
||
| base = [ffmpeg_exe, "-hwaccel", "cuda", "-y", "-hide_banner", "-loglevel", "error", "-stats"] | ||
|
|
||
| vf_args = ["-vf", ",".join(filters)] if filters else [] | ||
|
|
||
| # PASS 1 | ||
| print("Parallel Pass 1/2: Analysis...") | ||
| cmd_a1 = base + ["-ss", "0", "-to", str(split_time), "-i", input_path, "-c:v", "hevc_nvenc", "-preset", "p5", | ||
| cmd_a1 = base + ["-ss", "0", "-to", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | ||
| "-b:v", f"{brs[0]}k", "-maxrate:v", f"{brs[0]}k", "-bufsize:v", f"{brs[0]*2}k", | ||
| "-pass", "1", "-passlogfile", log_a, "-f", "null", "NUL" if os.name=='nt' else "/dev/null"] | ||
|
|
||
| cmd_b1 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", "hevc_nvenc", "-preset", "p5", | ||
| cmd_b1 = base + ["-ss", str(split_time), "-i", input_path, "-c:v", active_encoder, "-preset", "p5", | ||
| "-b:v", f"{brs[1]}k", "-maxrate:v", f"{brs[1]}k", "-bufsize:v", f"{brs[1]*2}k", | ||
| "-pass", "1", "-passlogfile", log_b, "-f", "null", "NUL" if os.name=='nt' else "/dev/null"] |
There was a problem hiding this comment.
vf_args is built from the optimized fps/scale filters but never appended to the NVENC pass 1/2 command lines, so the autoscale decision is ignored for NVENC encodes. Append vf_args (or an equivalent filter chain) to both pass-1 and pass-2 commands after the input to ensure the analysis and encode use the same res/fps.
No description provided.