Compare commits

...

11 Commits

Author SHA1 Message Date
e39153c8fc buttons css
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m12s
2026-02-10 22:51:56 -05:00
229f7c4b16 try to fix mobile crop
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 2m40s
2026-02-10 21:51:00 -05:00
c10c9d819c edge dragging for crop
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m9s
2026-02-09 22:47:16 -05:00
8a5822e556 snap edges on crop
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m10s
2026-02-09 22:42:36 -05:00
d94b2d6ab8 improved crop features
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m8s
2026-02-09 22:35:55 -05:00
fa287261d0 prevent cache
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m9s
2026-02-09 22:20:16 -05:00
24b0c07620 crop QOL
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m26s
2026-02-09 21:59:57 -05:00
640582df33 previews and back to edit button
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m12s
2026-02-09 21:53:52 -05:00
89e8c88a15 better words fix validation
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m10s
2026-02-09 21:34:56 -05:00
5d221bb51c fix scale
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m13s
2026-02-09 21:29:42 -05:00
cae62e0697 Not sure if this is allowed...
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m15s
2026-02-09 21:26:20 -05:00
2 changed files with 534 additions and 60 deletions

23
app.py
View File

@@ -159,8 +159,8 @@ def process_video():
# Get quality (CRF value)
quality = data.get('quality', 23)
# Ensure quality is within valid range (18-32)
quality = max(18, min(32, int(quality)))
# Ensure quality is within valid range (18-50)
quality = max(18, min(50, int(quality)))
# Output settings for H.264 MP4
cmd.extend([
@@ -211,6 +211,25 @@ def download_video(file_id):
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/preview/<file_id>')
def preview_video(file_id):
"""Serve processed video for inline preview"""
try:
output_filename = f"{file_id}_processed.mp4"
output_path = os.path.join(app.config['OUTPUT_FOLDER'], output_filename)
if not os.path.exists(output_path):
return jsonify({'error': 'File not found'}), 404
response = send_file(output_path, mimetype='video/mp4')
# Prevent caching
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/cleanup/<file_id>', methods=['DELETE'])
def cleanup_files(file_id):
"""Clean up uploaded and processed files"""

View File

@@ -456,6 +456,8 @@
display: flex;
gap: 15px;
margin-top: 20px;
flex-wrap: wrap;
justify-content: center;
}
.loader {
@@ -543,6 +545,9 @@
<!-- Crop Controls -->
<div class="form-group">
<label>✂️ Crop Video</label>
<p style="font-size: 0.9em; color: #6c757d; margin-bottom: 10px;">
💡 Draw crop area on video above • Double-click edges or corners to snap
</p>
<div class="crop-controls">
<div class="form-row">
<div>
@@ -596,16 +601,17 @@
<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">
<input type="range" class="scale-slider" id="quality-slider" min="18" max="42" 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="18">Overkill</button>
<button class="preset-btn" data-quality="21">Quality</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>
<button class="preset-btn" data-quality="26">Compressed</button>
<button class="preset-btn" data-quality="30">Small File</button>
<button class="preset-btn" data-quality="40">Dog 💩</button>
</div>
</div>
</div>
@@ -636,8 +642,16 @@
<div class="section hidden" id="download-section">
<h2>3. Download Processed Video</h2>
<p style="margin-bottom: 20px;">Your video has been processed successfully!</p>
<div class="video-preview">
<div class="video-wrapper">
<video id="processed-video-preview" controls style="max-width: 100%; max-height: 500px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);"></video>
</div>
</div>
<div class="button-group">
<button class="btn" id="download-btn">⬇️ Download Video</button>
<button class="btn btn-secondary" id="back-to-edit-btn">✏️ Make More Changes</button>
<button class="btn btn-secondary" id="new-video-btn">🎬 Process Another Video</button>
</div>
</div>
@@ -655,6 +669,14 @@
let rotationDegrees = 0;
let muteAudio = false;
let compressionQuality = 23;
let isDraggingHandle = false;
let isDraggingEdge = false;
let isDraggingBox = false;
let activeHandle = null;
let activeEdge = null;
let dragOffsetX = 0;
let dragOffsetY = 0;
const handleSize = 12;
// Upload area handling
const uploadArea = document.getElementById('upload-area');
@@ -728,7 +750,7 @@
<p><strong>Codec:</strong> ${videoInfo.codec}</p>
<div style="margin-top: 15px; text-align: center;">
<button class="crop-mode-btn" id="crop-mode-btn">🎯 Click to Draw Crop Area</button>
<p class="crop-hint" id="crop-hint" style="display: none;">Click and drag on the video to select crop area</p>
<p class="crop-hint" id="crop-hint" style="display: none;">Click and drag to select • Drag corners and edges to resize or double click them to max • Drag center to move</p>
</div>
`;
@@ -737,6 +759,12 @@
scalePercentage = 100;
scaleSlider.value = 100;
updateScaleDisplay();
// Set max values for crop inputs
document.getElementById('crop-x').max = videoInfo.width;
document.getElementById('crop-y').max = videoInfo.height;
document.getElementById('crop-width').max = videoInfo.width;
document.getElementById('crop-height').max = videoInfo.height;
// Setup canvas for cropping
setupCropCanvas();
@@ -784,7 +812,7 @@
if (cropMode) {
cropCanvas.classList.add('active');
cropModeBtn.classList.add('active');
cropModeBtn.textContent = '❌ Cancel Crop Selection';
cropModeBtn.textContent = '✅ Done with Crop';
cropHint.style.display = 'block';
document.getElementById('video-preview').pause();
} else {
@@ -793,6 +821,7 @@
cropModeBtn.textContent = '🎯 Click to Draw Crop Area';
cropHint.style.display = 'none';
clearCropCanvas();
cropRect = null;
}
});
}
@@ -803,71 +832,207 @@
cropCanvas.addEventListener('mousedown', (e) => {
if (!cropMode) return;
isDrawing = true;
e.preventDefault();
const rect = cropCanvas.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
handlePointerDown(mouseX, mouseY);
});
cropCanvas.addEventListener('touchstart', (e) => {
if (!cropMode) return;
e.preventDefault();
const rect = cropCanvas.getBoundingClientRect();
const touch = e.touches[0];
const mouseX = touch.clientX - rect.left;
const mouseY = touch.clientY - rect.top;
handlePointerDown(mouseX, mouseY);
});
function handlePointerDown(mouseX, mouseY) {
// Check if clicking on a handle
if (cropRect) {
const handle = getHandleAtPosition(mouseX, mouseY);
if (handle) {
isDraggingHandle = true;
activeHandle = handle;
return;
}
// Check if clicking on an edge
const edge = getEdgeAtPosition(mouseX, mouseY);
if (edge) {
isDraggingEdge = true;
activeEdge = edge;
return;
}
// Check if clicking inside the box to move it
if (isInsideRect(mouseX, mouseY)) {
isDraggingBox = true;
dragOffsetX = mouseX - cropRect.x;
dragOffsetY = mouseY - cropRect.y;
cropCanvas.style.cursor = 'move';
return;
}
}
// Start new selection
isDrawing = true;
cropRect = null;
startX = mouseX;
startY = mouseY;
}
cropCanvas.addEventListener('mousemove', (e) => {
if (!cropMode || !isDrawing) return;
if (!cropMode) return;
const rect = cropCanvas.getBoundingClientRect();
const currentX = e.clientX - rect.left;
const currentY = e.clientY - rect.top;
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
drawCropRect(startX, startY, currentX - startX, currentY - startY);
handlePointerMove(mouseX, mouseY);
});
cropCanvas.addEventListener('touchmove', (e) => {
if (!cropMode) return;
e.preventDefault();
const rect = cropCanvas.getBoundingClientRect();
const touch = e.touches[0];
const mouseX = touch.clientX - rect.left;
const mouseY = touch.clientY - rect.top;
handlePointerMove(mouseX, mouseY);
});
function handlePointerMove(mouseX, mouseY) {
// Update cursor based on position
if (!isDrawing && !isDraggingHandle && !isDraggingEdge && !isDraggingBox && cropRect) {
const handle = getHandleAtPosition(mouseX, mouseY);
if (handle) {
cropCanvas.style.cursor = getHandleCursor(handle);
} else {
const edge = getEdgeAtPosition(mouseX, mouseY);
if (edge) {
cropCanvas.style.cursor = getEdgeCursor(edge);
} else if (isInsideRect(mouseX, mouseY)) {
cropCanvas.style.cursor = 'move';
} else {
cropCanvas.style.cursor = 'crosshair';
}
}
}
// Handle resizing
if (isDraggingHandle && cropRect) {
resizeCropRect(mouseX, mouseY);
updateFormFields();
return;
}
// Handle edge resizing
if (isDraggingEdge && cropRect) {
resizeEdge(mouseX, mouseY);
updateFormFields();
return;
}
// Handle moving
if (isDraggingBox && cropRect) {
moveCropRect(mouseX, mouseY);
updateFormFields();
return;
}
// Handle initial drawing
if (isDrawing) {
const width = mouseX - startX;
const height = mouseY - startY;
cropRect = {
x: Math.min(startX, mouseX),
y: Math.min(startY, mouseY),
width: Math.abs(width),
height: Math.abs(height)
};
drawCropRect();
}
}
cropCanvas.addEventListener('mouseup', (e) => {
if (!cropMode || !isDrawing) return;
if (!cropMode) return;
handlePointerUp();
});
cropCanvas.addEventListener('touchend', (e) => {
if (!cropMode) return;
e.preventDefault();
handlePointerUp();
});
function handlePointerUp() {
if (isDrawing || isDraggingHandle || isDraggingEdge || isDraggingBox) {
updateFormFields();
}
isDrawing = false;
isDraggingHandle = false;
isDraggingEdge = false;
isDraggingBox = false;
activeHandle = null;
activeEdge = null;
cropCanvas.style.cursor = 'crosshair';
// Keep crop mode active and canvas visible
drawCropRect();
}
// Handle mouse leaving canvas while dragging
cropCanvas.addEventListener('mouseleave', (e) => {
if (isDrawing || isDraggingHandle || isDraggingEdge || isDraggingBox) {
handlePointerUp();
}
});
cropCanvas.addEventListener('touchcancel', (e) => {
if (cropMode) {
handlePointerUp();
}
});
// Double-click on handles to snap to edges
cropCanvas.addEventListener('dblclick', (e) => {
if (!cropMode || !cropRect) return;
const rect = cropCanvas.getBoundingClientRect();
const endX = e.clientX - rect.left;
const endY = e.clientY - rect.top;
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const width = Math.abs(endX - startX);
const height = Math.abs(endY - startY);
const x = Math.min(startX, endX);
const y = Math.min(startY, endY);
// Calculate actual video coordinates
const video = document.getElementById('video-preview');
const scaleX = videoInfo.width / video.offsetWidth;
const scaleY = videoInfo.height / video.offsetHeight;
const actualX = Math.round(x * scaleX);
const actualY = Math.round(y * scaleY);
const actualWidth = Math.round(width * scaleX);
const actualHeight = Math.round(height * scaleY);
// Update form fields
document.getElementById('crop-x').value = actualX;
document.getElementById('crop-y').value = actualY;
document.getElementById('crop-width').value = actualWidth;
document.getElementById('crop-height').value = actualHeight;
// Exit crop mode
cropMode = false;
const cropModeBtn = document.getElementById('crop-mode-btn');
const cropHint = document.getElementById('crop-hint');
if (cropModeBtn) {
cropModeBtn.classList.remove('active');
cropModeBtn.textContent = '🎯 Click to Draw Crop Area';
}
if (cropHint) {
cropHint.style.display = 'none';
// Check for edge click first (between corners)
const edge = getEdgeAtPosition(mouseX, mouseY);
if (edge) {
snapEdgeToSide(edge);
updateFormFields();
drawCropRect();
return;
}
// Keep the rectangle visible for a moment
setTimeout(() => {
cropCanvas.classList.remove('active');
}, 500);
// Fall back to corner handling
const handle = getHandleAtPosition(mouseX, mouseY);
if (handle) {
snapHandleToEdge(handle);
updateFormFields();
drawCropRect();
}
});
function drawCropRect(x, y, width, height) {
function drawCropRect() {
clearCropCanvas();
if (!cropRect) return;
const { x, y, width, height } = cropRect;
// Draw semi-transparent overlay
cropCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
cropCtx.fillRect(0, 0, cropCanvas.width, cropCanvas.height);
@@ -880,6 +1045,12 @@
cropCtx.lineWidth = 3;
cropCtx.strokeRect(x, y, width, height);
// Draw resize handles
drawHandle(x, y); // Top-left
drawHandle(x + width, y); // Top-right
drawHandle(x, y + height); // Bottom-left
drawHandle(x + width, y + height); // Bottom-right
// Calculate actual video dimensions for display
const video = document.getElementById('video-preview');
const scaleX = videoInfo.width / video.offsetWidth;
@@ -891,7 +1062,273 @@
cropCtx.fillStyle = '#667eea';
cropCtx.font = 'bold 14px sans-serif';
const text = `${actualWidth} × ${actualHeight}`;
cropCtx.fillText(text, x + 5, y - 5);
const textY = y > 25 ? y - 10 : y + height + 20;
cropCtx.fillText(text, x + 5, textY);
}
function drawHandle(x, y) {
cropCtx.fillStyle = '#ffffff';
cropCtx.strokeStyle = '#667eea';
cropCtx.lineWidth = 2;
const offset = handleSize / 2;
cropCtx.fillRect(x - offset, y - offset, handleSize, handleSize);
cropCtx.strokeRect(x - offset, y - offset, handleSize, handleSize);
}
function getHandleAtPosition(x, y) {
if (!cropRect) return null;
// Larger hit area on mobile (touch targets should be at least 44x44px)
const hitSize = 'ontouchstart' in window ? 22 : handleSize;
const handles = [
{ name: 'tl', x: cropRect.x, y: cropRect.y },
{ name: 'tr', x: cropRect.x + cropRect.width, y: cropRect.y },
{ name: 'bl', x: cropRect.x, y: cropRect.y + cropRect.height },
{ name: 'br', x: cropRect.x + cropRect.width, y: cropRect.y + cropRect.height }
];
for (const handle of handles) {
if (Math.abs(x - handle.x) <= hitSize && Math.abs(y - handle.y) <= hitSize) {
return handle.name;
}
}
return null;
}
function getEdgeAtPosition(x, y) {
if (!cropRect) return null;
// Larger tolerance on mobile for easier touch interaction
const tolerance = 'ontouchstart' in window ? 25 : 15;
const { x: rx, y: ry, width: rw, height: rh } = cropRect;
const hitSize = 'ontouchstart' in window ? 22 : handleSize;
// Check if within the rectangle bounds (with tolerance)
const inHorizontalRange = x >= rx - tolerance && x <= rx + rw + tolerance;
const inVerticalRange = y >= ry - tolerance && y <= ry + rh + tolerance;
// Top edge (exclude corners)
if (Math.abs(y - ry) <= tolerance && x > rx + hitSize && x < rx + rw - hitSize) {
return 'top';
}
// Bottom edge (exclude corners)
if (Math.abs(y - (ry + rh)) <= tolerance && x > rx + hitSize && x < rx + rw - hitSize) {
return 'bottom';
}
// Left edge (exclude corners)
if (Math.abs(x - rx) <= tolerance && y > ry + hitSize && y < ry + rh - hitSize) {
return 'left';
}
// Right edge (exclude corners)
if (Math.abs(x - (rx + rw)) <= tolerance && y > ry + hitSize && y < ry + rh - hitSize) {
return 'right';
}
return null;
}
function getHandleCursor(handle) {
const cursors = {
'tl': 'nw-resize',
'tr': 'ne-resize',
'bl': 'sw-resize',
'br': 'se-resize'
};
return cursors[handle] || 'default';
}
function getEdgeCursor(edge) {
const cursors = {
'top': 'ns-resize',
'bottom': 'ns-resize',
'left': 'ew-resize',
'right': 'ew-resize'
};
return cursors[edge] || 'default';
}
function isInsideRect(x, y) {
if (!cropRect) return false;
return x >= cropRect.x && x <= cropRect.x + cropRect.width &&
y >= cropRect.y && y <= cropRect.y + cropRect.height;
}
function resizeCropRect(mouseX, mouseY) {
const minSize = 20;
switch (activeHandle) {
case 'tl':
const newWidth = cropRect.x + cropRect.width - mouseX;
const newHeight = cropRect.y + cropRect.height - mouseY;
if (newWidth > minSize) {
cropRect.width = newWidth;
cropRect.x = mouseX;
}
if (newHeight > minSize) {
cropRect.height = newHeight;
cropRect.y = mouseY;
}
break;
case 'tr':
const widthTR = mouseX - cropRect.x;
const heightTR = cropRect.y + cropRect.height - mouseY;
if (widthTR > minSize) cropRect.width = widthTR;
if (heightTR > minSize) {
cropRect.height = heightTR;
cropRect.y = mouseY;
}
break;
case 'bl':
const widthBL = cropRect.x + cropRect.width - mouseX;
const heightBL = mouseY - cropRect.y;
if (widthBL > minSize) {
cropRect.width = widthBL;
cropRect.x = mouseX;
}
if (heightBL > minSize) cropRect.height = heightBL;
break;
case 'br':
const widthBR = mouseX - cropRect.x;
const heightBR = mouseY - cropRect.y;
if (widthBR > minSize) cropRect.width = widthBR;
if (heightBR > minSize) cropRect.height = heightBR;
break;
}
// Constrain to canvas bounds
cropRect.x = Math.max(0, Math.min(cropRect.x, cropCanvas.width - cropRect.width));
cropRect.y = Math.max(0, Math.min(cropRect.y, cropCanvas.height - cropRect.height));
cropRect.width = Math.min(cropRect.width, cropCanvas.width - cropRect.x);
cropRect.height = Math.min(cropRect.height, cropCanvas.height - cropRect.y);
drawCropRect();
}
function resizeEdge(mouseX, mouseY) {
const minSize = 20;
switch (activeEdge) {
case 'top':
const newHeight = cropRect.y + cropRect.height - mouseY;
if (newHeight > minSize && mouseY >= 0) {
cropRect.height = newHeight;
cropRect.y = mouseY;
}
break;
case 'bottom':
const heightBottom = mouseY - cropRect.y;
if (heightBottom > minSize && mouseY <= cropCanvas.height) {
cropRect.height = heightBottom;
}
break;
case 'left':
const newWidth = cropRect.x + cropRect.width - mouseX;
if (newWidth > minSize && mouseX >= 0) {
cropRect.width = newWidth;
cropRect.x = mouseX;
}
break;
case 'right':
const widthRight = mouseX - cropRect.x;
if (widthRight > minSize && mouseX <= cropCanvas.width) {
cropRect.width = widthRight;
}
break;
}
// Constrain to canvas bounds
cropRect.x = Math.max(0, cropRect.x);
cropRect.y = Math.max(0, cropRect.y);
cropRect.width = Math.min(cropRect.width, cropCanvas.width - cropRect.x);
cropRect.height = Math.min(cropRect.height, cropCanvas.height - cropRect.y);
drawCropRect();
}
function moveCropRect(mouseX, mouseY) {
const newX = mouseX - dragOffsetX;
const newY = mouseY - dragOffsetY;
// Constrain to canvas bounds
cropRect.x = Math.max(0, Math.min(newX, cropCanvas.width - cropRect.width));
cropRect.y = Math.max(0, Math.min(newY, cropCanvas.height - cropRect.height));
drawCropRect();
}
function snapHandleToEdge(handle) {
if (!cropRect) return;
switch (handle) {
case 'tl': // Top-left -> snap to top-left corner
cropRect.width += cropRect.x; // Extend width by current x
cropRect.height += cropRect.y; // Extend height by current y
cropRect.x = 0;
cropRect.y = 0;
break;
case 'tr': // Top-right -> snap to top-right corner
cropRect.width = cropCanvas.width - cropRect.x;
cropRect.height += cropRect.y;
cropRect.y = 0;
break;
case 'bl': // Bottom-left -> snap to bottom-left corner
cropRect.width += cropRect.x;
cropRect.height = cropCanvas.height - cropRect.y;
cropRect.x = 0;
break;
case 'br': // Bottom-right -> snap to bottom-right corner
cropRect.width = cropCanvas.width - cropRect.x;
cropRect.height = cropCanvas.height - cropRect.y;
break;
}
}
function snapEdgeToSide(edge) {
if (!cropRect) return;
switch (edge) {
case 'top': // Snap top edge to y=0
cropRect.height += cropRect.y; // Extend height by current y
cropRect.y = 0;
break;
case 'bottom': // Snap bottom edge to canvas height
cropRect.height = cropCanvas.height - cropRect.y;
break;
case 'left': // Snap left edge to x=0
cropRect.width += cropRect.x; // Extend width by current x
cropRect.x = 0;
break;
case 'right': // Snap right edge to canvas width
cropRect.width = cropCanvas.width - cropRect.x;
break;
}
}
function updateFormFields() {
if (!cropRect) return;
// Calculate actual video coordinates
const video = document.getElementById('video-preview');
const scaleX = videoInfo.width / video.offsetWidth;
const scaleY = videoInfo.height / video.offsetHeight;
const actualX = Math.round(cropRect.x * scaleX);
const actualY = Math.round(cropRect.y * scaleY);
const actualWidth = Math.round(cropRect.width * scaleX);
const actualHeight = Math.round(cropRect.height * scaleY);
// Update form fields
document.getElementById('crop-x').value = actualX;
document.getElementById('crop-y').value = actualY;
document.getElementById('crop-width').value = actualWidth;
document.getElementById('crop-height').value = actualHeight;
// Update scale display since crop changed
updateScaleDisplay();
}
function clearCropCanvas() {
@@ -986,11 +1423,12 @@
// Update quality label
let qualityLabel = 'Balanced';
if (compressionQuality <= 19) qualityLabel = 'Best Quality';
else if (compressionQuality <= 22) qualityLabel = 'High';
if (compressionQuality <= 19) qualityLabel = 'Overkill';
else if (compressionQuality <= 22) qualityLabel = 'Quality';
else if (compressionQuality <= 25) qualityLabel = 'Balanced';
else if (compressionQuality <= 28) qualityLabel = 'Low';
else qualityLabel = 'Smallest File';
else if (compressionQuality <= 28) qualityLabel = 'Compressed';
else if (compressionQuality <= 32) qualityLabel = 'Small File';
else qualityLabel = 'Dog 💩';
qualityValueDisplay.textContent = qualityLabel;
}
@@ -1067,6 +1505,10 @@
throw new Error(data.error || 'Processing failed');
}
// Load processed video preview with cache-busting
const processedVideoPreview = document.getElementById('processed-video-preview');
processedVideoPreview.src = `/preview/${currentFileId}?t=${Date.now()}`;
// Show download section
document.getElementById('edit-section').classList.add('hidden');
document.getElementById('download-section').classList.remove('hidden');
@@ -1085,9 +1527,22 @@
window.location.href = `/download/${currentFileId}`;
});
// Back to edit button
document.getElementById('back-to-edit-btn').addEventListener('click', backToEdit);
// Reset buttons
document.getElementById('reset-btn').addEventListener('click', resetApp);
document.getElementById('new-video-btn').addEventListener('click', resetApp);
function backToEdit() {
// Go back to edit section without resetting values
document.getElementById('download-section').classList.add('hidden');
document.getElementById('edit-section').classList.remove('hidden');
// Clear the processed video preview to free memory
const processedVideoPreview = document.getElementById('processed-video-preview');
processedVideoPreview.src = '';
}
async function resetApp() {
if (currentFileId) {