Release v1.9.0: Add video-comparer skill and enhance transcript-fixer
## New Skill: video-comparer v1.0.0 - Compare original and compressed videos with interactive HTML reports - Calculate quality metrics (PSNR, SSIM) for compression analysis - Generate frame-by-frame visual comparisons (slider, side-by-side, grid) - Extract video metadata (codec, resolution, bitrate, duration) - Multi-platform FFmpeg support with security features ## transcript-fixer Enhancements - Add async AI processor for parallel processing - Add connection pool management for database operations - Add concurrency manager and rate limiter - Add audit log retention and database migrations - Add health check and metrics monitoring - Add comprehensive test suite (8 new test files) - Enhance security with domain and path validators ## Marketplace Updates - Update marketplace version from 1.8.0 to 1.9.0 - Update skills count from 15 to 16 - Update documentation (README.md, CLAUDE.md, CHANGELOG.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
4
video-comparer/.security-scan-passed
Normal file
4
video-comparer/.security-scan-passed
Normal file
@@ -0,0 +1,4 @@
|
||||
Security scan passed
|
||||
Scanned at: 2025-10-29T01:20:09.276880
|
||||
Tool: gitleaks + pattern-based validation
|
||||
Content hash: 54ee1c2464322bf78b1ddf827546d9e90d77135549c211cf3373f6ca9539c4b0
|
||||
348
video-comparer/README.md
Normal file
348
video-comparer/README.md
Normal file
@@ -0,0 +1,348 @@
|
||||
# Video Comparer
|
||||
|
||||
A professional video comparison tool that analyzes compression quality and generates interactive HTML reports. Compare original vs compressed videos with detailed metrics (PSNR, SSIM) and frame-by-frame visual comparisons.
|
||||
|
||||
## Features
|
||||
|
||||
### 🎯 Video Analysis
|
||||
- **Metadata Extraction**: Codec, resolution, frame rate, bitrate, duration, file size
|
||||
- **Quality Metrics**: PSNR (Peak Signal-to-Noise Ratio) and SSIM (Structural Similarity Index)
|
||||
- **Compression Analysis**: Size and bitrate reduction percentages
|
||||
|
||||
### 🖼️ Interactive Comparison
|
||||
- **Three Viewing Modes**:
|
||||
- **Slider Mode**: Interactive before/after slider using img-comparison-slider
|
||||
- **Side-by-Side Mode**: Simultaneous display of both frames
|
||||
- **Grid Mode**: Compact 2-column layout
|
||||
- **Zoom Controls**: 50%-200% zoom with real image dimension scaling
|
||||
- **Responsive Design**: Works on desktop, tablet, and mobile
|
||||
|
||||
### 🔒 Security & Reliability
|
||||
- **Path Validation**: Prevents directory traversal attacks
|
||||
- **Command Injection Prevention**: No shell=True in subprocess calls
|
||||
- **Resource Limits**: File size and timeout restrictions
|
||||
- **Comprehensive Error Handling**: User-friendly error messages
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Python 3.8+** (for type hints and modern features)
|
||||
2. **FFmpeg** (required for video analysis)
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install ffmpeg
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt install ffmpeg
|
||||
|
||||
# Windows
|
||||
# Download from https://ffmpeg.org/download.html
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Navigate to the skill directory
|
||||
cd /path/to/video-comparer
|
||||
|
||||
# Compare two videos
|
||||
python3 scripts/compare.py original.mp4 compressed.mp4
|
||||
|
||||
# Open the generated report
|
||||
open comparison.html # macOS
|
||||
# or
|
||||
xdg-open comparison.html # Linux
|
||||
# or
|
||||
start comparison.html # Windows
|
||||
```
|
||||
|
||||
### Command Line Options
|
||||
|
||||
```bash
|
||||
python3 scripts/compare.py <original> <compressed> [options]
|
||||
|
||||
Arguments:
|
||||
original Path to original video file
|
||||
compressed Path to compressed video file
|
||||
|
||||
Options:
|
||||
-o, --output PATH Output HTML report path (default: comparison.html)
|
||||
--interval SECONDS Frame extraction interval in seconds (default: 5)
|
||||
-h, --help Show help message
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Basic comparison
|
||||
python3 scripts/compare.py original.mp4 compressed.mp4
|
||||
|
||||
# Custom output file
|
||||
python3 scripts/compare.py original.mp4 compressed.mp4 -o report.html
|
||||
|
||||
# Extract frames every 10 seconds (fewer frames, faster processing)
|
||||
python3 scripts/compare.py original.mp4 compressed.mp4 --interval 10
|
||||
|
||||
# Compare with absolute paths
|
||||
python3 scripts/compare.py ~/Videos/original.mov ~/Videos/compressed.mov
|
||||
|
||||
# Batch comparison
|
||||
for original in originals/*.mp4; do
|
||||
compressed="compressed/$(basename "$original")"
|
||||
python3 scripts/compare.py "$original" "$compressed" -o "reports/$(basename "$original" .mp4).html"
|
||||
done
|
||||
```
|
||||
|
||||
## Supported Formats
|
||||
|
||||
| Format | Extension | Notes |
|
||||
|--------|-----------|-------|
|
||||
| MP4 | `.mp4` | Recommended, widely supported |
|
||||
| MOV | `.mov` | Apple QuickTime format |
|
||||
| AVI | `.avi` | Legacy format |
|
||||
| MKV | `.mkv` | Matroska container |
|
||||
| WebM | `.webm` | Web-optimized format |
|
||||
|
||||
## Output Report
|
||||
|
||||
The generated HTML report includes:
|
||||
|
||||
### 1. Video Parameters Comparison
|
||||
- **Codec**: Video compression format (h264, hevc, vp9, etc.)
|
||||
- **Resolution**: Width × Height in pixels
|
||||
- **Frame Rate**: Frames per second
|
||||
- **Bitrate**: Data rate (kbps/Mbps)
|
||||
- **Duration**: Total video length
|
||||
- **File Size**: Storage requirement
|
||||
- **Filenames**: Original file names
|
||||
|
||||
### 2. Quality Analysis
|
||||
- **Size Reduction**: Percentage of storage saved
|
||||
- **Bitrate Reduction**: Percentage of bandwidth saved
|
||||
- **PSNR**: Peak Signal-to-Noise Ratio (dB)
|
||||
- 30-35 dB: Acceptable quality
|
||||
- 35-40 dB: Good quality
|
||||
- 40+ dB: Excellent quality
|
||||
- **SSIM**: Structural Similarity Index (0.0-1.0)
|
||||
- 0.90-0.95: Good quality
|
||||
- 0.95-0.98: Very good quality
|
||||
- 0.98+: Excellent quality
|
||||
|
||||
### 3. Frame-by-Frame Comparison
|
||||
- Interactive slider for detailed comparison
|
||||
- Side-by-side viewing for overall assessment
|
||||
- Grid layout for quick scanning
|
||||
- Zoom controls (50%-200%)
|
||||
- Timestamp labels for each frame
|
||||
|
||||
## Configuration
|
||||
|
||||
### Constants in `scripts/compare.py`
|
||||
|
||||
```python
|
||||
ALLOWED_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv', '.webm'}
|
||||
MAX_FILE_SIZE_MB = 500 # Maximum file size limit
|
||||
FFMPEG_TIMEOUT = 300 # FFmpeg timeout (5 minutes)
|
||||
FFPROBE_TIMEOUT = 30 # FFprobe timeout (30 seconds)
|
||||
BASE_FRAME_HEIGHT = 800 # Frame height for comparison
|
||||
FRAME_INTERVAL = 5 # Default frame extraction interval
|
||||
```
|
||||
|
||||
### Customizing Frame Resolution
|
||||
|
||||
To change the frame resolution for comparison:
|
||||
|
||||
```python
|
||||
# In scripts/compare.py
|
||||
BASE_FRAME_HEIGHT = 1200 # Higher resolution (larger file size)
|
||||
# or
|
||||
BASE_FRAME_HEIGHT = 600 # Lower resolution (smaller file size)
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Processing Time
|
||||
- **Metadata Extraction**: < 5 seconds
|
||||
- **Quality Metrics**: 1-2 minutes (depends on video duration)
|
||||
- **Frame Extraction**: 30-60 seconds (depends on video length and interval)
|
||||
- **Report Generation**: < 10 seconds
|
||||
|
||||
### File Sizes
|
||||
- **Input Videos**: Up to 500MB each (configurable)
|
||||
- **Generated Report**: 2-5MB (depends on frame count)
|
||||
- **Temporary Files**: Auto-cleaned during processing
|
||||
|
||||
### Resource Usage
|
||||
- **Memory**: ~200-500MB during processing
|
||||
- **Disk Space**: ~100MB temporary files
|
||||
- **CPU**: Moderate (video decoding)
|
||||
|
||||
## Security Features
|
||||
|
||||
### Path Validation
|
||||
- ✅ Converts all paths to absolute paths
|
||||
- ✅ Verifies files exist and are readable
|
||||
- ✅ Checks file extensions against whitelist
|
||||
- ✅ Validates file size before processing
|
||||
|
||||
### Command Injection Prevention
|
||||
- ✅ All subprocess calls use argument lists
|
||||
- ✅ No `shell=True` in subprocess calls
|
||||
- ✅ User input never passed to shell
|
||||
- ✅ FFmpeg arguments validated and escaped
|
||||
|
||||
### Resource Limits
|
||||
- ✅ File size limit enforcement
|
||||
- ✅ Timeout limits for FFmpeg operations
|
||||
- ✅ Temporary files auto-cleanup
|
||||
- ✅ Memory usage monitoring
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "FFmpeg not found"
|
||||
```bash
|
||||
# Install FFmpeg using your package manager
|
||||
brew install ffmpeg # macOS
|
||||
sudo apt install ffmpeg # Ubuntu/Debian
|
||||
sudo yum install ffmpeg # CentOS/RHEL/Fedora
|
||||
```
|
||||
|
||||
#### "File too large: X MB"
|
||||
```bash
|
||||
# Options:
|
||||
1. Compress videos before comparison
|
||||
2. Increase MAX_FILE_SIZE_MB in compare.py
|
||||
3. Use shorter video clips
|
||||
```
|
||||
|
||||
#### "Operation timed out"
|
||||
```bash
|
||||
# For very long videos:
|
||||
python3 scripts/compare.py original.mp4 compressed.mp4 --interval 10
|
||||
# or
|
||||
# Increase FFMPEG_TIMEOUT in compare.py
|
||||
```
|
||||
|
||||
#### "No frames extracted"
|
||||
- Check if videos are playable in media player
|
||||
- Verify videos have sufficient duration (> interval seconds)
|
||||
- Ensure FFmpeg can decode the codec
|
||||
|
||||
#### "Frame count mismatch"
|
||||
- Videos have different durations or frame rates
|
||||
- Script automatically truncates to minimum frame count
|
||||
- Warning is displayed in output
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable verbose output by modifying the script:
|
||||
|
||||
```python
|
||||
# Add at the top of compare.py
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### File Structure
|
||||
```
|
||||
video-comparer/
|
||||
├── SKILL.md # Skill description and invocation
|
||||
├── README.md # This file
|
||||
├── assets/
|
||||
│ └── template.html # HTML report template
|
||||
├── references/
|
||||
│ ├── video_metrics.md # Quality metrics reference
|
||||
│ └── ffmpeg_commands.md # FFmpeg command examples
|
||||
└── scripts/
|
||||
└── compare.py # Main comparison script (696 lines)
|
||||
```
|
||||
|
||||
### Code Organization
|
||||
|
||||
- **compare.py**: Main script with all functionality
|
||||
- Input validation and security checks
|
||||
- FFmpeg integration and command execution
|
||||
- Video metadata extraction
|
||||
- Quality metrics calculation (PSNR, SSIM)
|
||||
- Frame extraction and processing
|
||||
- HTML report generation
|
||||
|
||||
- **template.html**: Interactive report template
|
||||
- Responsive CSS Grid layout
|
||||
- Web Components for slider functionality
|
||||
- Base64-encoded image embedding
|
||||
- Interactive controls and zoom
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **Python Standard Library**: os, subprocess, json, pathlib, tempfile, base64
|
||||
- **External Tools**: FFmpeg, FFprobe (must be installed separately)
|
||||
- **Web Components**: img-comparison-slider (loaded from CDN)
|
||||
|
||||
## Contributing
|
||||
|
||||
### Development Setup
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd video-comparer
|
||||
|
||||
# Create virtual environment (optional but recommended)
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # macOS/Linux
|
||||
# or
|
||||
venv\Scripts\activate # Windows
|
||||
|
||||
# Install FFmpeg (see Prerequisites section)
|
||||
# Test the installation
|
||||
python3 scripts/compare.py --help
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- **Python**: PEP 8 compliance
|
||||
- **Type Hints**: All function signatures
|
||||
- **Docstrings**: All public functions and classes
|
||||
- **Error Handling**: Comprehensive exception handling
|
||||
- **Security**: Input validation and sanitization
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Test with sample videos (you'll need to provide these)
|
||||
python3 scripts/compare.py test/original.mp4 test/compressed.mp4
|
||||
|
||||
# Test error handling
|
||||
python3 scripts/compare.py nonexistent.mp4 also_nonexistent.mp4
|
||||
python3 scripts/compare.py original.txt compressed.txt
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This skill is part of the claude-code-skills collection. See the main repository for license information.
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
1. Check this README for troubleshooting
|
||||
2. Review the SKILL.md file for detailed usage instructions
|
||||
3. Ensure FFmpeg is properly installed
|
||||
4. Verify video files are supported formats
|
||||
|
||||
## Changelog
|
||||
|
||||
### v1.0.0
|
||||
- Initial release
|
||||
- Video metadata extraction
|
||||
- PSNR and SSIM quality metrics
|
||||
- Frame extraction and comparison
|
||||
- Interactive HTML report generation
|
||||
- Security features and error handling
|
||||
- Responsive design and mobile support
|
||||
140
video-comparer/SKILL.md
Normal file
140
video-comparer/SKILL.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
name: video-comparer
|
||||
description: This skill should be used when comparing two videos to analyze compression results or quality differences. Generates interactive HTML reports with quality metrics (PSNR, SSIM) and frame-by-frame visual comparisons. Triggers when users mention "compare videos", "video quality", "compression analysis", "before/after compression", or request quality assessment of compressed videos.
|
||||
---
|
||||
|
||||
# Video Comparer
|
||||
|
||||
## Overview
|
||||
|
||||
Compare two videos and generate an interactive HTML report analyzing compression results. The script extracts video metadata, calculates quality metrics (PSNR, SSIM), and creates frame-by-frame visual comparisons with three viewing modes: slider, side-by-side, and grid.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Comparing original and compressed videos
|
||||
- Analyzing video compression quality and efficiency
|
||||
- Evaluating codec performance or bitrate reduction impact
|
||||
- Users mention "compare videos", "video quality", "compression analysis", or "before/after compression"
|
||||
|
||||
## Core Usage
|
||||
|
||||
### Basic Command
|
||||
|
||||
```bash
|
||||
python3 scripts/compare.py original.mp4 compressed.mp4
|
||||
```
|
||||
|
||||
Generates `comparison.html` with:
|
||||
- Video parameters (codec, resolution, bitrate, duration, file size)
|
||||
- Quality metrics (PSNR, SSIM, size/bitrate reduction percentages)
|
||||
- Frame-by-frame comparison (default: frames at 5s intervals)
|
||||
|
||||
### Command Options
|
||||
|
||||
```bash
|
||||
# Custom output file
|
||||
python3 scripts/compare.py original.mp4 compressed.mp4 -o report.html
|
||||
|
||||
# Custom frame interval (larger = fewer frames, faster processing)
|
||||
python3 scripts/compare.py original.mp4 compressed.mp4 --interval 10
|
||||
|
||||
# Batch comparison
|
||||
for original in originals/*.mp4; do
|
||||
compressed="compressed/$(basename "$original")"
|
||||
output="reports/$(basename "$original" .mp4).html"
|
||||
python3 scripts/compare.py "$original" "$compressed" -o "$output"
|
||||
done
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
### System Dependencies
|
||||
|
||||
**FFmpeg and FFprobe** (required for video analysis and frame extraction):
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install ffmpeg
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt update && sudo apt install ffmpeg
|
||||
|
||||
# Windows
|
||||
# Download from https://ffmpeg.org/download.html
|
||||
# Or use: winget install ffmpeg
|
||||
```
|
||||
|
||||
**Python 3.8+** (uses type hints, f-strings, pathlib)
|
||||
|
||||
### Video Specifications
|
||||
|
||||
- **Supported formats:** `.mp4` (recommended), `.mov`, `.avi`, `.mkv`, `.webm`
|
||||
- **File size limit:** 500MB per video (configurable)
|
||||
- **Processing time:** ~1-2 minutes for typical videos; varies by duration and frame interval
|
||||
|
||||
## Script Behavior
|
||||
|
||||
### Automatic Validation
|
||||
|
||||
The script automatically validates:
|
||||
- FFmpeg/FFprobe installation and availability
|
||||
- File existence, extensions, and size limits
|
||||
- Path security (prevents directory traversal)
|
||||
|
||||
Clear error messages with resolution guidance appear when validation fails.
|
||||
|
||||
### Quality Metrics
|
||||
|
||||
The script calculates two standard quality metrics:
|
||||
|
||||
**PSNR (Peak Signal-to-Noise Ratio):** Pixel-level similarity measurement (20-50 dB scale, higher is better)
|
||||
|
||||
**SSIM (Structural Similarity Index):** Perceptual similarity measurement (0.0-1.0 scale, higher is better)
|
||||
|
||||
For detailed interpretation scales and quality thresholds, consult `references/video_metrics.md`.
|
||||
|
||||
### Frame Extraction
|
||||
|
||||
The script extracts frames at specified intervals (default: 5 seconds), scales them to consistent height (800px) for comparison, and embeds them as base64 data URLs in self-contained HTML. Temporary files are automatically cleaned after processing.
|
||||
|
||||
### Output Report
|
||||
|
||||
The generated HTML report includes:
|
||||
- **Slider Mode**: Drag to reveal original vs compressed (default)
|
||||
- **Side-by-Side Mode**: Simultaneous display for direct comparison
|
||||
- **Grid Mode**: Compact 2-column layout
|
||||
- **Zoom Controls**: 50%-200% magnification
|
||||
- Self-contained format (no server required, works offline)
|
||||
|
||||
## Important Implementation Details
|
||||
|
||||
### Security
|
||||
|
||||
The script implements:
|
||||
- Path validation (absolute paths, prevents directory traversal)
|
||||
- Command injection prevention (no `shell=True`, validated arguments)
|
||||
- Resource limits (file size, timeouts)
|
||||
- Custom exceptions: `ValidationError`, `FFmpegError`, `VideoComparisonError`
|
||||
|
||||
### Common Error Scenarios
|
||||
|
||||
**"FFmpeg not found"**: Install FFmpeg via platform package manager (see Requirements section)
|
||||
|
||||
**"File too large"**: Compress videos before comparison, or adjust `MAX_FILE_SIZE_MB` in `scripts/compare.py`
|
||||
|
||||
**"Operation timed out"**: Increase `FFMPEG_TIMEOUT` constant or use larger `--interval` value (processes fewer frames)
|
||||
|
||||
**"Frame count mismatch"**: Videos have different durations/frame rates; script auto-truncates to minimum frame count and shows warning
|
||||
|
||||
## Configuration
|
||||
|
||||
The script includes adjustable constants for file size limits, timeouts, frame dimensions, and extraction intervals. To customize behavior, edit the constants at the top of `scripts/compare.py`. For detailed configuration options and their impacts, consult `references/configuration.md`.
|
||||
|
||||
## Reference Materials
|
||||
|
||||
Consult these files for detailed information:
|
||||
- **`references/video_metrics.md`**: Quality metrics interpretation (PSNR/SSIM scales, compression targets, bitrate guidelines)
|
||||
- **`references/ffmpeg_commands.md`**: FFmpeg command reference (metadata extraction, frame extraction, troubleshooting)
|
||||
- **`references/configuration.md`**: Script configuration options and adjustable constants
|
||||
- **`assets/template.html`**: HTML report template for customizing viewing modes and styling
|
||||
893
video-comparer/assets/template.html
Normal file
893
video-comparer/assets/template.html
Normal file
@@ -0,0 +1,893 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>视频质量对比分析</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/img-comparison-slider@8/dist/styles.css">
|
||||
<script defer src="https://unpkg.com/img-comparison-slider@8/dist/index.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 32px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.analysis-section {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.metrics-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.metrics-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
text-align: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.metric-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.metric-card h3 {
|
||||
color: #667eea;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
line-height: 1.3;
|
||||
word-break: keep-all;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.metric-value.multiline {
|
||||
white-space: normal;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.metric-subtitle {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.comparison-section {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.comparison-container {
|
||||
width: 100%;
|
||||
background: #000;
|
||||
border-radius: 15px;
|
||||
overflow: auto;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
max-height: 900px;
|
||||
}
|
||||
|
||||
img-comparison-slider {
|
||||
width: auto;
|
||||
max-width: none;
|
||||
--divider-width: 3px;
|
||||
--divider-color: #ffffff;
|
||||
--default-handle-opacity: 1;
|
||||
--default-handle-width: 50px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
img-comparison-slider img {
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 50px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
margin: 0 auto 20px;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.zoom-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
background: #f5f7fa;
|
||||
color: #667eea;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.zoom-btn:hover {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.zoom-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.zoom-btn.reset {
|
||||
width: auto;
|
||||
padding: 0 16px;
|
||||
border-radius: 18px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#zoomSlider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 180px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: #e9ecef;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
#zoomSlider:hover {
|
||||
background: #dee2e6;
|
||||
}
|
||||
|
||||
#zoomSlider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
#zoomSlider::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.2);
|
||||
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.6);
|
||||
}
|
||||
|
||||
#zoomSlider::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
#zoomSlider::-moz-range-thumb:hover {
|
||||
transform: scale(1.2);
|
||||
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.6);
|
||||
}
|
||||
|
||||
#zoomLevel {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
min-width: 48px;
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.labels {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.label {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.frame-selector {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.frame-btn {
|
||||
padding: 10px 16px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.frame-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.frame-btn.active {
|
||||
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
padding: 12px 24px;
|
||||
background: white;
|
||||
color: #667eea;
|
||||
border: 2px solid #667eea;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.mode-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.mode-btn.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.side-by-side-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
background: #000;
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.side-by-side-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.side-by-side-item .image-container {
|
||||
background: #000;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.side-by-side-item img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
background: #000;
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.grid-item img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.grid-item-label {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 5px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.grid-item-time {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 5px;
|
||||
background: rgba(102, 126, 234, 0.9);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 5px;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.quality-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.indicator-bar {
|
||||
flex: 1;
|
||||
height: 20px;
|
||||
background: #e9ecef;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.indicator-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #ff6b6b, #feca57, #48dbfb, #1dd1a1);
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.indicator-label {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.findings {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 20px;
|
||||
margin-top: 30px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.findings h3 {
|
||||
color: #856404;
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.findings ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.findings li {
|
||||
color: #856404;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 25px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.findings li::before {
|
||||
content: '⚠️';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.good-news {
|
||||
background: #d4edda;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
||||
.good-news h3 {
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.good-news li {
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.good-news li::before {
|
||||
content: '✅';
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.side-by-side-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.view-mode-selector {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>📊 微信视频号质量分析报告</h1>
|
||||
<p>原始视频 vs 微信视频号下载视频对比</p>
|
||||
</div>
|
||||
|
||||
<div class="analysis-section">
|
||||
<h2 style="margin-bottom: 20px; color: #333;">📈 视频参数对比</h2>
|
||||
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<h3>视频编码</h3>
|
||||
<div class="metric-value">HEVC → H264</div>
|
||||
<div class="metric-subtitle">微信重新编码</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>分辨率</h3>
|
||||
<div class="metric-value">1080×1920</div>
|
||||
<div class="metric-subtitle">保持不变</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>帧率</h3>
|
||||
<div class="metric-value">30 FPS</div>
|
||||
<div class="metric-subtitle">保持不变</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>时长</h3>
|
||||
<div class="metric-value">105.37 秒</div>
|
||||
<div class="metric-subtitle">几乎相同</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>视频码率</h3>
|
||||
<div class="metric-value multiline">6.89 → 6.91<br>Mbps</div>
|
||||
<div class="metric-subtitle">+0.3%</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>文件大小</h3>
|
||||
<div class="metric-value multiline">89 → 89.3<br>MB</div>
|
||||
<div class="metric-subtitle">+0.3 MB</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>画质保留</h3>
|
||||
<div class="metric-value">87.3%</div>
|
||||
<div class="metric-subtitle">SSIM</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>PSNR</h3>
|
||||
<div class="metric-value">23.37 dB</div>
|
||||
<div class="metric-subtitle">偏低</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 style="margin-top: 40px; margin-bottom: 20px; color: #333;">🔬 画质详细分析</h2>
|
||||
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<h3>亮度通道 Y</h3>
|
||||
<div class="metric-value">21.72 dB</div>
|
||||
<div class="metric-subtitle">PSNR / SSIM: 0.831</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>色度通道 U</h3>
|
||||
<div class="metric-value">33.18 dB</div>
|
||||
<div class="metric-subtitle">PSNR / SSIM: 0.953</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>色度通道 V</h3>
|
||||
<div class="metric-value">36.68 dB</div>
|
||||
<div class="metric-subtitle">PSNR / SSIM: 0.962</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<h3>质量评价</h3>
|
||||
<div class="metric-value multiline">细节损失<br>色彩保留好</div>
|
||||
<div class="metric-subtitle">有损压缩</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 style="margin-top: 40px; margin-bottom: 20px; color: #333;">🖼️ 帧对比</h2>
|
||||
|
||||
<div class="view-mode-selector" style="margin-bottom: 20px; display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;">
|
||||
<button class="mode-btn active" data-mode="slider">🔀 滑块对比</button>
|
||||
<button class="mode-btn" data-mode="sidebyside">📊 并排对比</button>
|
||||
<button class="mode-btn" data-mode="grid">🎬 网格对比</button>
|
||||
</div>
|
||||
|
||||
<div class="frame-selector">
|
||||
<button class="frame-btn active" data-frame="1">0秒</button>
|
||||
<button class="frame-btn" data-frame="2">5秒</button>
|
||||
<button class="frame-btn" data-frame="3">10秒</button>
|
||||
<button class="frame-btn" data-frame="4">15秒</button>
|
||||
<button class="frame-btn" data-frame="5">20秒</button>
|
||||
<button class="frame-btn" data-frame="6">25秒</button>
|
||||
<button class="frame-btn" data-frame="7">30秒</button>
|
||||
<button class="frame-btn" data-frame="8">35秒</button>
|
||||
<button class="frame-btn" data-frame="9">40秒</button>
|
||||
<button class="frame-btn" data-frame="10">45秒</button>
|
||||
<button class="frame-btn" data-frame="11">50秒</button>
|
||||
<button class="frame-btn" data-frame="12">55秒</button>
|
||||
<button class="frame-btn" data-frame="13">60秒</button>
|
||||
<button class="frame-btn" data-frame="14">65秒</button>
|
||||
<button class="frame-btn" data-frame="15">70秒</button>
|
||||
<button class="frame-btn" data-frame="16">75秒</button>
|
||||
<button class="frame-btn" data-frame="17">80秒</button>
|
||||
<button class="frame-btn" data-frame="18">85秒</button>
|
||||
<button class="frame-btn" data-frame="19">90秒</button>
|
||||
<button class="frame-btn" data-frame="20">95秒</button>
|
||||
<button class="frame-btn" data-frame="21">100秒</button>
|
||||
<button class="frame-btn" data-frame="22">105秒</button>
|
||||
</div>
|
||||
|
||||
<!-- 滑块对比模式 -->
|
||||
<div class="comparison-section" id="sliderMode">
|
||||
<div class="labels">
|
||||
<span class="label">🎬 原始视频 (HEVC)</span>
|
||||
<span class="label">📱 微信视频号 (H264)</span>
|
||||
</div>
|
||||
|
||||
<!-- 缩放控制 -->
|
||||
<div class="zoom-controls">
|
||||
<button class="zoom-btn" id="zoomOut" title="缩小">−</button>
|
||||
<input type="range" id="zoomSlider" min="50" max="200" value="100" step="10" title="拖动缩放">
|
||||
<button class="zoom-btn" id="zoomIn" title="放大">+</button>
|
||||
<span id="zoomLevel">100%</span>
|
||||
<button class="zoom-btn reset" id="zoomReset" title="重置缩放">重置</button>
|
||||
</div>
|
||||
|
||||
<div class="comparison-container" id="sliderContainer">
|
||||
<img-comparison-slider id="comparisonSlider">
|
||||
<img slot="first" id="originalImage" src="original/frame_001.png" alt="原始视频" style="height: 800px;" />
|
||||
<img slot="second" id="wechatImage" src="wechat/frame_001.png" alt="微信视频" style="height: 800px;" />
|
||||
</img-comparison-slider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 并排对比模式 -->
|
||||
<div class="comparison-section" id="sideBySideMode" style="display: none;">
|
||||
<div class="side-by-side-container">
|
||||
<div class="side-by-side-item">
|
||||
<div class="label" style="margin-bottom: 10px;">🎬 原始视频 (HEVC)</div>
|
||||
<div class="image-container">
|
||||
<img id="originalImageSBS" src="original/frame_001.png" alt="原始视频" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="side-by-side-item">
|
||||
<div class="label" style="margin-bottom: 10px;">📱 微信视频号 (H264)</div>
|
||||
<div class="image-container">
|
||||
<img id="wechatImageSBS" src="wechat/frame_001.png" alt="微信视频" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网格对比模式 -->
|
||||
<div class="comparison-section" id="gridMode" style="display: none;">
|
||||
<div class="labels">
|
||||
<span class="label">🎬 原始视频 (HEVC)</span>
|
||||
<span class="label">📱 微信视频号 (H264)</span>
|
||||
</div>
|
||||
<div class="grid-container" id="gridContainer">
|
||||
<!-- 动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="findings">
|
||||
<h3>⚠️ 发现的问题</h3>
|
||||
<ul>
|
||||
<li><strong>编码转换损失</strong>: HEVC → H264 转码导致质量下降,尤其是亮度通道</li>
|
||||
<li><strong>PSNR 偏低</strong>: 23.37 dB 表示存在明显的压缩伪影和细节损失</li>
|
||||
<li><strong>亮度细节受损</strong>: Y 通道 PSNR 仅 21.72 dB,细节模糊化明显</li>
|
||||
<li><strong>压缩算法不同</strong>: 微信使用了更激进的压缩策略</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="findings good-news">
|
||||
<h3>✅ 保留较好的方面</h3>
|
||||
<ul>
|
||||
<li><strong>分辨率不变</strong>: 依然保持 1080×1920 的原始分辨率</li>
|
||||
<li><strong>色彩保留好</strong>: 色度通道 PSNR > 33 dB,色彩还原度较高</li>
|
||||
<li><strong>结构相似度高</strong>: SSIM 0.873 说明整体结构和内容保持良好</li>
|
||||
<li><strong>码率基本不变</strong>: 从 6.89 → 6.91 Mbps,带宽消耗相近</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; padding: 20px; background: #e7f3ff; border-radius: 10px;">
|
||||
<h3 style="color: #0066cc; margin-bottom: 15px;">💡 技术解释</h3>
|
||||
<p style="color: #004080; line-height: 1.8; margin-bottom: 10px;">
|
||||
<strong>为什么感觉模糊?</strong><br>
|
||||
主要原因是微信视频号将你的 HEVC(H.265)视频重新编码为 H264(H.265 压缩效率更高)。
|
||||
虽然码率几乎相同,但 H264 在相同码率下的画质不如 HEVC,导致细节损失。
|
||||
</p>
|
||||
<p style="color: #004080; line-height: 1.8; margin-bottom: 10px;">
|
||||
<strong>PSNR 和 SSIM 含义:</strong><br>
|
||||
• PSNR > 30 dB: 优秀<br>
|
||||
• 20-30 dB: 有损压缩,可见损失<br>
|
||||
• SSIM > 0.9: 几乎无损<br>
|
||||
• 0.8-0.9: 轻微损失<br>
|
||||
你的视频 PSNR=23.37, SSIM=0.873,属于典型的有损压缩。
|
||||
</p>
|
||||
<p style="color: #004080; line-height: 1.8;">
|
||||
<strong>建议:</strong><br>
|
||||
如果希望保持更好的画质,可以尝试上传前降低原始视频的码率(如 4-5 Mbps),
|
||||
这样微信重新编码时损失会更小。或者直接上传 H264 编码的视频。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 生成22帧的数据
|
||||
const frames = {};
|
||||
for (let i = 1; i <= 22; i++) {
|
||||
const paddedNum = String(i).padStart(3, '0');
|
||||
frames[i] = {
|
||||
original: `original/frame_${paddedNum}.png`,
|
||||
wechat: `wechat/frame_${paddedNum}.png`,
|
||||
time: (i - 1) * 5
|
||||
};
|
||||
}
|
||||
|
||||
// 滑块模式的图片元素(在缩放功能中使用)
|
||||
const originalImg = document.getElementById('originalImage');
|
||||
const wechatImg = document.getElementById('wechatImage');
|
||||
|
||||
// 并排模式的图片元素
|
||||
const originalImgSBS = document.getElementById('originalImageSBS');
|
||||
const wechatImgSBS = document.getElementById('wechatImageSBS');
|
||||
|
||||
let currentMode = 'slider';
|
||||
let currentFrame = 1;
|
||||
let currentZoom = 100;
|
||||
const BASE_HEIGHT = 800; // 基础高度
|
||||
|
||||
// 模式切换
|
||||
document.querySelectorAll('.mode-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const mode = btn.dataset.mode;
|
||||
currentMode = mode;
|
||||
|
||||
// 更新按钮状态
|
||||
document.querySelectorAll('.mode-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
|
||||
// 切换显示模式
|
||||
document.getElementById('sliderMode').style.display = mode === 'slider' ? 'block' : 'none';
|
||||
document.getElementById('sideBySideMode').style.display = mode === 'sidebyside' ? 'block' : 'none';
|
||||
document.getElementById('gridMode').style.display = mode === 'grid' ? 'block' : 'none';
|
||||
|
||||
// 如果是网格模式,生成网格
|
||||
if (mode === 'grid') {
|
||||
generateGrid();
|
||||
} else if (mode === 'sidebyside') {
|
||||
// 切换到并排模式时同步当前帧
|
||||
originalImgSBS.src = frames[currentFrame].original;
|
||||
wechatImgSBS.src = frames[currentFrame].wechat;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 帧选择器
|
||||
document.querySelectorAll('.frame-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const frameNum = parseInt(btn.dataset.frame);
|
||||
currentFrame = frameNum;
|
||||
|
||||
// 更新图片
|
||||
if (currentMode === 'slider') {
|
||||
originalImg.src = frames[frameNum].original;
|
||||
wechatImg.src = frames[frameNum].wechat;
|
||||
// 保持当前缩放级别
|
||||
const newHeight = BASE_HEIGHT * (currentZoom / 100);
|
||||
originalImg.style.height = newHeight + 'px';
|
||||
wechatImg.style.height = newHeight + 'px';
|
||||
} else if (currentMode === 'sidebyside') {
|
||||
originalImgSBS.src = frames[frameNum].original;
|
||||
wechatImgSBS.src = frames[frameNum].wechat;
|
||||
}
|
||||
|
||||
// 更新按钮状态
|
||||
document.querySelectorAll('.frame-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// 缩放功能 - 直接改变图片高度,不使用 transform
|
||||
const zoomSlider = document.getElementById('zoomSlider');
|
||||
const zoomLevel = document.getElementById('zoomLevel');
|
||||
const zoomIn = document.getElementById('zoomIn');
|
||||
const zoomOut = document.getElementById('zoomOut');
|
||||
const zoomReset = document.getElementById('zoomReset');
|
||||
|
||||
function updateZoom(zoom) {
|
||||
currentZoom = Math.max(50, Math.min(200, zoom));
|
||||
const newHeight = BASE_HEIGHT * (currentZoom / 100);
|
||||
|
||||
// 直接改变图片高度
|
||||
originalImg.style.height = newHeight + 'px';
|
||||
wechatImg.style.height = newHeight + 'px';
|
||||
|
||||
zoomSlider.value = currentZoom;
|
||||
zoomLevel.textContent = currentZoom + '%';
|
||||
}
|
||||
|
||||
zoomSlider.addEventListener('input', (e) => {
|
||||
updateZoom(parseInt(e.target.value));
|
||||
});
|
||||
|
||||
zoomIn.addEventListener('click', () => {
|
||||
updateZoom(currentZoom + 10);
|
||||
});
|
||||
|
||||
zoomOut.addEventListener('click', () => {
|
||||
updateZoom(currentZoom - 10);
|
||||
});
|
||||
|
||||
zoomReset.addEventListener('click', () => {
|
||||
updateZoom(100);
|
||||
});
|
||||
|
||||
// 鼠标滚轮缩放(按住 Ctrl/Cmd)
|
||||
const sliderContainer = document.getElementById('sliderContainer');
|
||||
sliderContainer.addEventListener('wheel', (e) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
const delta = e.deltaY > 0 ? -10 : 10;
|
||||
updateZoom(currentZoom + delta);
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
// 生成网格视图 - 使用安全的 DOM 方法
|
||||
function generateGrid() {
|
||||
const gridContainer = document.getElementById('gridContainer');
|
||||
|
||||
// 清空容器
|
||||
while (gridContainer.firstChild) {
|
||||
gridContainer.removeChild(gridContainer.firstChild);
|
||||
}
|
||||
|
||||
// 选择关键帧:0, 15, 30, 45, 60, 75, 90, 105秒
|
||||
const keyFrames = [1, 4, 7, 10, 13, 16, 19, 22];
|
||||
|
||||
keyFrames.forEach(frameNum => {
|
||||
// 原始视频
|
||||
const originalItem = document.createElement('div');
|
||||
originalItem.className = 'grid-item';
|
||||
|
||||
const originalImg = document.createElement('img');
|
||||
originalImg.src = frames[frameNum].original;
|
||||
originalImg.alt = '原始 ' + frames[frameNum].time + '秒';
|
||||
|
||||
const originalLabel = document.createElement('div');
|
||||
originalLabel.className = 'grid-item-label';
|
||||
originalLabel.textContent = '🎬 HEVC';
|
||||
|
||||
const originalTime = document.createElement('div');
|
||||
originalTime.className = 'grid-item-time';
|
||||
originalTime.textContent = frames[frameNum].time + '秒';
|
||||
|
||||
originalItem.appendChild(originalImg);
|
||||
originalItem.appendChild(originalLabel);
|
||||
originalItem.appendChild(originalTime);
|
||||
gridContainer.appendChild(originalItem);
|
||||
|
||||
// 微信视频
|
||||
const wechatItem = document.createElement('div');
|
||||
wechatItem.className = 'grid-item';
|
||||
|
||||
const wechatImg = document.createElement('img');
|
||||
wechatImg.src = frames[frameNum].wechat;
|
||||
wechatImg.alt = '微信 ' + frames[frameNum].time + '秒';
|
||||
|
||||
const wechatLabel = document.createElement('div');
|
||||
wechatLabel.className = 'grid-item-label';
|
||||
wechatLabel.textContent = '📱 H264';
|
||||
|
||||
const wechatTime = document.createElement('div');
|
||||
wechatTime.className = 'grid-item-time';
|
||||
wechatTime.textContent = frames[frameNum].time + '秒';
|
||||
|
||||
wechatItem.appendChild(wechatImg);
|
||||
wechatItem.appendChild(wechatLabel);
|
||||
wechatItem.appendChild(wechatTime);
|
||||
gridContainer.appendChild(wechatItem);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
213
video-comparer/references/configuration.md
Normal file
213
video-comparer/references/configuration.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Script Configuration Reference
|
||||
|
||||
## Contents
|
||||
|
||||
- [Adjustable Constants](#adjustable-constants) - Modifying script behavior
|
||||
- [File Processing Limits](#file-processing-limits) - Size and timeout constraints
|
||||
- [Frame Extraction Settings](#frame-extraction-settings) - Visual comparison parameters
|
||||
- [Configuration Impact](#configuration-impact) - Performance and quality tradeoffs
|
||||
|
||||
## Adjustable Constants
|
||||
|
||||
All configuration constants are defined at the top of `scripts/compare.py`:
|
||||
|
||||
```python
|
||||
ALLOWED_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv', '.webm'}
|
||||
MAX_FILE_SIZE_MB = 500 # Maximum file size per video
|
||||
FFMPEG_TIMEOUT = 300 # FFmpeg timeout (seconds) - 5 minutes
|
||||
FFPROBE_TIMEOUT = 30 # FFprobe timeout (seconds) - 30 seconds
|
||||
BASE_FRAME_HEIGHT = 800 # Frame height for comparison (pixels)
|
||||
FRAME_INTERVAL = 5 # Default extraction interval (seconds)
|
||||
```
|
||||
|
||||
## File Processing Limits
|
||||
|
||||
### MAX_FILE_SIZE_MB
|
||||
|
||||
**Default:** 500 MB
|
||||
|
||||
**Purpose:** Prevents memory exhaustion when processing very large videos.
|
||||
|
||||
**When to increase:**
|
||||
- Working with high-resolution or long-duration source videos
|
||||
- System has ample RAM (16GB+)
|
||||
- Processing 4K or 8K content
|
||||
|
||||
**When to decrease:**
|
||||
- Limited system memory
|
||||
- Processing on lower-spec machines
|
||||
- Batch processing many videos simultaneously
|
||||
|
||||
**Impact:** No effect on output quality, only determines which files can be processed.
|
||||
|
||||
### FFMPEG_TIMEOUT
|
||||
|
||||
**Default:** 300 seconds (5 minutes)
|
||||
|
||||
**Purpose:** Prevents FFmpeg operations from hanging indefinitely.
|
||||
|
||||
**When to increase:**
|
||||
- Processing very long videos (>1 hour)
|
||||
- Extracting many frames (small `--interval` value)
|
||||
- Slow storage (network drives, external HDDs)
|
||||
- High-resolution videos (4K, 8K)
|
||||
|
||||
**Recommended values:**
|
||||
- Short videos (<10 min): 120 seconds
|
||||
- Medium videos (10-60 min): 300 seconds (default)
|
||||
- Long videos (>60 min): 600-900 seconds
|
||||
|
||||
**Impact:** Operation fails if exceeded; does not affect output quality.
|
||||
|
||||
### FFPROBE_TIMEOUT
|
||||
|
||||
**Default:** 30 seconds
|
||||
|
||||
**Purpose:** Prevents metadata extraction from hanging.
|
||||
|
||||
**When to increase:**
|
||||
- Accessing videos over slow network connections
|
||||
- Processing files with complex codec structures
|
||||
- Corrupt or malformed video files
|
||||
|
||||
**Typical behavior:** Metadata extraction usually completes in <5 seconds; longer times suggest file issues.
|
||||
|
||||
**Impact:** Operation fails if exceeded; does not affect output quality.
|
||||
|
||||
## Frame Extraction Settings
|
||||
|
||||
### BASE_FRAME_HEIGHT
|
||||
|
||||
**Default:** 800 pixels
|
||||
|
||||
**Purpose:** Standardizes frame dimensions for side-by-side comparison.
|
||||
|
||||
**When to increase:**
|
||||
- Comparing high-resolution videos (4K, 8K)
|
||||
- Analyzing fine details or subtle compression artifacts
|
||||
- Generating reports for large displays
|
||||
|
||||
**When to decrease:**
|
||||
- Faster processing and smaller HTML output files
|
||||
- Viewing reports on mobile devices or small screens
|
||||
- Limited bandwidth for sharing reports
|
||||
|
||||
**Recommended values:**
|
||||
- Mobile/low-bandwidth: 480-600 pixels
|
||||
- Desktop viewing: 800 pixels (default)
|
||||
- High-detail analysis: 1080-1440 pixels
|
||||
- 4K/8K analysis: 2160+ pixels
|
||||
|
||||
**Impact:** Higher values increase HTML file size and processing time but preserve more detail.
|
||||
|
||||
### FRAME_INTERVAL
|
||||
|
||||
**Default:** 5 seconds
|
||||
|
||||
**Purpose:** Controls frame extraction frequency.
|
||||
|
||||
**When to decrease (extract more frames):**
|
||||
- Analyzing fast-motion content
|
||||
- Detailed temporal analysis needed
|
||||
- Short videos where more samples help
|
||||
|
||||
**When to increase (extract fewer frames):**
|
||||
- Long videos to reduce processing time
|
||||
- Reducing HTML output file size
|
||||
- Overview analysis (general quality check)
|
||||
|
||||
**Recommended values:**
|
||||
- Fast-motion/detailed: 1-3 seconds
|
||||
- Standard analysis: 5 seconds (default)
|
||||
- Long-form content: 10-15 seconds
|
||||
- Quick overview: 30-60 seconds
|
||||
|
||||
**Impact:**
|
||||
- Smaller intervals: More frames, larger HTML, longer processing, more comprehensive analysis
|
||||
- Larger intervals: Fewer frames, smaller HTML, faster processing, may miss transient artifacts
|
||||
|
||||
## Configuration Impact
|
||||
|
||||
### Processing Time
|
||||
|
||||
Processing time is primarily affected by:
|
||||
1. Video duration
|
||||
2. `FRAME_INTERVAL` (smaller = more frames = longer processing)
|
||||
3. `BASE_FRAME_HEIGHT` (higher = more pixels = longer processing)
|
||||
4. System CPU/storage speed
|
||||
|
||||
**Typical processing times:**
|
||||
- 5-minute video, 5s interval, 800px height: ~45-90 seconds
|
||||
- 30-minute video, 5s interval, 800px height: ~3-5 minutes
|
||||
- 60-minute video, 10s interval, 800px height: ~4-7 minutes
|
||||
|
||||
### HTML Output Size
|
||||
|
||||
HTML file size is primarily affected by:
|
||||
1. Number of extracted frames
|
||||
2. `BASE_FRAME_HEIGHT` (higher = larger base64-encoded images)
|
||||
3. Video complexity (detailed frames compress less efficiently)
|
||||
|
||||
**Typical HTML sizes:**
|
||||
- 5-minute video, 5s interval, 800px: 5-10 MB
|
||||
- 30-minute video, 5s interval, 800px: 20-40 MB
|
||||
- 60-minute video, 10s interval, 800px: 30-50 MB
|
||||
|
||||
### Quality vs Performance Tradeoffs
|
||||
|
||||
**High Quality Configuration (detailed analysis):**
|
||||
```python
|
||||
MAX_FILE_SIZE_MB = 2000
|
||||
FFMPEG_TIMEOUT = 900
|
||||
BASE_FRAME_HEIGHT = 1440
|
||||
FRAME_INTERVAL = 2
|
||||
```
|
||||
Use case: Detailed quality analysis, archival comparison, professional codec evaluation
|
||||
|
||||
**Balanced Configuration (default):**
|
||||
```python
|
||||
MAX_FILE_SIZE_MB = 500
|
||||
FFMPEG_TIMEOUT = 300
|
||||
BASE_FRAME_HEIGHT = 800
|
||||
FRAME_INTERVAL = 5
|
||||
```
|
||||
Use case: Standard compression analysis, typical desktop viewing
|
||||
|
||||
**Fast Processing Configuration (quick overview):**
|
||||
```python
|
||||
MAX_FILE_SIZE_MB = 500
|
||||
FFMPEG_TIMEOUT = 180
|
||||
BASE_FRAME_HEIGHT = 600
|
||||
FRAME_INTERVAL = 10
|
||||
```
|
||||
Use case: Batch processing, quick quality checks, mobile viewing
|
||||
|
||||
## Allowed File Extensions
|
||||
|
||||
**Default:** `{'.mp4', '.mov', '.avi', '.mkv', '.webm'}`
|
||||
|
||||
**Purpose:** Restricts input to known video formats.
|
||||
|
||||
**When to modify:**
|
||||
- Adding support for additional container formats (e.g., `.flv`, `.m4v`, `.wmv`)
|
||||
- Restricting to specific formats for workflow standardization
|
||||
|
||||
**Note:** Adding extensions does not guarantee compatibility; FFmpeg must support the codec/container.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**Do NOT modify:**
|
||||
- Path validation logic
|
||||
- Command execution methods (must avoid `shell=True`)
|
||||
- Exception handling patterns
|
||||
|
||||
**Safe to modify:**
|
||||
- Numeric limits (file size, timeouts, dimensions)
|
||||
- Allowed file extensions (add formats supported by FFmpeg)
|
||||
- Output formatting preferences
|
||||
|
||||
**Unsafe modifications:**
|
||||
- Removing path sanitization
|
||||
- Bypassing file validation
|
||||
- Enabling shell command interpolation
|
||||
- Disabling resource limits
|
||||
155
video-comparer/references/ffmpeg_commands.md
Normal file
155
video-comparer/references/ffmpeg_commands.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# FFmpeg Commands Reference
|
||||
|
||||
## Contents
|
||||
|
||||
- [Video Metadata Extraction](#video-metadata-extraction) - Getting video properties with ffprobe
|
||||
- [Frame Extraction](#frame-extraction) - Extracting frames at intervals
|
||||
- [Quality Metrics Calculation](#quality-metrics-calculation) - PSNR, SSIM, VMAF calculations
|
||||
- [Video Information](#video-information) - Duration, resolution, frame rate, bitrate, codec queries
|
||||
- [Image Processing](#image-processing) - Scaling and format conversion
|
||||
- [Troubleshooting](#troubleshooting) - Debugging FFmpeg issues
|
||||
- [Performance Optimization](#performance-optimization) - Speed and resource management
|
||||
|
||||
## Video Metadata Extraction
|
||||
|
||||
### Basic Video Info
|
||||
```bash
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
|
||||
```
|
||||
|
||||
### Stream-specific Information
|
||||
```bash
|
||||
ffprobe -v quiet -select_streams v:0 -print_format json -show_format -show_streams input.mp4
|
||||
```
|
||||
|
||||
### Get Specific Fields
|
||||
```bash
|
||||
ffprobe -v quiet -show_entries format=duration -show_entries stream=width,height,codec_name,r_frame_rate -of csv=p=0 input.mp4
|
||||
```
|
||||
|
||||
## Frame Extraction
|
||||
|
||||
### Extract Frames at Intervals
|
||||
```bash
|
||||
ffmpeg -i input.mp4 -vf "select='not(mod(t\,5))',setpts=N/FRAME_RATE/TB" -vsync 0 output_%03d.jpg
|
||||
```
|
||||
|
||||
### Extract Every Nth Frame
|
||||
```bash
|
||||
ffmpeg -i input.mp4 -vf "select='not(mod(n\,150))',scale=-1:800" -vsync 0 -q:v 2 frame_%03d.jpg
|
||||
```
|
||||
|
||||
### Extract Frames with Timestamp
|
||||
```bash
|
||||
ffmpeg -i input.mp4 -vf "fps=1/5,scale=-1:800" -q:v 2 frame_%05d.jpg
|
||||
```
|
||||
|
||||
## Quality Metrics Calculation
|
||||
|
||||
### PSNR Calculation
|
||||
```bash
|
||||
ffmpeg -i original.mp4 -i compressed.mp4 -lavfi "[0:v][1:v]psnr=stats_file=-" -f null -
|
||||
```
|
||||
|
||||
### SSIM Calculation
|
||||
```bash
|
||||
ffmpeg -i original.mp4 -i compressed.mp4 -lavfi "[0:v][1:v]ssim=stats_file=-" -f null -
|
||||
```
|
||||
|
||||
### Combined PSNR and SSIM
|
||||
```bash
|
||||
ffmpeg -i original.mp4 -i compressed.mp4 -lavfi '[0:v][1:v]psnr=stats_file=-;[0:v][1:v]ssim=stats_file=-' -f null -
|
||||
```
|
||||
|
||||
### VMAF Calculation
|
||||
```bash
|
||||
ffmpeg -i original.mp4 -i compressed.mp4 -lavfi "[0:v][1:v]libvmaf=log_path=vmaf.log" -f null -
|
||||
```
|
||||
|
||||
## Video Information
|
||||
|
||||
### Get Video Duration
|
||||
```bash
|
||||
ffprobe -v quiet -show_entries format=duration -of csv=p=0 input.mp4
|
||||
```
|
||||
|
||||
### Get Video Resolution
|
||||
```bash
|
||||
ffprobe -v quiet -show_entries stream=width,height -of csv=p=0 input.mp4
|
||||
```
|
||||
|
||||
### Get Frame Rate
|
||||
```bash
|
||||
ffprobe -v quiet -show_entries stream=r_frame_rate -of csv=p=0 input.mp4
|
||||
```
|
||||
|
||||
### Get Bitrate
|
||||
```bash
|
||||
ffprobe -v quiet -show_entries format=bit_rate -of csv=p=0 input.mp4
|
||||
```
|
||||
|
||||
### Get Codec Information
|
||||
```bash
|
||||
ffprobe -v quiet -show_entries stream=codec_name,codec_type -of csv=p=0 input.mp4
|
||||
```
|
||||
|
||||
## Image Processing
|
||||
|
||||
### Scale to Fixed Height
|
||||
```bash
|
||||
ffmpeg -i input.jpg -vf "scale=-1:800" output.jpg
|
||||
```
|
||||
|
||||
### Scale to Fixed Width
|
||||
```bash
|
||||
ffmpeg -i input.jpg -vf "scale=1200:-1" output.jpg
|
||||
```
|
||||
|
||||
### High Quality JPEG
|
||||
```bash
|
||||
ffmpeg -i input.jpg -q:v 2 output.jpg
|
||||
```
|
||||
|
||||
### Progressive JPEG
|
||||
```bash
|
||||
ffmpeg -i input.jpg -q:v 2 -progressive output.jpg
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Check FFmpeg Version
|
||||
```bash
|
||||
ffmpeg -version
|
||||
```
|
||||
|
||||
### Check Available Filters
|
||||
```bash
|
||||
ffmpeg -filters
|
||||
```
|
||||
|
||||
### Test Video Decoding
|
||||
```bash
|
||||
ffmpeg -i input.mp4 -f null -
|
||||
```
|
||||
|
||||
### Extract First Frame
|
||||
```bash
|
||||
ffmpeg -i input.mp4 -vframes 1 -q:v 2 first_frame.jpg
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Use Multiple Threads
|
||||
```bash
|
||||
ffmpeg -threads 4 -i input.mp4 -c:v libx264 -preset fast output.mp4
|
||||
```
|
||||
|
||||
### Set Timeout
|
||||
```bash
|
||||
timeout 300 ffmpeg -i input.mp4 -c:v libx264 output.mp4
|
||||
```
|
||||
|
||||
### Limit Memory Usage
|
||||
```bash
|
||||
ffmpeg -i input.mp4 -c:v libx264 -x264-params threads=2:ref=3 output.mp4
|
||||
```
|
||||
97
video-comparer/references/video_metrics.md
Normal file
97
video-comparer/references/video_metrics.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Video Quality Metrics Reference
|
||||
|
||||
## Contents
|
||||
|
||||
- [PSNR (Peak Signal-to-Noise Ratio)](#psnr-peak-signal-to-noise-ratio) - Pixel-level similarity measurement
|
||||
- [SSIM (Structural Similarity Index)](#ssim-structural-similarity-index) - Perceptual quality measurement
|
||||
- [VMAF (Video Multimethod Assessment Fusion)](#vmaf-video-multimethod-assessment-fusion) - Machine learning-based quality prediction
|
||||
- [File Size and Bitrate Considerations](#file-size-and-bitrate-considerations) - Compression targets and guidelines
|
||||
|
||||
## PSNR (Peak Signal-to-Noise Ratio)
|
||||
|
||||
### Definition
|
||||
PSNR measures the ratio between the maximum possible power of a signal and the power of corrupting noise. It's commonly used to measure the quality of reconstruction of lossy compression codecs.
|
||||
|
||||
### Scale
|
||||
- **Range**: Typically 20-50 dB
|
||||
- **Higher is better**: More signal, less noise
|
||||
|
||||
### Quality Interpretation
|
||||
| PSNR (dB) | Quality Level | Use Case |
|
||||
|-----------|---------------|----------|
|
||||
| < 20 | Poor | Unacceptable for most applications |
|
||||
| 20-25 | Low | Acceptable for very low-bandwidth scenarios |
|
||||
| 25-30 | Fair | Basic video streaming |
|
||||
| 30-35 | Good | Standard streaming quality |
|
||||
| 35-40 | Very Good | High-quality streaming |
|
||||
| 40+ | Excellent | Near-lossless quality, archival |
|
||||
|
||||
### Calculation Formula
|
||||
```
|
||||
PSNR = 10 * log10(MAX_I^2 / MSE)
|
||||
```
|
||||
Where:
|
||||
- MAX_I = maximum pixel value (255 for 8-bit images)
|
||||
- MSE = mean squared error
|
||||
|
||||
## SSIM (Structural Similarity Index)
|
||||
|
||||
### Definition
|
||||
SSIM is a perceptual metric that quantifies image quality degradation based on structural information changes rather than pixel-level differences.
|
||||
|
||||
### Scale
|
||||
- **Range**: 0.0 to 1.0
|
||||
- **Higher is better**: More structural similarity
|
||||
|
||||
### Quality Interpretation
|
||||
| SSIM | Quality Level | Use Case |
|
||||
|------|---------------|----------|
|
||||
| < 0.70 | Poor | Visible artifacts, structural damage |
|
||||
| 0.70-0.80 | Fair | Noticeable quality loss |
|
||||
| 0.80-0.90 | Good | Acceptable for most streaming |
|
||||
| 0.90-0.95 | Very Good | High-quality streaming |
|
||||
| 0.95-0.98 | Excellent | Near-identical perception |
|
||||
| 0.98+ | Perfect | Indistinguishable from original |
|
||||
|
||||
### Components
|
||||
SSIM combines three comparisons:
|
||||
1. **Luminance**: Local brightness comparisons
|
||||
2. **Contrast**: Local contrast comparisons
|
||||
3. **Structure**: Local structure correlations
|
||||
|
||||
## VMAF (Video Multimethod Assessment Fusion)
|
||||
|
||||
### Definition
|
||||
VMAF is a machine learning-based metric that predicts subjective video quality by combining multiple quality metrics.
|
||||
|
||||
### Scale
|
||||
- **Range**: 0-100
|
||||
- **Higher is better**: Better perceived quality
|
||||
|
||||
### Quality Interpretation
|
||||
| VMAF | Quality Level | Use Case |
|
||||
|-------|---------------|----------|
|
||||
| < 20 | Poor | Unacceptable |
|
||||
| 20-40 | Low | Basic streaming |
|
||||
| 40-60 | Fair | Standard streaming |
|
||||
| 60-80 | Good | High-quality streaming |
|
||||
| 80-90 | Very Good | Premium streaming |
|
||||
| 90+ | Excellent | Reference quality |
|
||||
|
||||
## File Size and Bitrate Considerations
|
||||
|
||||
### Compression Targets by Use Case
|
||||
| Use Case | Size Reduction | PSNR Target | SSIM Target |
|
||||
|----------|----------------|-------------|-------------|
|
||||
| Social Media | 40-60% | 35-40 dB | 0.95-0.98 |
|
||||
| Streaming | 50-70% | 30-35 dB | 0.90-0.95 |
|
||||
| Archival | 20-40% | 40+ dB | 0.98+ |
|
||||
| Mobile | 60-80% | 25-30 dB | 0.85-0.90 |
|
||||
|
||||
### Bitrate Guidelines
|
||||
| Resolution | Target Bitrate (1080p equivalent) |
|
||||
|------------|-----------------------------------|
|
||||
| 480p | 1-2 Mbps |
|
||||
| 720p | 2-5 Mbps |
|
||||
| 1080p | 5-10 Mbps |
|
||||
| 4K | 20-50 Mbps |
|
||||
1036
video-comparer/scripts/compare.py
Executable file
1036
video-comparer/scripts/compare.py
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user