Compare commits

...

2 Commits

Author SHA1 Message Date
0d9c00ea29 update readme and context
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m11s
2026-02-09 21:16:22 -05:00
fcc777ae7c add compression slider 2026-02-09 21:08:44 -05:00
4 changed files with 626 additions and 47 deletions

426
PROJECT_CONTEXT.md Normal file
View File

@@ -0,0 +1,426 @@
# 📘 Project Context for AI Assistants
This document provides technical context for AI assistants working on this video editor project. It covers architecture decisions, implementation patterns, and key code locations.
---
## 🏗️ Architecture Overview
### Tech Stack
- **Backend**: Flask 3.0 (Python 3.11+)
- **Video Engine**: FFmpeg with libx264 encoder
- **Frontend**: Vanilla JavaScript (no frameworks)
- **Deployment**: Python local / Docker / Docker Compose
### Request Flow
```
1. User uploads video → /upload endpoint
2. Server assigns UUID, stores in uploads/ dir
3. User edits via UI → sends params to /process
4. FFmpeg processes with filters → outputs/ dir
5. User downloads → /download/<file_id> endpoint
6. Cleanup → /cleanup/<file_id> removes temp files
```
### File Structure
```
c:\Projects\editor/
├── app.py # Flask backend, FFmpeg integration
├── templates/
│ └── index.html # Single-page frontend app
├── requirements.txt # Python dependencies
├── uploads/ # Temporary uploaded files (created on first run)
├── outputs/ # Processed video outputs (created on first run)
├── Dockerfile # Container image definition
├── docker-compose.yml # Local development compose
├── prod-compose.yml # Production registry-pull compose
├── .gitea/
│ └── workflows/
│ └── rebuild-prod.yaml # CI/CD build and deployment
├── README.md # User documentation
└── PROJECT_CONTEXT.md # This file (technical context)
```
---
## 🔑 Key Implementation Details
### 1. Video Processing (`app.py`)
#### FFmpeg Filter Chain
The app builds a complex FFmpeg filter chain dynamically based on user selections:
```python
# Base filters array
filters = []
# Rotation (if not 0°)
if rotate and rotate != '0':
# transpose values: 1=90°CW, 2=90°CCW, 3=180° (using 2,2)
if rotate == '90':
filters.append('transpose=1')
elif rotate == '180':
filters.append('transpose=2,transpose=2')
elif rotate == '270':
filters.append('transpose=2')
# Crop (if specified)
if crop_w and crop_h:
filters.append(f'crop={crop_w}:{crop_h}:{crop_x}:{crop_y}')
# Scale with even dimension enforcement
if scale_percentage < 100:
# trunc(value/2)*2 ensures dimensions are divisible by 2 (H.264 requirement)
filters.append(f'scale=trunc(iw*{scale_percentage/100}/2)*2:trunc(ih*{scale_percentage/100}/2)*2')
# Final filter string
filter_str = ','.join(filters)
```
**Critical**: The `trunc(iw*percentage/100/2)*2` pattern ensures output dimensions are always even numbers. H.264 encoder requires width and height divisible by 2.
#### File ID System
- UUID4 generates unique IDs for each upload
- `original_filenames` dict maps UUID → original filename
- Downloads use: `{original_name}_processed_{timestamp}.mp4`
- Timestamp format: `YYYYMMDD_HHMMSS` (e.g., `20240315_143022`)
#### CRF Quality Control
- CRF (Constant Rate Factor) range: 18-32
- Lower CRF = higher quality, larger file
- Default: 23 (balanced)
- Passed directly to FFmpeg: `-crf {quality}`
### 2. Visual Crop Selection (`index.html`)
#### Canvas Overlay Technique
```javascript
// Create canvas overlay matching video display size
cropCanvas.width = video.offsetWidth;
cropCanvas.height = video.offsetHeight;
// Calculate scale factors for actual video pixels
const scaleX = video.videoWidth / video.offsetWidth;
const scaleY = video.videoHeight / video.offsetHeight;
// Mouse drag draws rectangle on canvas
// Convert canvas coordinates → actual video pixels
const actualX = Math.round(rectX * scaleX);
const actualY = Math.round(rectY * scaleY);
const actualW = Math.round(rectW * scaleX);
const actualH = Math.round(rectH * scaleY);
```
**Why this works**: Video element display size ≠ actual video resolution. Scale factors convert screen pixels to video pixels for accurate crop commands.
#### Dynamic Button Creation
The crop button is dynamically injected into video-info after video loads:
```javascript
function setupCropButton(video, canvas) {
const videoInfoDiv = document.getElementById('video-info');
// Create button HTML
const cropButtonHtml = `
<button id="draw-crop-btn" class="btn btn-primary">
🎯 Click to Draw Crop Area
</button>
`;
// Inject after codec paragraph
videoInfoDiv.innerHTML += cropButtonHtml;
// Add event listener to new button
document.getElementById('draw-crop-btn').addEventListener('click', () => {
enableCropDrawing(video, canvas);
});
}
```
**Caution**: Using `innerHTML +=` recreates all child elements. Event listeners must be re-attached after injection.
### 3. Trim Controls
#### Video Position Capture
Instead of complex timeline scrubbing, uses simple current position capture:
```javascript
// "Set Start Here" button
video.currentTime // Gets current playback position in seconds
// Display position in real-time
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
const ms = Math.floor((seconds % 1) * 100);
return `${mins}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(2, '0')}`;
}
```
**Design Decision**: Timeline scrubbers added complexity without improving UX. Position-based buttons are more intuitive.
### 4. Scale and Dimension Display
#### Live Output Resolution Preview
```javascript
// Calculate what output dimensions will be
let outputW = videoWidth;
let outputH = videoHeight;
// If cropped, use crop dimensions
if (cropW && cropH) {
outputW = cropW;
outputH = cropH;
}
// Apply scale percentage
outputW = Math.round(outputW * (scalePercentage / 100));
outputH = Math.round(outputH * (scalePercentage / 100));
// Force even dimensions (H.264 compatibility)
if (outputW % 2 !== 0) outputW++;
if (outputH % 2 !== 0) outputH++;
// Display to user
document.getElementById('output-resolution').textContent = `${outputW}x${outputH}`;
```
**Important**: Frontend preview must match backend FFMPEG logic exactly, or users see incorrect dimensions.
---
## 🎨 UI/UX Patterns
### Feature Ordering Logic
Features are ordered by typical editing workflow:
1. **Trim** - Select video segment (temporal)
2. **Crop** - Select video area (spatial)
3. **Output Scale** - Apply to trimmed/cropped result
4. **Compression Quality** - Final output quality
5. **Quick Actions** - Rotate/mute (can apply anytime)
### Reset Functionality
Complete reset clears:
- All input values (trim, crop, scale, quality)
- Canvas drawings (crop rectangle)
- Video preview (remove src)
- Button states (rotate, mute)
- Display texts (resolution, filename, time)
- Section visibility (hide edit/download sections)
```javascript
// Comprehensive reset
video.src = '';
cropCanvas.width = 0;
cropCanvas.height = 0;
document.querySelectorAll('input[type="number"]').forEach(input => input.value = '');
document.getElementById('scale-slider').value = 100;
document.getElementById('mute-audio').classList.remove('active');
// ... etc
```
### Button State Management
Visual feedback for toggles:
```javascript
// Mute audio toggle
muteBtn.classList.toggle('active');
muteBtn.textContent = muted ? '🔇 Audio Muted' : '🔊 Mute Audio';
// Rotation state
document.getElementById('rotation-display').textContent =
rotationAngle === 0 ? '' : `Rotated ${rotationAngle}°`;
```
---
## 🐳 Deployment Configurations
### Local Development
```bash
# Python
python app.py # Runs on localhost:5000
# Docker
docker-compose up --build
```
### Production (CI/CD)
`.gitea/workflows/rebuild-prod.yaml`:
1. Triggered on push to main
2. Builds Docker image with FFmpeg
3. Pushes to `reg.dev.nervesocket.com/video-editor:latest`
4. Calls Portainer API to redeploy container
5. Uses production compose pulling from registry
**Registry**: Private registry at `reg.dev.nervesocket.com`
**Deployment**: Portainer API with webhook ID `df5c509c-8002-463e-b009-3b05b8a1e2c3`
---
## 🧩 Common Extension Patterns
### Adding New FFmpeg Filter
1. Add UI control in `index.html` (slider, button, input)
2. Send parameter with form submission (line ~400)
3. Extract in `app.py` process_video: `request.form.get('param_name')`
4. Add filter to filters array: `filters.append('filter_name=value')`
5. Ensure filter order is logical (crop before scale, etc.)
### Adding New Quick Action
1. Create button in Quick Actions section
2. Add click handler setting global variable
3. Send parameter in POST to /process
4. Handle in backend with FFmpeg flag or filter
5. Add reset logic to resetApp()
### Handling New Video Codec Output
Currently hardcoded to H.264/AAC. To add codec selection:
1. Add codec dropdown in UI
2. Pass codec parameter to backend
3. Conditional FFmpeg args based on codec:
```python
if codec == 'h264':
cmd.extend(['-c:v', 'libx264', '-crf', quality])
elif codec == 'h265':
cmd.extend(['-c:v', 'libx265', '-crf', quality])
```
---
## 🔍 Debugging Tips
### FFmpeg Command Inspection
The app prints full FFmpeg command to console:
```python
print("FFmpeg command:", ' '.join(cmd))
subprocess.run(cmd, check=True)
```
Copy this command and run manually to debug encoding issues.
### Canvas Coordinate Debugging
Add console logs in mouse handlers:
```javascript
console.log('Canvas coords:', x, y);
console.log('Video coords:', Math.round(x * scaleX), Math.round(y * scaleY));
```
### Processing Failures
Common issues:
- **Odd dimensions**: Backend now handles with trunc(), shouldn't occur
- **Invalid crop**: Crop exceeds video bounds (validate in frontend)
- **Missing FFmpeg**: Docker image includes it, but local Python needs manual install
- **Unsupported format**: Check FFmpeg support with `ffmpeg -formats`
---
## 📦 Dependencies
### Python (requirements.txt)
```
Flask==3.0.0
Werkzeug==3.0.1
```
### System
- **FFmpeg**: Must be installed and in PATH
- On Debian/Ubuntu: `apt-get install ffmpeg`
- On Windows: Download from ffmpeg.org
- Docker: Installed in Dockerfile
### FFmpeg Codecs Used
- **Video**: libx264 (H.264/AVC)
- **Audio**: aac (AAC)
- **Filters**: crop, scale, transpose
---
## 🚨 Critical Implementation Notes
### 1. H.264 Even Dimension Requirement
**Must enforce**: Width and height divisible by 2
- Backend: `trunc(iw*scale/2)*2`
- Frontend: `if (dim % 2 !== 0) dim++;`
- Violation causes FFmpeg error: "width not divisible by 2"
### 2. File Cleanup
Currently, files are NOT auto-deleted. Manual cleanup endpoint exists but:
- `/cleanup/<file_id>` removes files
- Consider adding auto-cleanup timer (e.g., 1 hour after upload)
- Or cron job to clean old files
### 3. Memory Usage
`original_filenames` dict grows indefinitely in memory. For production:
- Persist to Redis/database
- Or tie to file existence (check disk, rebuild dict on startup)
### 4. Security Considerations
Current implementation is for trusted users:
- No file size limits (could DOS server)
- No rate limiting
- No authentication
- FFmpeg runs with user permissions (subprocess)
- For public deployment, add nginx upload limits, Flask rate limiting, user sessions
### 5. Browser Compatibility
- **Canvas API**: IE not supported (use Chrome/Firefox/Edge)
- **Video Preview**: Requires browser codec support (H.264 widely supported)
- **File API**: Drag-and-drop requires modern browser
---
## 🎯 Design Philosophy Explained
### Why No Framework?
- **Simplicity**: Single HTML file, no build process
- **Learning**: Clear vanilla JS patterns
- **Deployment**: No npm, no bundling, just Python + FFmpeg
- **Performance**: No framework overhead for simple app
### Why Percentage-Based Scaling?
- **Intuitive**: "50% smaller" clearer than "calculate dimensions"
- **Flexible**: Works regardless of input resolution
- **Safer**: Avoids invalid dimensions (too large/small)
- **Chaining**: Works naturally after crop (scale the crop, not original)
### Why CRF Instead of Bitrate?
- **Quality-based**: Maintains consistent quality across different content
- **Simpler**: One slider, no need to understand bitrate implications
- **Better results**: Variable bitrate adapts to video complexity
- **Industry standard**: CRF 18-23 is professional range
---
## 🔮 Future Enhancement Considerations
If extending this project, consider:
1. **Multiple Video Support**: Concatenation, side-by-side
2. **Audio Controls**: Volume adjustment, audio replacement
3. **Filters**: Brightness, contrast, saturation, blur
4. **Watermarks**: Text or image overlays
5. **Format Selection**: WebM, GIF, HEVC output options
6. **Batch Processing**: Queue multiple jobs
7. **Progress Bar**: Real-time FFmpeg encoding progress (parse stderr)
8. **Undo/Redo**: State management for edit history
9. **Presets**: Save common settings as named presets
10. **Cloud Storage**: S3/Azure integration for large files
---
## 📞 Getting Help
When asking for assistance:
1. **Describe the feature**: User-facing behavior first
2. **Specify location**: Which file/function needs changes
3. **FFmpeg context**: If encoding-related, mention codec/format
4. **Show errors**: Full error messages help diagnose issues
5. **Test command**: If FFmpeg fails, what command would work manually?
Example good request:
> "I want to add a brightness filter. Users should have a slider in the UI from -1.0 to 1.0. This should apply the FFmpeg `eq=brightness=X` filter before the scale filter. The parameter should be called 'brightness' and default to 0.0."
---
**Last Updated**: 2024-03-15 (after adding compression quality slider and filename preservation)
**Current Version**: Fully functional simple video editor with 7 main features (trim, crop, scale, quality, rotation, mute, visual controls)

180
README.md
View File

@@ -1,15 +1,52 @@
# 🎬 Simple Video Editor # 🎬 Simple Video Editor
A lightweight, web-based video editor with an intuitive interface for basic video editing tasks. Built with Flask and FFmpeg, it allows you to trim, crop, resize, and convert videos to H.264 MP4 format. A lightweight, web-based video editor with an intuitive interface for basic video editing tasks. Built with Flask and FFmpeg, it allows you to trim, crop, scale, rotate, and convert videos to H.264 MP4 format with interactive visual controls.
## ✨ Features ## ✨ Features
### Video Input/Output
- **📹 Video Upload**: Drag and drop or browse to upload videos (supports MP4, AVI, MOV, MKV, WMV, FLV, WebM) - **📹 Video Upload**: Drag and drop or browse to upload videos (supports MP4, AVI, MOV, MKV, WMV, FLV, WebM)
- **⏱️ Trim**: Cut videos by specifying start and end times
- **✂️ Crop**: Crop videos by defining X/Y position and dimensions
- **📐 Resize**: Change output resolution with preset options (4K, 1080p, 720p, 480p) or custom dimensions
- **🎯 H.264 Export**: All videos are exported as H.264 MP4 with optimized settings - **🎯 H.264 Export**: All videos are exported as H.264 MP4 with optimized settings
- **🖥️ Intuitive UI**: Clean, modern interface with real-time video preview - **💾 Smart Filenames**: Downloads preserve original filename with "_processed" suffix and unique timestamp
- **📊 Video Info**: Displays resolution, duration, codec details
### Editing Features
#### ⏱️ Trim Video
- **Visual Time Selection**: Play video and click buttons to set trim points at current playback position
- **Live Time Display**: Shows current video position in real-time (MM:SS.ms format)
- **Manual Input**: Fine-tune start/end times with precise second inputs
#### ✂️ Crop Video
- **Interactive Canvas Selection**: Click and draw directly on video to select crop area
- **Visual Feedback**: Semi-transparent overlay shows what will be removed
- **Real-time Dimensions**: Displays actual video pixel dimensions while dragging
- **Manual Input**: Enter exact crop position (X/Y) and dimensions
- **Automatic Adjustment**: Ensures even dimensions for H.264 compatibility
#### 📐 Output Scale
- **Percentage-based Scaling**: Scale from 10% to 100% in 5% increments
- **Preset Options**: Quick buttons for 100%, 75%, 50%, 25%
- **Smart Cropping**: Scale applies to cropped area (if crop selected) or original video
- **Live Preview**: Shows final output resolution with even dimensions
#### 💾 Compression Quality
- **CRF Control**: Adjust H.264 quality from CRF 18 (best) to 32 (smallest)
- **Smart Presets**: Best Quality, High, Balanced, Low, Smallest File
- **Visual Feedback**: Shows quality label and CRF value in real-time
- **File Size Control**: Balance between quality and file size
#### ⚡ Quick Actions
- **🔇 Mute Audio**: Remove audio track completely
- **🔄 Rotate**: Rotate video 90°, 180°, 270° or back to original
- **Visual State**: Buttons show active state and rotation amount
### User Experience
- **🖥️ Centered Video Preview**: Large, centered video display for easier editing
- **📱 Responsive Design**: Clean, modern gradient interface
- **🎨 Visual Feedback**: Button states, loading indicators, error messages
- **🔄 Reset Controls**: Start over or process another video with clean state
- **💡 Helpful Hints**: Contextual tips explain how each feature works
## 🚀 Quick Start ## 🚀 Quick Start
@@ -32,26 +69,41 @@ A lightweight, web-based video editor with an intuitive interface for basic vide
``` ```
3. **Run the Application**: 3. **Run the Application**:
```powershell The editor provides features in logical order:
python app.py
```
4. **Open in Browser**: #### 1. Trim Video
Navigate to `http://localhost:5000` - **Play the video** and pause at desired point
- Click **"⏮️ Set Start Here"** to mark the beginning
- Play to the end point and click **"⏭️ Set End Here"**
- Current video position shows in real-time above buttons
- Fine-tune with manual second inputs if needed
### Option 2: Run with Docker #### 2. Crop Video
- Click **"🎯 Click to Draw Crop Area"** button (under video info)
- **Click and drag** on the video to select the area to keep
- Purple rectangle shows selected area with pixel dimensions
- Darkened area shows what will be removed
- Release mouse to apply crop coordinates
- Or manually enter X/Y position and width/height values
#### Prerequisites #### 3. Output Scale
- Docker installed - Use slider to scale from 10% to 100%
- Docker Compose installed - Click preset buttons for quick scaling (100%, 75%, 50%, 25%)
- Scale applies to cropped area, or full video if no crop
- Example: 200x400 crop at 50% = 100x200 output
- Dimensions automatically rounded to even numbers for H.264
#### Steps #### 4. Compression Quality
- Adjust slider from CRF 18 (best quality) to 32 (smallest file)
1. **Build and Run**: - Default is CRF 23 (balanced)
```bash - Lower CRF = larger files, better quality
docker-compose up --build - Higher CRF = smaller files, more compression
``` - Use presets: Best Quality, High, Balanced, Low, Smallest File
#### 5. Quick Actions
- **Mute Audio**: Click to remove audio track entirely
- **Rotate 90°**: Click repeatedly to rotate (90° → 180° → 270° → 0°)
- Buttons show active state when applied
2. **Open in Browser**: 2. **Open in Browser**:
Navigate to `http://localhost:5000` Navigate to `http://localhost:5000`
@@ -88,15 +140,19 @@ A lightweight, web-based video editor with an intuitive interface for basic vide
- **Y Position**: Vertical starting point for crop (0 = top edge) - **Y Position**: Vertical starting point for crop (0 = top edge)
- **Crop Width**: Width of the cropped area - **Crop Width**: Width of the cropped area
- **Crop Height**: Height of the cropped area - **Crop Height**: Height of the cropped area
- Leave empty to skip cropping - Leave eUser-adjustable (18-32, default 23)
- Audio codec: AAC at 128kbps (unless muted)
### Step 3: Process and Download - Rotation: FFmpeg transpose filter
- Click **"🎬 Process Video"** to start encoding - Even dimensions: Automatic rounding for H.264 compatibility
- Wait for processing to complete (time depends on video length and settings) - Fast start enabled for web streaming
- Click **"⬇️ Download Video"** to save the processed file
## 🛠️ Technical Details
### Frontend
- Pure HTML, CSS, and JavaScript (no frameworks)
- HTML5 Canvas for interactive crop selection
- Real-time video metadata display
- Responsive gradient design
- Drag-and-drop file upload
- Live updating scales and quality indicators
### Backend ### Backend
- **Framework**: Flask 3.0 - **Framework**: Flask 3.0
- **Video Processing**: FFmpeg with libx264 encoder - **Video Processing**: FFmpeg with libx264 encoder
@@ -118,10 +174,17 @@ A lightweight, web-based video editor with an intuitive interface for basic vide
``` ```
editor/ editor/
├── app.py # Flask backend application ├── app.py # Flask backend application
├── templates/ The quality is now user-adjustable via the UI. Default backend settings in `app.py`:
│ └── index.html # Frontend UI ```python
├── requirements.txt # Python dependencies '-crf', str(quality), # User-selected CRF (18-32)
├── Dockerfile # Docker image configuration '-preset', 'medium', # ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
```
### Scaling and Dimensions
Scale is percentage-based and ensures H.264 compatibility:
```python
# Force even dimensions
scale_filter = f"scale=trunc(iw*{scale_percentage/100}/2)*2:trunc(ih*{scale_percentage/100}/2)*2"
├── docker-compose.yml # Docker Compose configuration ├── docker-compose.yml # Docker Compose configuration
├── .gitignore # Git ignore rules ├── .gitignore # Git ignore rules
├── uploads/ # Temporary upload storage (auto-created) ├── uploads/ # Temporary upload storage (auto-created)
@@ -131,6 +194,11 @@ editor/
## 🔧 Configuration ## 🔧 Configuration
### File Size Limit ### File Size Limit
- Odd dimensions error: The app now auto-rounds to even dimensions
### Crop selection not accurate
- The displayed dimensions while dragging show actual video pixels (not screen pixels)
- Canvas automatically scales between display size and actual video resolution
Edit in `app.py`: Edit in `app.py`:
```python ```python
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024 # 500MB app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024 # 500MB
@@ -159,21 +227,40 @@ Edit in `app.py` (process_video function):
- Check FFmpeg console output in the terminal - Check FFmpeg console output in the terminal
### Upload fails ### Upload fails
- Verify file size is under 500MB - Ve<56> Feature Workflow Examples
- Check file format is supported
- Ensure `uploads/` folder has write permissions
## 🔒 Security Considerations ### Example 1: Create Social Media Clip
1. Upload full-length video
2. Play and set start/end to extract 15-second segment
3. Draw crop to make it vertical (9:16)
4. Scale to 50% for smaller file
5. Set quality to "High" (CRF 21)
6. Process and download
**Note**: This application is designed for local or self-hosted use. For production deployment: ### Example 2: Fix Phone Video
1. Upload sideways phone video
2. Click "Rotate 90°" once or multiple times
3. Optionally crop edges
4. Keep scale at 100%
5. Set quality to "Balanced" (CRF 23)
6. Process and download
1. Add authentication/authorization ### Example 3: Create Silent GIF-style Video
2. Implement rate limiting 1. Upload video
3. Validate and sanitize all inputs 2. Trim to desired segment
4. Add HTTPS support 3. Click "Mute Audio"
5. Configure proper CORS policies 4. Scale to 25% for tiny file size
6. Implement file cleanup routines 5. Set quality to "Low" (CRF 26)
7. Add virus scanning for uploaded files 6. Process for small, silent video
## 🎯 Design Philosophy
This editor prioritizes:
- **Simplicity**: No complex timeline or multi-track editing
- **Visual Feedback**: See exactly what you're doing with live previews
- **Speed**: Quick single-video processing without render queues
- **Accessibility**: No installation needed (web-based)
- **Self-hosting**: Run locally or deploy privately with Docker
## 📝 License ## 📝 License
@@ -181,6 +268,11 @@ This project is provided as-is for personal and educational use.
## 🤝 Contributing ## 🤝 Contributing
Feel free to fork, modify, and submit pull requests for improvements!
This project is provided as-is for personal and educational use.
## 🤝 Contributing
Feel free to fork, modify, and submit pull requests for improvements! Feel free to fork, modify, and submit pull requests for improvements!
## 💡 Future Enhancements ## 💡 Future Enhancements

7
app.py
View File

@@ -157,11 +157,16 @@ def process_video():
if mute_audio: if mute_audio:
cmd.extend(['-an']) # Remove audio cmd.extend(['-an']) # Remove audio
# Get quality (CRF value)
quality = data.get('quality', 23)
# Ensure quality is within valid range (18-32)
quality = max(18, min(32, int(quality)))
# Output settings for H.264 MP4 # Output settings for H.264 MP4
cmd.extend([ cmd.extend([
'-c:v', 'libx264', '-c:v', 'libx264',
'-preset', 'medium', '-preset', 'medium',
'-crf', '23', '-crf', str(quality),
'-c:a', 'aac', '-c:a', 'aac',
'-b:a', '128k', '-b:a', '128k',
'-movflags', '+faststart', '-movflags', '+faststart',

View File

@@ -588,6 +588,28 @@
</div> </div>
</div> </div>
<!-- Compression Quality Controls -->
<div class="form-group">
<label>💾 Compression Quality</label>
<div class="scale-slider-container">
<div class="scale-display">
<span>Quality: <span class="scale-value" id="quality-value-display">High</span></span>
<span style="color: #6c757d;">CRF: <strong id="crf-value-display">23</strong></span>
</div>
<input type="range" class="scale-slider" id="quality-slider" min="18" max="32" value="23" step="1">
<div class="resolution-preview">
<p style="margin: 0;">💡 <strong>Lower CRF</strong> = Better quality, larger file. <strong>Higher CRF</strong> = More compressed, smaller file.</p>
</div>
<div class="preset-resolutions" style="margin-top: 15px;">
<button class="preset-btn" data-quality="18">Best Quality</button>
<button class="preset-btn" data-quality="21">High</button>
<button class="preset-btn" data-quality="23">Balanced</button>
<button class="preset-btn" data-quality="26">Low</button>
<button class="preset-btn" data-quality="30">Smallest File</button>
</div>
</div>
</div>
<!-- Quick Actions --> <!-- Quick Actions -->
<div class="form-group"> <div class="form-group">
<label>⚡ Quick Actions</label> <label>⚡ Quick Actions</label>
@@ -632,6 +654,7 @@
let scalePercentage = 100; let scalePercentage = 100;
let rotationDegrees = 0; let rotationDegrees = 0;
let muteAudio = false; let muteAudio = false;
let compressionQuality = 23;
// Upload area handling // Upload area handling
const uploadArea = document.getElementById('upload-area'); const uploadArea = document.getElementById('upload-area');
@@ -935,6 +958,9 @@
const scaleSlider = document.getElementById('scale-slider'); const scaleSlider = document.getElementById('scale-slider');
const scaleValueDisplay = document.getElementById('scale-value-display'); const scaleValueDisplay = document.getElementById('scale-value-display');
const outputResolution = document.getElementById('output-resolution'); const outputResolution = document.getElementById('output-resolution');
const qualitySlider = document.getElementById('quality-slider');
const qualityValueDisplay = document.getElementById('quality-value-display');
const crfValueDisplay = document.getElementById('crf-value-display');
function updateScaleDisplay() { function updateScaleDisplay() {
scalePercentage = parseInt(scaleSlider.value); scalePercentage = parseInt(scaleSlider.value);
@@ -953,14 +979,40 @@
scaleSlider.addEventListener('input', updateScaleDisplay); scaleSlider.addEventListener('input', updateScaleDisplay);
// Quality slider
function updateQualityDisplay() {
compressionQuality = parseInt(qualitySlider.value);
crfValueDisplay.textContent = compressionQuality;
// Update quality label
let qualityLabel = 'Balanced';
if (compressionQuality <= 19) qualityLabel = 'Best Quality';
else if (compressionQuality <= 22) qualityLabel = 'High';
else if (compressionQuality <= 25) qualityLabel = 'Balanced';
else if (compressionQuality <= 28) qualityLabel = 'Low';
else qualityLabel = 'Smallest File';
qualityValueDisplay.textContent = qualityLabel;
}
qualitySlider.addEventListener('input', updateQualityDisplay);
updateQualityDisplay();
// Scale presets // Scale presets
document.querySelectorAll('.preset-btn').forEach(btn => { document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
const scale = btn.dataset.scale; const scale = btn.dataset.scale;
const quality = btn.dataset.quality;
if (scale) { if (scale) {
scaleSlider.value = scale; scaleSlider.value = scale;
updateScaleDisplay(); updateScaleDisplay();
} }
if (quality) {
qualitySlider.value = quality;
updateQualityDisplay();
}
}); });
}); });
@@ -984,7 +1036,8 @@
end_time: endTime, end_time: endTime,
scale_percentage: scalePercentage, scale_percentage: scalePercentage,
rotation: rotationDegrees, rotation: rotationDegrees,
mute_audio: muteAudio mute_audio: muteAudio,
quality: compressionQuality
}; };
if (cropWidth > 0 && cropHeight > 0) { if (cropWidth > 0 && cropHeight > 0) {
@@ -1048,8 +1101,9 @@
rotationDegrees = 0; rotationDegrees = 0;
muteAudio = false; muteAudio = false;
scalePercentage = 100; scalePercentage = 100;
compressionQuality = 23;
videoPreview.src = ''; videoPreview.src = '';;
videoInput.value = ''; videoInput.value = '';
clearCropCanvas(); clearCropCanvas();
cropCanvas.classList.remove('active'); cropCanvas.classList.remove('active');
@@ -1065,6 +1119,8 @@
document.getElementById('scale-value-display').textContent = '100%'; document.getElementById('scale-value-display').textContent = '100%';
document.getElementById('output-resolution').textContent = '--'; document.getElementById('output-resolution').textContent = '--';
document.getElementById('current-time-display').textContent = '0:00'; document.getElementById('current-time-display').textContent = '0:00';
document.getElementById('quality-slider').value = '23';
updateQualityDisplay();
// Reset quick actions // Reset quick actions
document.getElementById('mute-audio-btn').classList.remove('active'); document.getElementById('mute-audio-btn').classList.remove('active');