Spaces:
Running
Running
| # app.py | |
| from flask import Flask, render_template_string, request, jsonify | |
| import os | |
| import uuid | |
| import subprocess | |
| app = Flask(__name__) | |
| UPLOAD_FOLDER = "uploads" | |
| OUTPUT_FOLDER = "static/videos" | |
| os.makedirs(UPLOAD_FOLDER, exist_ok=True) | |
| os.makedirs(OUTPUT_FOLDER, exist_ok=True) | |
| HTML = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Photo + Audio To Video</title> | |
| <style> | |
| *{ | |
| margin:0; | |
| padding:0; | |
| box-sizing:border-box; | |
| font-family:Arial; | |
| } | |
| body{ | |
| background:#0f0f0f; | |
| color:white; | |
| min-height:100vh; | |
| display:flex; | |
| justify-content:center; | |
| align-items:center; | |
| padding:20px; | |
| } | |
| .container{ | |
| width:100%; | |
| max-width:500px; | |
| background:#1b1b1b; | |
| border-radius:20px; | |
| padding:25px; | |
| box-shadow:0 0 20px rgba(0,0,0,0.4); | |
| } | |
| h1{ | |
| text-align:center; | |
| margin-bottom:25px; | |
| font-size:28px; | |
| } | |
| .upload-box{ | |
| border:2px dashed #444; | |
| padding:20px; | |
| border-radius:15px; | |
| margin-bottom:20px; | |
| } | |
| label{ | |
| display:block; | |
| margin-bottom:8px; | |
| color:#ccc; | |
| } | |
| input{ | |
| width:100%; | |
| padding:12px; | |
| background:#2a2a2a; | |
| border:none; | |
| border-radius:10px; | |
| color:white; | |
| margin-bottom:15px; | |
| } | |
| button{ | |
| width:100%; | |
| padding:15px; | |
| border:none; | |
| border-radius:12px; | |
| background:#00aaff; | |
| color:white; | |
| font-size:18px; | |
| cursor:pointer; | |
| transition:0.3s; | |
| } | |
| button:hover{ | |
| opacity:0.9; | |
| } | |
| #loading{ | |
| display:none; | |
| text-align:center; | |
| margin-top:20px; | |
| } | |
| video{ | |
| width:100%; | |
| margin-top:20px; | |
| border-radius:15px; | |
| display:none; | |
| } | |
| .download-btn{ | |
| display:none; | |
| margin-top:15px; | |
| text-align:center; | |
| } | |
| .download-btn a{ | |
| display:inline-block; | |
| background:#22c55e; | |
| color:white; | |
| text-decoration:none; | |
| padding:12px 20px; | |
| border-radius:10px; | |
| } | |
| .preview{ | |
| margin-top:15px; | |
| width:100%; | |
| border-radius:15px; | |
| display:none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Photo + Audio → Video</h1> | |
| <form id="form"> | |
| <div class="upload-box"> | |
| <label>Select Photo</label> | |
| <input type="file" id="image" name="image" accept="image/*" required> | |
| <img id="preview" class="preview"> | |
| <label>Select Audio (mp3/wav)</label> | |
| <input type="file" name="audio" required> | |
| </div> | |
| <button type="submit">Generate Video</button> | |
| </form> | |
| <div id="loading"> | |
| Generating Video... | |
| </div> | |
| <video id="video" controls></video> | |
| <div class="download-btn" id="downloadDiv"> | |
| <a id="downloadBtn" download>Download Video</a> | |
| </div> | |
| </div> | |
| <script> | |
| const form = document.getElementById("form"); | |
| const loading = document.getElementById("loading"); | |
| const video = document.getElementById("video"); | |
| const downloadBtn = document.getElementById("downloadBtn"); | |
| const downloadDiv = document.getElementById("downloadDiv"); | |
| const preview = document.getElementById("preview"); | |
| document.getElementById("image").addEventListener("change", function(e){ | |
| const file = e.target.files[0]; | |
| if(file){ | |
| preview.src = URL.createObjectURL(file); | |
| preview.style.display = "block"; | |
| } | |
| }); | |
| form.addEventListener("submit", async (e)=>{ | |
| e.preventDefault(); | |
| loading.style.display = "block"; | |
| video.style.display = "none"; | |
| downloadDiv.style.display = "none"; | |
| const formData = new FormData(form); | |
| try{ | |
| const response = await fetch("/generate",{ | |
| method:"POST", | |
| body:formData | |
| }); | |
| const data = await response.json(); | |
| loading.style.display = "none"; | |
| if(data.video_url){ | |
| video.src = data.video_url + "?t=" + new Date().getTime(); | |
| video.style.display = "block"; | |
| downloadBtn.href = data.video_url; | |
| downloadDiv.style.display = "block"; | |
| }else{ | |
| alert(data.error || "Failed"); | |
| } | |
| }catch(err){ | |
| loading.style.display = "none"; | |
| alert("Server Error"); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| def home(): | |
| return render_template_string(HTML) | |
| def generate(): | |
| if "image" not in request.files or "audio" not in request.files: | |
| return jsonify({"error":"Missing files"}) | |
| image = request.files["image"] | |
| audio = request.files["audio"] | |
| uid = str(uuid.uuid4()) | |
| image_path = os.path.join(UPLOAD_FOLDER, uid + "_" + image.filename) | |
| audio_path = os.path.join(UPLOAD_FOLDER, uid + "_" + audio.filename) | |
| output_filename = uid + ".mp4" | |
| output_path = os.path.join(OUTPUT_FOLDER, output_filename) | |
| image.save(image_path) | |
| audio.save(audio_path) | |
| """cmd = [ | |
| "ffmpeg", | |
| "-y", | |
| "-loop", "1", | |
| "-i", image_path, | |
| "-i", audio_path, | |
| "-c:v", "libx264", | |
| "-tune", "stillimage", | |
| "-c:a", "aac", | |
| "-b:a", "192k", | |
| "-pix_fmt", "yuv420p", | |
| "-shortest", | |
| output_path | |
| ]""" | |
| cmd = [ | |
| "ffmpeg", | |
| "-y", | |
| "-loop", "1", | |
| "-i", image_path, | |
| "-i", audio_path, | |
| "-vf", | |
| "scale=trunc(iw/2)*2:trunc(ih/2)*2", | |
| "-c:v", "libx264", | |
| "-tune", "stillimage", | |
| "-c:a", "aac", | |
| "-b:a", "192k", | |
| "-pix_fmt", "yuv420p", | |
| "-shortest", | |
| output_path | |
| ] | |
| try: | |
| subprocess.run( | |
| cmd, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| check=True | |
| ) | |
| return jsonify({ | |
| "video_url": f"/static/videos/{output_filename}" | |
| }) | |
| except subprocess.CalledProcessError as e: | |
| return jsonify({ | |
| "error":"FFmpeg failed", | |
| "details": e.stderr.decode() | |
| }) | |
| if __name__ == "__main__": | |
| app.run(host="0.0.0.0", port=7860) |