percent based resolution
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:
13
app.py
13
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:
|
||||
|
||||
@@ -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 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resolution Controls -->
|
||||
<!-- Scale Controls -->
|
||||
<div class="form-group">
|
||||
<label>📐 Output Resolution</label>
|
||||
<div class="preset-resolutions">
|
||||
<button class="preset-btn" data-width="3840" data-height="2160">4K (3840x2160)</button>
|
||||
<button class="preset-btn" data-width="1920" data-height="1080">1080p (1920x1080)</button>
|
||||
<button class="preset-btn" data-width="1280" data-height="720">720p (1280x720)</button>
|
||||
<button class="preset-btn" data-width="854" data-height="480">480p (854x480)</button>
|
||||
<button class="preset-btn active" data-width="0" data-height="0">Original</button>
|
||||
<label>📐 Output Scale</label>
|
||||
<div class="scale-slider-container">
|
||||
<div class="scale-display">
|
||||
<span>Scale: <span class="scale-value" id="scale-value-display">100%</span></span>
|
||||
<span id="resolution-info" style="color: #6c757d;">Output: <strong id="output-resolution">--</strong></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div>
|
||||
<label style="font-weight: normal;">Width</label>
|
||||
<input type="number" id="width" placeholder="Original" min="0">
|
||||
<input type="range" class="scale-slider" id="scale-slider" min="10" max="100" value="100" step="5">
|
||||
<div class="resolution-preview" id="resolution-preview">
|
||||
<p style="margin: 0;">💡 <strong>How it works:</strong> Scale is applied to the cropped area (or original if no crop). At 50%, a 200x400 crop becomes 100x200.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-weight: normal;">Height</label>
|
||||
<input type="number" id="height" placeholder="Original" min="0">
|
||||
<div class="preset-resolutions" style="margin-top: 15px;">
|
||||
<button class="preset-btn" data-scale="100">100% (Original)</button>
|
||||
<button class="preset-btn" data-scale="75">75%</button>
|
||||
<button class="preset-btn" data-scale="50">50%</button>
|
||||
<button class="preset-btn" data-scale="25">25%</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user