diff --git a/app.py b/app.py index 8b5f3d7..4f96d7b 100644 --- a/app.py +++ b/app.py @@ -124,13 +124,12 @@ def process_video(): if width and height: filters.append(f"crop={width}:{height}:{x}:{y}") - # Add scale filter if resolution specified - resolution = data.get('resolution') - if resolution: - width = resolution.get('width') - height = resolution.get('height') - if width and height: - filters.append(f"scale={width}:{height}") + # Add scale filter if scale percentage specified (not 100%) + scale_percentage = data.get('scale_percentage', 100) + if scale_percentage != 100: + # Scale based on percentage - ffmpeg will apply this after crop + scale_filter = f"scale=iw*{scale_percentage/100}:ih*{scale_percentage/100}" + filters.append(scale_filter) # Apply filters if any if filters: diff --git a/templates/index.html b/templates/index.html index 720d6ca..83a17c4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -184,6 +184,63 @@ font-weight: 600; } + .scale-slider-container { + margin: 15px 0; + } + + .scale-slider { + width: 100%; + height: 8px; + border-radius: 4px; + background: #e9ecef; + outline: none; + -webkit-appearance: none; + } + + .scale-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + cursor: pointer; + border: 2px solid white; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + } + + .scale-slider::-moz-range-thumb { + width: 20px; + height: 20px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + cursor: pointer; + border: 2px solid white; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + } + + .scale-display { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + } + + .scale-value { + font-size: 1.2em; + font-weight: 600; + color: #667eea; + } + + .resolution-preview { + background: white; + padding: 10px; + border-radius: 6px; + margin-top: 10px; + font-size: 0.9em; + color: #495057; + } + .timeline-container { margin: 15px 0; padding: 20px; @@ -513,24 +570,23 @@ - +
- -
- - - - - -
-
-
- - + +
+
+ Scale: 100% + Output: --
-
- - + +
+

💡 How it works: Scale is applied to the cropped area (or original if no crop). At 50%, a 200x400 crop becomes 100x200.

+
+
+ + + +
@@ -595,6 +651,7 @@ let isDraggingEnd = false; let timelineStartPos = 0; let timelineEndPos = 100; + let scalePercentage = 100; // Upload area handling const uploadArea = document.getElementById('upload-area'); @@ -674,6 +731,9 @@ // Set default values document.getElementById('end-time').value = videoInfo.duration.toFixed(2); + scalePercentage = 100; + scaleSlider.value = 100; + updateScaleDisplay(); // Setup canvas for cropping setupCropCanvas(); @@ -915,26 +975,47 @@ updateTimeline(); }); - // Resolution presets + // Scale slider + const scaleSlider = document.getElementById('scale-slider'); + const scaleValueDisplay = document.getElementById('scale-value-display'); + const outputResolution = document.getElementById('output-resolution'); + + function updateScaleDisplay() { + scalePercentage = parseInt(scaleSlider.value); + scaleValueDisplay.textContent = scalePercentage + '%'; + + // Calculate output resolution + const cropWidth = parseInt(document.getElementById('crop-width').value) || videoInfo.width; + const cropHeight = parseInt(document.getElementById('crop-height').value) || videoInfo.height; + + const outputWidth = Math.round((cropWidth * scalePercentage) / 100); + const outputHeight = Math.round((cropHeight * scalePercentage) / 100); + + outputResolution.textContent = `${outputWidth}x${outputHeight}`; + } + + scaleSlider.addEventListener('input', updateScaleDisplay); + + // Scale presets document.querySelectorAll('.preset-btn').forEach(btn => { btn.addEventListener('click', () => { - document.querySelectorAll('.preset-btn').forEach(b => b.classList.remove('active')); - btn.classList.add('active'); - - const width = btn.dataset.width; - const height = btn.dataset.height; - - document.getElementById('width').value = width === '0' ? '' : width; - document.getElementById('height').value = height === '0' ? '' : height; + const scale = btn.dataset.scale; + if (scale) { + scaleSlider.value = scale; + updateScaleDisplay(); + } }); }); + // Update scale display when crop values change + ['crop-width', 'crop-height'].forEach(id => { + document.getElementById(id).addEventListener('input', updateScaleDisplay); + }); + // Process video document.getElementById('process-btn').addEventListener('click', async () => { const startTime = parseFloat(document.getElementById('start-time').value) || 0; const endTime = parseFloat(document.getElementById('end-time').value) || videoInfo.duration; - const width = parseInt(document.getElementById('width').value) || 0; - const height = parseInt(document.getElementById('height').value) || 0; const cropX = parseInt(document.getElementById('crop-x').value) || 0; const cropY = parseInt(document.getElementById('crop-y').value) || 0; const cropWidth = parseInt(document.getElementById('crop-width').value) || 0; @@ -943,13 +1024,10 @@ const payload = { file_id: currentFileId, start_time: startTime, - end_time: endTime + end_time: endTime, + scale_percentage: scalePercentage }; - if (width > 0 && height > 0) { - payload.resolution = { width, height }; - } - if (cropWidth > 0 && cropHeight > 0) { payload.crop = { x: cropX,