better timeline scrub
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m9s
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 1m9s
This commit is contained in:
@@ -184,6 +184,50 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trim-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 15px 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trim-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trim-btn:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trim-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-time-display {
|
||||||
|
background: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
.scale-slider-container {
|
.scale-slider-container {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
}
|
}
|
||||||
@@ -241,112 +285,6 @@
|
|||||||
color: #495057;
|
color: #495057;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
.video-info {
|
||||||
background: white;
|
background: white;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
@@ -538,26 +476,20 @@
|
|||||||
<!-- Trim Controls -->
|
<!-- Trim Controls -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>⏱️ Trim Video</label>
|
<label>⏱️ Trim Video</label>
|
||||||
<div class="timeline-container">
|
<div class="current-time-display">
|
||||||
<button class="trim-mode-btn" id="trim-mode-btn">✂️ Use Timeline to Select Trim</button>
|
Video Position: <span id="current-time-display">0:00</span>
|
||||||
<div id="timeline-scrubber" style="display: none;">
|
|
||||||
<div class="timeline-label">
|
|
||||||
<span>Start: <strong id="timeline-start-display">0.0s</strong></span>
|
|
||||||
<span>Duration: <strong id="timeline-duration-display">0.0s</strong></span>
|
|
||||||
<span>End: <strong id="timeline-end-display">0.0s</strong></span>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-wrapper" id="timeline-wrapper">
|
|
||||||
<div class="timeline-track" id="timeline-track"></div>
|
|
||||||
<div class="timeline-handle start" id="timeline-start-handle">
|
|
||||||
<div class="timeline-time" id="start-time-label">0.0s</div>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-handle end" id="timeline-end-handle">
|
|
||||||
<div class="timeline-time" id="end-time-label">0.0s</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p style="font-size: 0.9em; color: #6c757d; margin-top: 10px; text-align: center;">Drag the handles to set start and end times</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="trim-buttons">
|
||||||
|
<button class="trim-btn" id="set-start-btn">
|
||||||
|
⏮️ Set Start Here
|
||||||
|
</button>
|
||||||
|
<button class="trim-btn" id="set-end-btn">
|
||||||
|
⏭️ Set End Here
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p style="font-size: 0.9em; color: #6c757d; text-align: center; margin: 10px 0;">
|
||||||
|
💡 Play the video and click the buttons to set trim points at the current playback position
|
||||||
|
</p>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div>
|
<div>
|
||||||
<label style="font-weight: normal;">Start Time (seconds)</label>
|
<label style="font-weight: normal;">Start Time (seconds)</label>
|
||||||
@@ -646,11 +578,6 @@
|
|||||||
let cropStartX = 0;
|
let cropStartX = 0;
|
||||||
let cropStartY = 0;
|
let cropStartY = 0;
|
||||||
let cropRect = null;
|
let cropRect = null;
|
||||||
let trimMode = false;
|
|
||||||
let isDraggingStart = false;
|
|
||||||
let isDraggingEnd = false;
|
|
||||||
let timelineStartPos = 0;
|
|
||||||
let timelineEndPos = 100;
|
|
||||||
let scalePercentage = 100;
|
let scalePercentage = 100;
|
||||||
|
|
||||||
// Upload area handling
|
// Upload area handling
|
||||||
@@ -662,12 +589,10 @@
|
|||||||
const cropCtx = cropCanvas.getContext('2d');
|
const cropCtx = cropCanvas.getContext('2d');
|
||||||
const cropModeBtn = document.getElementById('crop-mode-btn');
|
const cropModeBtn = document.getElementById('crop-mode-btn');
|
||||||
const cropHint = document.getElementById('crop-hint');
|
const cropHint = document.getElementById('crop-hint');
|
||||||
const trimModeBtn = document.getElementById('trim-mode-btn');
|
const videoPreview = document.getElementById('video-preview');
|
||||||
const timelineScrubber = document.getElementById('timeline-scrubber');
|
const currentTimeDisplay = document.getElementById('current-time-display');
|
||||||
const timelineWrapper = document.getElementById('timeline-wrapper');
|
const setStartBtn = document.getElementById('set-start-btn');
|
||||||
const timelineTrack = document.getElementById('timeline-track');
|
const setEndBtn = document.getElementById('set-end-btn');
|
||||||
const startHandle = document.getElementById('timeline-start-handle');
|
|
||||||
const endHandle = document.getElementById('timeline-end-handle');
|
|
||||||
|
|
||||||
uploadArea.addEventListener('click', () => videoInput.click());
|
uploadArea.addEventListener('click', () => videoInput.click());
|
||||||
|
|
||||||
@@ -874,105 +799,37 @@
|
|||||||
cropCtx.clearRect(0, 0, cropCanvas.width, cropCanvas.height);
|
cropCtx.clearRect(0, 0, cropCanvas.width, cropCanvas.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeline trim mode
|
// Update current time display
|
||||||
trimModeBtn.addEventListener('click', () => {
|
videoPreview.addEventListener('timeupdate', () => {
|
||||||
trimMode = !trimMode;
|
const currentTime = videoPreview.currentTime;
|
||||||
if (trimMode) {
|
const minutes = Math.floor(currentTime / 60);
|
||||||
timelineScrubber.style.display = 'block';
|
const seconds = Math.floor(currentTime % 60);
|
||||||
trimModeBtn.classList.add('active');
|
const milliseconds = Math.floor((currentTime % 1) * 10);
|
||||||
trimModeBtn.textContent = '❌ Cancel Timeline Selection';
|
currentTimeDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}.${milliseconds}`;
|
||||||
initializeTimeline();
|
|
||||||
} else {
|
|
||||||
timelineScrubber.style.display = 'none';
|
|
||||||
trimModeBtn.classList.remove('active');
|
|
||||||
trimModeBtn.textContent = '✂️ Use Timeline to Select Trim';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function initializeTimeline() {
|
// Set start time button
|
||||||
timelineStartPos = 0;
|
setStartBtn.addEventListener('click', () => {
|
||||||
timelineEndPos = 100;
|
const currentTime = videoPreview.currentTime;
|
||||||
updateTimeline();
|
document.getElementById('start-time').value = currentTime.toFixed(1);
|
||||||
}
|
|
||||||
|
|
||||||
function updateTimeline() {
|
// Flash button feedback
|
||||||
// Update track position and width
|
setStartBtn.style.background = '#28a745';
|
||||||
timelineTrack.style.left = timelineStartPos + '%';
|
setTimeout(() => {
|
||||||
timelineTrack.style.width = (timelineEndPos - timelineStartPos) + '%';
|
setStartBtn.style.background = '#007bff';
|
||||||
|
}, 200);
|
||||||
// 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) => {
|
// Set end time button
|
||||||
e.stopPropagation();
|
setEndBtn.addEventListener('click', () => {
|
||||||
isDraggingEnd = true;
|
const currentTime = videoPreview.currentTime;
|
||||||
});
|
document.getElementById('end-time').value = currentTime.toFixed(1);
|
||||||
|
|
||||||
document.addEventListener('mousemove', (e) => {
|
// Flash button feedback
|
||||||
if (!isDraggingStart && !isDraggingEnd) return;
|
setEndBtn.style.background = '#28a745';
|
||||||
|
setTimeout(() => {
|
||||||
const rect = timelineWrapper.getBoundingClientRect();
|
setEndBtn.style.background = '#007bff';
|
||||||
const x = e.clientX - rect.left;
|
}, 200);
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Scale slider
|
// Scale slider
|
||||||
@@ -1086,18 +943,14 @@
|
|||||||
videoInfo = null;
|
videoInfo = null;
|
||||||
cropMode = false;
|
cropMode = false;
|
||||||
cropRect = null;
|
cropRect = null;
|
||||||
trimMode = false;
|
|
||||||
|
|
||||||
document.getElementById('video-preview').src = '';
|
videoPreview.src = '';
|
||||||
videoInput.value = '';
|
videoInput.value = '';
|
||||||
clearCropCanvas();
|
clearCropCanvas();
|
||||||
cropCanvas.classList.remove('active');
|
cropCanvas.classList.remove('active');
|
||||||
cropModeBtn.classList.remove('active');
|
cropModeBtn.classList.remove('active');
|
||||||
cropModeBtn.textContent = '🎯 Click to Draw Crop Area';
|
cropModeBtn.textContent = '🎯 Click to Draw Crop Area';
|
||||||
cropHint.style.display = 'none';
|
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('upload-section').classList.remove('hidden');
|
||||||
document.getElementById('edit-section').classList.add('hidden');
|
document.getElementById('edit-section').classList.add('hidden');
|
||||||
|
|||||||
Reference in New Issue
Block a user