diff --git a/templates/index.html b/templates/index.html index fed8165..720d6ca 100644 --- a/templates/index.html +++ b/templates/index.html @@ -131,12 +131,20 @@ .video-preview video { max-width: 100%; - max-height: 400px; + max-height: 500px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); display: block; } + .video-controls { + margin: 15px 0; + padding: 15px; + background: white; + border-radius: 8px; + text-align: center; + } + .crop-canvas { position: absolute; top: 0; @@ -151,7 +159,6 @@ } .crop-mode-btn { - margin: 10px 0; padding: 10px 20px; background: #28a745; color: white; @@ -177,6 +184,112 @@ font-weight: 600; } + .timeline-container { + margin: 15px 0; + padding: 20px; + background: white; + border-radius: 8px; + } + + .timeline-label { + display: flex; + justify-content: space-between; + margin-bottom: 10px; + font-size: 0.9em; + color: #495057; + } + + .timeline-wrapper { + position: relative; + height: 60px; + background: #f8f9fa; + border-radius: 8px; + margin: 10px 0; + cursor: pointer; + border: 2px solid #e9ecef; + } + + .timeline-track { + position: absolute; + height: 100%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + opacity: 0.3; + border-radius: 6px; + } + + .timeline-handle { + position: absolute; + width: 4px; + height: 100%; + background: #667eea; + cursor: ew-resize; + z-index: 2; + } + + .timeline-handle::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 20px; + height: 30px; + background: #667eea; + border-radius: 4px; + border: 2px solid white; + } + + .timeline-handle.start::after { + content: '⏴'; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 12px; + } + + .timeline-handle.end::after { + content: '⏵'; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 12px; + } + + .timeline-time { + position: absolute; + top: -25px; + background: #667eea; + color: white; + padding: 3px 8px; + border-radius: 4px; + font-size: 0.85em; + font-weight: 600; + white-space: nowrap; + transform: translateX(-50%); + } + + .trim-mode-btn { + padding: 10px 20px; + background: #007bff; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + transition: all 0.3s; + margin-right: 10px; + } + + .trim-mode-btn:hover { + background: #0056b3; + } + + .trim-mode-btn.active { + background: #dc3545; + } + .video-info { background: white; padding: 15px; @@ -357,14 +470,37 @@ - - + +
+ + +
+
+ + +
@@ -454,6 +590,11 @@ let cropStartX = 0; let cropStartY = 0; let cropRect = null; + let trimMode = false; + let isDraggingStart = false; + let isDraggingEnd = false; + let timelineStartPos = 0; + let timelineEndPos = 100; // Upload area handling const uploadArea = document.getElementById('upload-area'); @@ -464,6 +605,12 @@ const cropCtx = cropCanvas.getContext('2d'); const cropModeBtn = document.getElementById('crop-mode-btn'); const cropHint = document.getElementById('crop-hint'); + const trimModeBtn = document.getElementById('trim-mode-btn'); + const timelineScrubber = document.getElementById('timeline-scrubber'); + const timelineWrapper = document.getElementById('timeline-wrapper'); + const timelineTrack = document.getElementById('timeline-track'); + const startHandle = document.getElementById('timeline-start-handle'); + const endHandle = document.getElementById('timeline-end-handle'); uploadArea.addEventListener('click', () => videoInput.click()); @@ -667,6 +814,107 @@ cropCtx.clearRect(0, 0, cropCanvas.width, cropCanvas.height); } + // Timeline trim mode + trimModeBtn.addEventListener('click', () => { + trimMode = !trimMode; + if (trimMode) { + timelineScrubber.style.display = 'block'; + trimModeBtn.classList.add('active'); + trimModeBtn.textContent = '❌ Cancel Timeline Selection'; + initializeTimeline(); + } else { + timelineScrubber.style.display = 'none'; + trimModeBtn.classList.remove('active'); + trimModeBtn.textContent = '✂️ Use Timeline to Select Trim'; + } + }); + + function initializeTimeline() { + timelineStartPos = 0; + timelineEndPos = 100; + updateTimeline(); + } + + function updateTimeline() { + // Update track position and width + timelineTrack.style.left = timelineStartPos + '%'; + timelineTrack.style.width = (timelineEndPos - timelineStartPos) + '%'; + + // Update handle positions + startHandle.style.left = timelineStartPos + '%'; + endHandle.style.left = timelineEndPos + '%'; + + // Calculate actual times + const duration = videoInfo.duration; + const startTime = (timelineStartPos / 100) * duration; + const endTime = (timelineEndPos / 100) * duration; + const selectedDuration = endTime - startTime; + + // Update labels + document.getElementById('start-time-label').textContent = startTime.toFixed(1) + 's'; + document.getElementById('end-time-label').textContent = endTime.toFixed(1) + 's'; + document.getElementById('timeline-start-display').textContent = startTime.toFixed(1) + 's'; + document.getElementById('timeline-end-display').textContent = endTime.toFixed(1) + 's'; + document.getElementById('timeline-duration-display').textContent = selectedDuration.toFixed(1) + 's'; + + // Update form inputs + document.getElementById('start-time').value = startTime.toFixed(1); + document.getElementById('end-time').value = endTime.toFixed(1); + } + + // Timeline dragging + startHandle.addEventListener('mousedown', (e) => { + e.stopPropagation(); + isDraggingStart = true; + }); + + endHandle.addEventListener('mousedown', (e) => { + e.stopPropagation(); + isDraggingEnd = true; + }); + + document.addEventListener('mousemove', (e) => { + if (!isDraggingStart && !isDraggingEnd) return; + + const rect = timelineWrapper.getBoundingClientRect(); + const x = e.clientX - rect.left; + const percentage = Math.max(0, Math.min(100, (x / rect.width) * 100)); + + if (isDraggingStart) { + timelineStartPos = Math.min(percentage, timelineEndPos - 1); + } else if (isDraggingEnd) { + timelineEndPos = Math.max(percentage, timelineStartPos + 1); + } + + updateTimeline(); + }); + + document.addEventListener('mouseup', () => { + isDraggingStart = false; + isDraggingEnd = false; + }); + + // Click on timeline to set range + timelineWrapper.addEventListener('click', (e) => { + if (e.target.classList.contains('timeline-handle')) return; + + const rect = timelineWrapper.getBoundingClientRect(); + const x = e.clientX - rect.left; + const percentage = (x / rect.width) * 100; + + // Find closest handle and move it + const distToStart = Math.abs(percentage - timelineStartPos); + const distToEnd = Math.abs(percentage - timelineEndPos); + + if (distToStart < distToEnd) { + timelineStartPos = Math.min(percentage, timelineEndPos - 1); + } else { + timelineEndPos = Math.max(percentage, timelineStartPos + 1); + } + + updateTimeline(); + }); + // Resolution presets document.querySelectorAll('.preset-btn').forEach(btn => { btn.addEventListener('click', () => { @@ -760,6 +1008,7 @@ videoInfo = null; cropMode = false; cropRect = null; + trimMode = false; document.getElementById('video-preview').src = ''; videoInput.value = ''; @@ -768,6 +1017,9 @@ cropModeBtn.classList.remove('active'); cropModeBtn.textContent = '🎯 Click to Draw Crop Area'; cropHint.style.display = 'none'; + timelineScrubber.style.display = 'none'; + trimModeBtn.classList.remove('active'); + trimModeBtn.textContent = '✂️ Use Timeline to Select Trim'; document.getElementById('upload-section').classList.remove('hidden'); document.getElementById('edit-section').classList.add('hidden');