Complete video optimization tool with: - 21 platform configurations (Meta, TikTok, YouTube, Pinterest, Snapchat, Amazon) - FFmpeg-powered video conversion with H264, H265, and VP9 codecs - Python Flask backend with REST API - HTML/JS frontend with drag-drop interface - Black + #FFC407 color scheme with Montserrat font - Side-by-side video comparison player - Filename auto-detection for platform and aspect ratio - MAMP-compatible setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
280 lines
9 KiB
Python
280 lines
9 KiB
Python
"""
|
|
Flask backend for video optimization tool
|
|
"""
|
|
|
|
from flask import Flask, request, jsonify, send_file
|
|
from flask_cors import CORS
|
|
from werkzeug.utils import secure_filename
|
|
import os
|
|
import uuid
|
|
from datetime import datetime
|
|
from video_processor import VideoProcessor
|
|
from platform_specs import (
|
|
PLATFORM_SPECS,
|
|
detect_platform_from_filename,
|
|
detect_aspect_ratio_from_filename,
|
|
get_all_platforms,
|
|
get_platform_formats,
|
|
get_platform_info
|
|
)
|
|
|
|
app = Flask(__name__)
|
|
CORS(app)
|
|
|
|
# Configuration
|
|
UPLOAD_FOLDER = os.path.join(os.path.dirname(__file__), 'uploads')
|
|
OUTPUT_FOLDER = os.path.join(os.path.dirname(__file__), 'outputs')
|
|
ALLOWED_EXTENSIONS = {'mp4', 'mov', 'avi', 'mkv', 'webm', 'flv', 'wmv', 'm4v'}
|
|
MAX_FILE_SIZE = 500 * 1024 * 1024 # 500MB
|
|
|
|
# Create folders if they don't exist
|
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
|
|
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER
|
|
app.config['MAX_CONTENT_LENGTH'] = MAX_FILE_SIZE
|
|
|
|
|
|
def allowed_file(filename):
|
|
"""Check if file extension is allowed"""
|
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
|
|
|
|
|
@app.route('/api/health', methods=['GET'])
|
|
def health_check():
|
|
"""Health check endpoint"""
|
|
ffmpeg_installed = VideoProcessor.check_ffmpeg_installed()
|
|
return jsonify({
|
|
'status': 'ok',
|
|
'ffmpeg_installed': ffmpeg_installed,
|
|
'timestamp': datetime.now().isoformat()
|
|
})
|
|
|
|
|
|
@app.route('/api/platforms', methods=['GET'])
|
|
def get_platforms():
|
|
"""Get all available platforms and their specifications"""
|
|
platforms_list = []
|
|
|
|
for platform_key in get_all_platforms():
|
|
platform_info = get_platform_info(platform_key)
|
|
platforms_list.append({
|
|
'key': platform_key,
|
|
'name': platform_info['name'],
|
|
'codec': platform_info['codec'],
|
|
'formats': platform_info['formats']
|
|
})
|
|
|
|
return jsonify({
|
|
'platforms': platforms_list
|
|
})
|
|
|
|
|
|
@app.route('/api/detect', methods=['POST'])
|
|
def detect_from_filename():
|
|
"""Detect platform and aspect ratio from filename"""
|
|
data = request.get_json()
|
|
filename = data.get('filename', '')
|
|
|
|
platform = detect_platform_from_filename(filename)
|
|
aspect_ratio = detect_aspect_ratio_from_filename(filename)
|
|
|
|
return jsonify({
|
|
'platform': platform,
|
|
'aspect_ratio': aspect_ratio,
|
|
'detected': platform is not None or aspect_ratio is not None
|
|
})
|
|
|
|
|
|
@app.route('/api/upload', methods=['POST'])
|
|
def upload_file():
|
|
"""Handle file upload and return video info"""
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'No file provided'}), 400
|
|
|
|
file = request.files['file']
|
|
|
|
if file.filename == '':
|
|
return jsonify({'error': 'No file selected'}), 400
|
|
|
|
if not allowed_file(file.filename):
|
|
return jsonify({'error': 'File type not allowed'}), 400
|
|
|
|
try:
|
|
# Generate unique filename
|
|
original_filename = secure_filename(file.filename)
|
|
file_id = str(uuid.uuid4())
|
|
file_extension = original_filename.rsplit('.', 1)[1].lower()
|
|
unique_filename = f"{file_id}.{file_extension}"
|
|
file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
|
|
|
|
# Save file
|
|
file.save(file_path)
|
|
|
|
# Probe video to get info
|
|
processor = VideoProcessor(file_path)
|
|
video_info = processor.get_video_info()
|
|
|
|
# Detect platform and aspect ratio from filename
|
|
platform = detect_platform_from_filename(original_filename)
|
|
aspect_ratio = detect_aspect_ratio_from_filename(original_filename)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'file_id': file_id,
|
|
'filename': original_filename,
|
|
'video_info': video_info,
|
|
'detected_platform': platform,
|
|
'detected_aspect_ratio': aspect_ratio
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@app.route('/api/convert', methods=['POST'])
|
|
def convert_video():
|
|
"""Convert video based on platform and aspect ratio"""
|
|
data = request.get_json()
|
|
|
|
file_id = data.get('file_id')
|
|
platform = data.get('platform')
|
|
aspect_ratio = data.get('aspect_ratio')
|
|
custom_bitrate = data.get('custom_bitrate')
|
|
|
|
if not all([file_id, platform, aspect_ratio]):
|
|
return jsonify({'error': 'Missing required parameters'}), 400
|
|
|
|
# Find input file
|
|
input_files = [f for f in os.listdir(app.config['UPLOAD_FOLDER'])
|
|
if f.startswith(file_id)]
|
|
|
|
if not input_files:
|
|
return jsonify({'error': 'Input file not found'}), 404
|
|
|
|
input_path = os.path.join(app.config['UPLOAD_FOLDER'], input_files[0])
|
|
|
|
try:
|
|
# Get platform info to determine output container
|
|
platform_info = get_platform_info(platform)
|
|
if not platform_info:
|
|
return jsonify({'error': 'Invalid platform'}), 400
|
|
|
|
output_extension = platform_info['container']
|
|
output_filename = f"{file_id}_optimized.{output_extension}"
|
|
output_path = os.path.join(app.config['OUTPUT_FOLDER'], output_filename)
|
|
|
|
# Process video
|
|
processor = VideoProcessor(input_path)
|
|
result = processor.convert_video(
|
|
platform=platform,
|
|
aspect_ratio=aspect_ratio,
|
|
output_path=output_path,
|
|
custom_bitrate=custom_bitrate
|
|
)
|
|
|
|
# Calculate size reduction
|
|
input_size = os.path.getsize(input_path)
|
|
output_size = result['output_size']
|
|
size_reduction = ((input_size - output_size) / input_size) * 100
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'output_file_id': file_id,
|
|
'output_filename': output_filename,
|
|
'input_size': input_size,
|
|
'output_size': output_size,
|
|
'size_reduction_percent': round(size_reduction, 2),
|
|
'conversion_details': result
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@app.route('/api/download/<file_type>/<file_id>', methods=['GET'])
|
|
def download_file(file_type, file_id):
|
|
"""Download original or converted file"""
|
|
try:
|
|
if file_type == 'original':
|
|
folder = app.config['UPLOAD_FOLDER']
|
|
files = [f for f in os.listdir(folder) if f.startswith(file_id) and not 'optimized' in f]
|
|
elif file_type == 'optimized':
|
|
folder = app.config['OUTPUT_FOLDER']
|
|
files = [f for f in os.listdir(folder) if f.startswith(file_id)]
|
|
else:
|
|
return jsonify({'error': 'Invalid file type'}), 400
|
|
|
|
if not files:
|
|
return jsonify({'error': 'File not found'}), 404
|
|
|
|
file_path = os.path.join(folder, files[0])
|
|
return send_file(file_path, as_attachment=True)
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@app.route('/api/stream/<file_type>/<file_id>', methods=['GET'])
|
|
def stream_file(file_type, file_id):
|
|
"""Stream video for playback"""
|
|
try:
|
|
if file_type == 'original':
|
|
folder = app.config['UPLOAD_FOLDER']
|
|
files = [f for f in os.listdir(folder) if f.startswith(file_id) and not 'optimized' in f]
|
|
elif file_type == 'optimized':
|
|
folder = app.config['OUTPUT_FOLDER']
|
|
files = [f for f in os.listdir(folder) if f.startswith(file_id)]
|
|
else:
|
|
return jsonify({'error': 'Invalid file type'}), 400
|
|
|
|
if not files:
|
|
return jsonify({'error': 'File not found'}), 404
|
|
|
|
file_path = os.path.join(folder, files[0])
|
|
return send_file(file_path, mimetype='video/mp4')
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@app.route('/api/cleanup/<file_id>', methods=['DELETE'])
|
|
def cleanup_files(file_id):
|
|
"""Delete uploaded and converted files"""
|
|
try:
|
|
deleted = []
|
|
|
|
# Clean upload folder
|
|
for filename in os.listdir(app.config['UPLOAD_FOLDER']):
|
|
if filename.startswith(file_id):
|
|
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
|
os.remove(file_path)
|
|
deleted.append(filename)
|
|
|
|
# Clean output folder
|
|
for filename in os.listdir(app.config['OUTPUT_FOLDER']):
|
|
if filename.startswith(file_id):
|
|
file_path = os.path.join(app.config['OUTPUT_FOLDER'], filename)
|
|
os.remove(file_path)
|
|
deleted.append(filename)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'deleted_files': deleted
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Check FFmpeg installation
|
|
if not VideoProcessor.check_ffmpeg_installed():
|
|
print("WARNING: FFmpeg not found. Please install FFmpeg to use video conversion features.")
|
|
print("Install with: brew install ffmpeg (macOS) or apt-get install ffmpeg (Linux)")
|
|
|
|
print("Starting Video Optimization Server...")
|
|
print(f"Upload folder: {UPLOAD_FOLDER}")
|
|
print(f"Output folder: {OUTPUT_FOLDER}")
|
|
app.run(debug=True, host='0.0.0.0', port=5000)
|