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/') 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)