import spaces # <--- CRITICAL: MUST BE THE FIRST IMPORT import os import time import threading import json import traceback import shutil import cv2 import numpy as np import srt from flask import Flask, jsonify, request, send_from_directory, send_file # ====================================================== # 🚀 ZEROGPU CONFIGURATION # ====================================================== @spaces.GPU def gpu_warmup(): import torch return torch.cuda.is_available() # ====================================================== # 💾 STORAGE SETUP # ====================================================== BASE_STORAGE_PATH = '/data' if os.path.exists('/data') else '.' BASE_USER_DIR = os.path.join(BASE_STORAGE_PATH, "userdata") os.makedirs(BASE_USER_DIR, exist_ok=True) # ====================================================== # 🔧 JSON SANITIZER (FIX FOR int64 ERROR) # ====================================================== def sanitize_json(obj): if isinstance(obj, dict): return {k: sanitize_json(v) for k, v in obj.items()} elif isinstance(obj, list): return [sanitize_json(v) for v in obj] elif isinstance(obj, (np.int64, np.int32, np.int16)): return int(obj) elif isinstance(obj, (np.float64, np.float32)): return float(obj) elif isinstance(obj, np.ndarray): return sanitize_json(obj.tolist()) return obj # ====================================================== # 🧠 CORE GPU GENERATOR # ====================================================== @spaces.GPU(duration=300) def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages): # Heavy AI imports inside function to avoid Startup Timeout from backend.keyframes.keyframes import black_bar_crop from backend.simple_color_enhancer import SimpleColorEnhancer from backend.subtitles.subs_real import get_real_subtitles from backend.ai_bubble_placement import ai_bubble_placer from backend.ai_enhanced_core import face_detector cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) or 25 total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) duration = total_frames / fps cap.release() # 1. Subtitles user_srt = os.path.join(user_dir, 'subs.srt') try: get_real_subtitles(video_path) if os.path.exists('test1.srt'): shutil.move('test1.srt', user_srt) except: with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:05,000\nDialogue...\n") with open(user_srt, 'r', encoding='utf-8') as f: try: all_subs = list(srt.parse(f.read())) except: all_subs = [] # 2. Logic for 5 Panels Per Page panels_per_page = 5 target_pages = int(target_pages) total_needed = target_pages * panels_per_page if not all_subs: times = np.linspace(1, max(1.1, duration-1), total_needed) moments = [{'text': '', 'start': t} for t in times] elif len(all_subs) <= total_needed: moments = [{'text': s.content, 'start': s.start.total_seconds()} for s in all_subs] while len(moments) < total_needed: moments.append({'text': '', 'start': duration/2}) else: indices = np.linspace(0, len(all_subs) - 1, total_needed, dtype=int) moments = [{'text': all_subs[i].content, 'start': all_subs[i].start.total_seconds()} for i in indices] # 3. Frame Extraction frame_metadata = {} cap = cv2.VideoCapture(video_path) frame_files = [] for i, m in enumerate(moments): cap.set(cv2.CAP_PROP_POS_MSEC, m['start'] * 1000) ret, frame = cap.read() if ret: fname = f"frame_{i:04d}.png" p = os.path.join(frames_dir, fname) cv2.imwrite(p, frame) frame_metadata[fname] = {'dialogue': m['text'], 'time': m['start']} frame_files.append(fname) cap.release() with open(metadata_path, 'w') as f: json.dump(sanitize_json(frame_metadata), f) try: black_bar_crop() except: pass # 4. Enhance & Assemble se = SimpleColorEnhancer() pages_data = [] for p_idx in range(target_pages): p_p, p_b = [], [] start = p_idx * 5 for i in range(start, start + 5): if i >= len(frame_files): break f_name = frame_files[i] img_p = os.path.join(frames_dir, f_name) try: se.enhance_single(img_p, img_p) except: pass txt = frame_metadata[f_name]['dialogue'] try: faces = face_detector.detect_faces(img_p) lip = face_detector.get_lip_position(img_p, faces[0]) if faces else (-1, -1) bx, by = ai_bubble_placer.place_bubble_ai(img_p, lip) item = {'dialog': txt, 'x': bx, 'y': by} except: item = {'dialog': txt, 'x': 50, 'y': 25} p_p.append({'image': f_name}) p_b.append(item) pages_data.append({'panels': p_p, 'bubbles': p_b}) return sanitize_json(pages_data) # ====================================================== # 🔧 APP ENGINE # ====================================================== app = Flask(__name__) @app.route('/') def index(): return INDEX_HTML @app.route('/uploader', methods=['POST']) def uploader(): sid = request.args.get('sid') u_dir = os.path.join(BASE_USER_DIR, sid) f_dir = os.path.join(u_dir, 'frames'); o_dir = os.path.join(u_dir, 'output') os.makedirs(f_dir, exist_ok=True); os.makedirs(o_dir, exist_ok=True) vid_p = os.path.join(u_dir, 'video.mp4') request.files['file'].save(vid_p) pages = request.form.get('pages', 2) def task(): try: with open(os.path.join(o_dir, 'status.json'), 'w') as f: json.dump({'message': 'Generating Panels...', 'progress': 30}, f) data = generate_comic_gpu(vid_p, u_dir, f_dir, os.path.join(f_dir, 'meta.json'), pages) with open(os.path.join(o_dir, 'pages.json'), 'w') as f: json.dump(data, f) with open(os.path.join(o_dir, 'status.json'), 'w') as f: json.dump({'message': 'Complete', 'progress': 100}, f) except Exception as e: with open(os.path.join(o_dir, 'status.json'), 'w') as f: json.dump({'message': f'Error: {str(e)}', 'progress': -1}, f) threading.Thread(target=task).start() return jsonify({'success': True}) @app.route('/status') def status(): sid = request.args.get('sid') p = os.path.join(BASE_USER_DIR, sid, 'output', 'status.json') if os.path.exists(p): return send_file(p) return jsonify({'progress': 0}) @app.route('/frames//') def get_frame(sid, filename): return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'frames'), filename) @app.route('/output//') def get_output(sid, filename): return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'output'), filename) # ====================================================== # 🌐 UI HTML (USING YOUR FINAL COORDINATES) # ====================================================== INDEX_HTML = ''' Elite Geometric Comic Maker

🎬 Elite Comic Maker

Using Final Dragged Coordinates





''' if __name__ == '__main__': try: gpu_warmup() except: pass app.run(host='0.0.0.0', port=7860)