duqing2026's picture
Update: Automate asset generation and exclude binaries from git
23b4a3c
from flask import Flask, render_template, request, jsonify, send_file
from werkzeug.exceptions import HTTPException
import os
import subprocess
import uuid
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['OUTPUT_FOLDER'] = 'outputs'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB limit to prevent server overload
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['OUTPUT_FOLDER'], exist_ok=True)
import shutil
import time
import traceback
# Check if ffmpeg is installed
FFMPEG_AVAILABLE = shutil.which('ffmpeg') is not None
def cleanup_old_files():
"""Cleanup files older than 1 hour"""
try:
now = time.time()
for folder in [app.config['UPLOAD_FOLDER'], app.config['OUTPUT_FOLDER']]:
if not os.path.exists(folder):
continue
for filename in os.listdir(folder):
filepath = os.path.join(folder, filename)
# Remove if older than 1 hour
if os.path.isfile(filepath) and os.path.getmtime(filepath) < now - 3600:
try:
os.remove(filepath)
except Exception:
pass
except Exception:
pass
@app.errorhandler(500)
def internal_error(error):
return jsonify({
"error": "服务器内部错误 (Internal Server Error)",
"details": str(error),
"trace": traceback.format_exc(),
"suggestion": "请尝试上传较小的文件,或检查文件格式是否正确。"
}), 500
@app.errorhandler(413)
def request_entity_too_large(error):
return jsonify({
"error": "文件过大 (File Too Large)",
"details": "上传的文件超过了服务器限制 (100MB)。"
}), 413
@app.errorhandler(Exception)
def handle_exception(e):
# Pass through HTTP errors
if isinstance(e, HTTPException):
return e
# Log the full traceback
print("Error occurred:")
traceback.print_exc()
# Now you're handling non-HTTP exceptions only
return jsonify({
"error": "Unexpected Error",
"details": str(e),
"trace": traceback.format_exc() # Return trace to client for debugging
}), 500
@app.route('/')
def index():
return render_template('index.html', ffmpeg_available=FFMPEG_AVAILABLE)
@app.route('/convert', methods=['POST'])
def convert():
cleanup_old_files()
if 'video' not in request.files:
return jsonify({"error": "No video file"}), 400
video = request.files['video']
filename = str(uuid.uuid4())
input_path = os.path.join(app.config['UPLOAD_FOLDER'], filename + '.webm')
output_path = os.path.join(app.config['OUTPUT_FOLDER'], filename + '.mp4')
try:
video.save(input_path)
if not FFMPEG_AVAILABLE:
return jsonify({"error": "FFmpeg not installed on server", "fallback": True}), 503
# FFmpeg conversion
# -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" ensures dimensions are even for libx264
# -pix_fmt yuv420p is often required for broad compatibility
# Added -movflags +faststart for better web playback
cmd = [
'ffmpeg', '-y', '-i', input_path,
'-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
'-pix_fmt', 'yuv420p',
'-c:a', 'aac', '-b:a', '192k',
'-vf', 'pad=ceil(iw/2)*2:ceil(ih/2)*2',
'-movflags', '+faststart',
output_path
]
# Log command for debugging
print(f"Running command: {' '.join(cmd)}")
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return jsonify({"url": f"/download/{filename}.mp4"})
except subprocess.CalledProcessError as e:
print(f"FFmpeg Error: {e.stderr.decode() if e.stderr else str(e)}")
return jsonify({"error": "Conversion failed", "details": str(e)}), 500
except Exception as e:
print(f"General Error: {str(e)}")
return jsonify({"error": "Internal server error", "details": str(e)}), 500
finally:
# Cleanup input
if os.path.exists(input_path):
try:
os.remove(input_path)
except:
pass
@app.route('/download/<filename>')
def download_file(filename):
return send_file(os.path.join(app.config['OUTPUT_FOLDER'], filename), as_attachment=True)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860)