Update app_enhanced.py
Browse files- app_enhanced.py +50 -73
app_enhanced.py
CHANGED
|
@@ -39,6 +39,22 @@ SAVED_COMICS_DIR = os.path.join(BASE_STORAGE_PATH, "saved_comics")
|
|
| 39 |
os.makedirs(BASE_USER_DIR, exist_ok=True)
|
| 40 |
os.makedirs(SAVED_COMICS_DIR, exist_ok=True)
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
# ======================================================
|
| 43 |
# 🧱 DATA CLASSES
|
| 44 |
# ======================================================
|
|
@@ -61,30 +77,17 @@ class Page:
|
|
| 61 |
self.bubbles = bubbles
|
| 62 |
|
| 63 |
# ======================================================
|
| 64 |
-
#
|
| 65 |
-
# ======================================================
|
| 66 |
-
logging.basicConfig(level=logging.INFO)
|
| 67 |
-
app = Flask(__name__)
|
| 68 |
-
|
| 69 |
-
def generate_save_code(length=8):
|
| 70 |
-
chars = string.ascii_uppercase + string.digits
|
| 71 |
-
while True:
|
| 72 |
-
code = ''.join(random.choices(chars, k=length))
|
| 73 |
-
if not os.path.exists(os.path.join(SAVED_COMICS_DIR, code)):
|
| 74 |
-
return code
|
| 75 |
-
|
| 76 |
-
# ======================================================
|
| 77 |
-
# 🧠 GPU FUNCTIONS (OPTIMIZED FOR SPEED)
|
| 78 |
# ======================================================
|
| 79 |
@spaces.GPU(duration=120)
|
| 80 |
def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages):
|
| 81 |
-
print(f"🚀
|
| 82 |
|
| 83 |
import cv2
|
| 84 |
import srt
|
| 85 |
import numpy as np
|
| 86 |
|
| 87 |
-
# 1. Video
|
| 88 |
cap = cv2.VideoCapture(video_path)
|
| 89 |
if not cap.isOpened(): raise Exception("Cannot open video")
|
| 90 |
fps = cap.get(cv2.CAP_PROP_FPS) or 25
|
|
@@ -92,10 +95,9 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 92 |
duration = total_frames / fps
|
| 93 |
cap.release()
|
| 94 |
|
| 95 |
-
# 2.
|
| 96 |
user_srt = os.path.join(user_dir, 'subs.srt')
|
| 97 |
if not os.path.exists(user_srt):
|
| 98 |
-
# Create dummy SRT if missing to avoid crashes
|
| 99 |
with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
|
| 100 |
|
| 101 |
with open(user_srt, 'r', encoding='utf-8') as f:
|
|
@@ -105,7 +107,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 105 |
valid_subs = [s for s in all_subs if s.content.strip()]
|
| 106 |
raw_moments = [{'text': s.content, 'start': s.start.total_seconds(), 'end': s.end.total_seconds()} for s in valid_subs]
|
| 107 |
|
| 108 |
-
# 3.
|
| 109 |
if target_pages <= 0: target_pages = 1
|
| 110 |
panels_per_page = 4
|
| 111 |
total_panels_needed = target_pages * panels_per_page
|
|
@@ -120,7 +122,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 120 |
indices = np.linspace(0, len(raw_moments) - 1, total_panels_needed, dtype=int)
|
| 121 |
selected_moments = [raw_moments[i] for i in indices]
|
| 122 |
|
| 123 |
-
# 4.
|
| 124 |
frame_metadata = {}
|
| 125 |
cap = cv2.VideoCapture(video_path)
|
| 126 |
count = 0
|
|
@@ -133,10 +135,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 133 |
ret, frame = cap.read()
|
| 134 |
|
| 135 |
if ret:
|
| 136 |
-
#
|
| 137 |
-
# ⚡ OPTIMIZATION: 16:9 RESIZE ONLY (NO AI)
|
| 138 |
-
# 1280x720 preserves the "Wide Space"
|
| 139 |
-
# ------------------------------------------------
|
| 140 |
frame = cv2.resize(frame, (1280, 720))
|
| 141 |
|
| 142 |
fname = f"frame_{count:04d}.png"
|
|
@@ -150,18 +149,16 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 150 |
cap.release()
|
| 151 |
with open(metadata_path, 'w') as f: json.dump(frame_metadata, f, indent=2)
|
| 152 |
|
| 153 |
-
# 5.
|
| 154 |
bubbles_list = []
|
| 155 |
for f in frame_files_ordered:
|
| 156 |
dialogue = frame_metadata.get(f, {}).get('dialogue', '')
|
| 157 |
b_type = 'speech'
|
| 158 |
if '(' in dialogue: b_type = 'narration'
|
| 159 |
elif '!' in dialogue: b_type = 'reaction'
|
| 160 |
-
|
| 161 |
-
# Default to Center (50, 50) to save processing time
|
| 162 |
bubbles_list.append(bubble(dialog=dialogue, bubble_offset_x=50, bubble_offset_y=50, type=b_type))
|
| 163 |
|
| 164 |
-
# 6.
|
| 165 |
pages = []
|
| 166 |
for i in range(target_pages):
|
| 167 |
start_idx = i * 4
|
|
@@ -169,7 +166,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 169 |
p_frames = frame_files_ordered[start_idx:end_idx]
|
| 170 |
p_bubbles = bubbles_list[start_idx:end_idx]
|
| 171 |
|
| 172 |
-
#
|
| 173 |
while len(p_frames) < 4:
|
| 174 |
fname = f"empty_{i}_{len(p_frames)}.png"
|
| 175 |
img = np.zeros((720, 1280, 3), dtype=np.uint8); img[:] = (40,40,40)
|
|
@@ -181,7 +178,6 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 181 |
pg_panels = [panel(image=f) for f in p_frames]
|
| 182 |
pages.append(Page(panels=pg_panels, bubbles=p_bubbles))
|
| 183 |
|
| 184 |
-
# 7. Convert to Dict
|
| 185 |
result = []
|
| 186 |
for pg in pages:
|
| 187 |
p_data = [p if isinstance(p, dict) else p.__dict__ for p in pg.panels]
|
|
@@ -202,7 +198,7 @@ def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
|
|
| 202 |
t = meta[fname]['time'] if isinstance(meta[fname], dict) else meta[fname]
|
| 203 |
cap = cv2.VideoCapture(video_path)
|
| 204 |
fps = cap.get(cv2.CAP_PROP_FPS) or 25
|
| 205 |
-
offset = (1.0/fps) * (1 if direction == 'forward' else -1)
|
| 206 |
new_t = max(0, t + offset)
|
| 207 |
|
| 208 |
cap.set(cv2.CAP_PROP_POS_MSEC, new_t * 1000)
|
|
@@ -220,30 +216,6 @@ def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
|
|
| 220 |
return {"success": True, "message": f"Time: {new_t:.2f}s"}
|
| 221 |
return {"success": False, "message": "End of video"}
|
| 222 |
|
| 223 |
-
@spaces.GPU
|
| 224 |
-
def get_frame_at_ts_gpu(video_path, frames_dir, metadata_path, fname, ts):
|
| 225 |
-
import cv2
|
| 226 |
-
import json
|
| 227 |
-
|
| 228 |
-
cap = cv2.VideoCapture(video_path)
|
| 229 |
-
cap.set(cv2.CAP_PROP_POS_MSEC, float(ts) * 1000)
|
| 230 |
-
ret, frame = cap.read()
|
| 231 |
-
cap.release()
|
| 232 |
-
|
| 233 |
-
if ret:
|
| 234 |
-
frame = cv2.resize(frame, (1280, 720)) # Keep 16:9
|
| 235 |
-
p = os.path.join(frames_dir, fname)
|
| 236 |
-
cv2.imwrite(p, frame)
|
| 237 |
-
|
| 238 |
-
if os.path.exists(metadata_path):
|
| 239 |
-
with open(metadata_path, 'r') as f: meta = json.load(f)
|
| 240 |
-
if fname in meta:
|
| 241 |
-
if isinstance(meta[fname], dict): meta[fname]['time'] = float(ts)
|
| 242 |
-
else: meta[fname] = float(ts)
|
| 243 |
-
with open(metadata_path, 'w') as f: json.dump(meta, f, indent=2)
|
| 244 |
-
return {"success": True, "message": f"Jumped to {ts}s"}
|
| 245 |
-
return {"success": False, "message": "Invalid timestamp"}
|
| 246 |
-
|
| 247 |
# ======================================================
|
| 248 |
# 💻 BACKEND CLASS
|
| 249 |
# ======================================================
|
|
@@ -266,7 +238,7 @@ class EnhancedComicGenerator:
|
|
| 266 |
|
| 267 |
def run(self, target_pages):
|
| 268 |
try:
|
| 269 |
-
self.write_status("
|
| 270 |
data = generate_comic_gpu(self.video_path, self.user_dir, self.frames_dir, self.metadata_path, int(target_pages))
|
| 271 |
with open(os.path.join(self.output_dir, 'pages.json'), 'w') as f:
|
| 272 |
json.dump(data, f, indent=2)
|
|
@@ -280,10 +252,10 @@ class EnhancedComicGenerator:
|
|
| 280 |
json.dump({'message': msg, 'progress': prog}, f)
|
| 281 |
|
| 282 |
# ======================================================
|
| 283 |
-
# 🌐
|
| 284 |
# ======================================================
|
| 285 |
INDEX_HTML = '''
|
| 286 |
-
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>
|
| 287 |
|
| 288 |
#upload-container { display: flex; flex-direction:column; justify-content: center; align-items: center; min-height: 100vh; width: 100%; padding: 20px; }
|
| 289 |
.upload-box { max-width: 500px; width: 100%; padding: 40px; background: #333; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.5); text-align: center; }
|
|
@@ -399,9 +371,7 @@ INDEX_HTML = '''
|
|
| 399 |
.reset-btn { background: #e74c3c; color: white; }
|
| 400 |
.secondary-btn { background: #f39c12; color: white; }
|
| 401 |
|
| 402 |
-
.tip {
|
| 403 |
-
text-align:center; padding:10px; background:#e74c3c; color:white; font-weight:bold; margin-bottom:20px; border-radius:5px;
|
| 404 |
-
}
|
| 405 |
</style>
|
| 406 |
</head> <body>
|
| 407 |
|
|
@@ -469,12 +439,6 @@ INDEX_HTML = '''
|
|
| 469 |
let interval, selectedBubble = null, selectedPanel = null;
|
| 470 |
let dragType = null, activeObj = null, dragStart = {x:0, y:0};
|
| 471 |
|
| 472 |
-
// STATE MANAGEMENT
|
| 473 |
-
function getCurrentState() {
|
| 474 |
-
// Simple state grabber for saves (not fully implemented in this fast version, but structure is here)
|
| 475 |
-
return {};
|
| 476 |
-
}
|
| 477 |
-
|
| 478 |
async function upload() {
|
| 479 |
const f = document.getElementById('file-upload').files[0];
|
| 480 |
const pCount = document.getElementById('page-count').value;
|
|
@@ -489,8 +453,12 @@ INDEX_HTML = '''
|
|
| 489 |
fd.append('target_pages', pCount);
|
| 490 |
|
| 491 |
const r = await fetch(`/uploader?sid=${sid}`, {method:'POST', body:fd});
|
| 492 |
-
if(r.ok) interval = setInterval(checkStatus, 1500);
|
| 493 |
-
else {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
}
|
| 495 |
|
| 496 |
async function checkStatus() {
|
|
@@ -674,15 +642,24 @@ def index():
|
|
| 674 |
|
| 675 |
@app.route('/uploader', methods=['POST'])
|
| 676 |
def upload():
|
| 677 |
-
|
|
|
|
| 678 |
if not sid: return jsonify({'success': False, 'message': 'Missing session ID'}), 400
|
| 679 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
|
| 681 |
target_pages = request.form.get('target_pages', 4)
|
| 682 |
-
|
| 683 |
gen = EnhancedComicGenerator(sid)
|
| 684 |
gen.cleanup()
|
| 685 |
-
|
| 686 |
gen.write_status("Starting...", 5)
|
| 687 |
|
| 688 |
threading.Thread(target=gen.run, args=(target_pages,)).start()
|
|
|
|
| 39 |
os.makedirs(BASE_USER_DIR, exist_ok=True)
|
| 40 |
os.makedirs(SAVED_COMICS_DIR, exist_ok=True)
|
| 41 |
|
| 42 |
+
# ======================================================
|
| 43 |
+
# 🔧 APP CONFIG
|
| 44 |
+
# ======================================================
|
| 45 |
+
logging.basicConfig(level=logging.INFO)
|
| 46 |
+
app = Flask(__name__)
|
| 47 |
+
|
| 48 |
+
# ALLOW LARGE UPLOADS (500MB)
|
| 49 |
+
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024
|
| 50 |
+
|
| 51 |
+
def generate_save_code(length=8):
|
| 52 |
+
chars = string.ascii_uppercase + string.digits
|
| 53 |
+
while True:
|
| 54 |
+
code = ''.join(random.choices(chars, k=length))
|
| 55 |
+
if not os.path.exists(os.path.join(SAVED_COMICS_DIR, code)):
|
| 56 |
+
return code
|
| 57 |
+
|
| 58 |
# ======================================================
|
| 59 |
# 🧱 DATA CLASSES
|
| 60 |
# ======================================================
|
|
|
|
| 77 |
self.bubbles = bubbles
|
| 78 |
|
| 79 |
# ======================================================
|
| 80 |
+
# 🧠 GPU GENERATION
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
# ======================================================
|
| 82 |
@spaces.GPU(duration=120)
|
| 83 |
def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages):
|
| 84 |
+
print(f"🚀 Generation Started for {video_path}...")
|
| 85 |
|
| 86 |
import cv2
|
| 87 |
import srt
|
| 88 |
import numpy as np
|
| 89 |
|
| 90 |
+
# 1. Video Prep
|
| 91 |
cap = cv2.VideoCapture(video_path)
|
| 92 |
if not cap.isOpened(): raise Exception("Cannot open video")
|
| 93 |
fps = cap.get(cv2.CAP_PROP_FPS) or 25
|
|
|
|
| 95 |
duration = total_frames / fps
|
| 96 |
cap.release()
|
| 97 |
|
| 98 |
+
# 2. Subtitles (Dummy if missing)
|
| 99 |
user_srt = os.path.join(user_dir, 'subs.srt')
|
| 100 |
if not os.path.exists(user_srt):
|
|
|
|
| 101 |
with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
|
| 102 |
|
| 103 |
with open(user_srt, 'r', encoding='utf-8') as f:
|
|
|
|
| 107 |
valid_subs = [s for s in all_subs if s.content.strip()]
|
| 108 |
raw_moments = [{'text': s.content, 'start': s.start.total_seconds(), 'end': s.end.total_seconds()} for s in valid_subs]
|
| 109 |
|
| 110 |
+
# 3. Calculate Intervals
|
| 111 |
if target_pages <= 0: target_pages = 1
|
| 112 |
panels_per_page = 4
|
| 113 |
total_panels_needed = target_pages * panels_per_page
|
|
|
|
| 122 |
indices = np.linspace(0, len(raw_moments) - 1, total_panels_needed, dtype=int)
|
| 123 |
selected_moments = [raw_moments[i] for i in indices]
|
| 124 |
|
| 125 |
+
# 4. Extract Frames (1280x720 16:9)
|
| 126 |
frame_metadata = {}
|
| 127 |
cap = cv2.VideoCapture(video_path)
|
| 128 |
count = 0
|
|
|
|
| 135 |
ret, frame = cap.read()
|
| 136 |
|
| 137 |
if ret:
|
| 138 |
+
# FORCE LANDSCAPE 16:9 for Wide View
|
|
|
|
|
|
|
|
|
|
| 139 |
frame = cv2.resize(frame, (1280, 720))
|
| 140 |
|
| 141 |
fname = f"frame_{count:04d}.png"
|
|
|
|
| 149 |
cap.release()
|
| 150 |
with open(metadata_path, 'w') as f: json.dump(frame_metadata, f, indent=2)
|
| 151 |
|
| 152 |
+
# 5. Bubbles
|
| 153 |
bubbles_list = []
|
| 154 |
for f in frame_files_ordered:
|
| 155 |
dialogue = frame_metadata.get(f, {}).get('dialogue', '')
|
| 156 |
b_type = 'speech'
|
| 157 |
if '(' in dialogue: b_type = 'narration'
|
| 158 |
elif '!' in dialogue: b_type = 'reaction'
|
|
|
|
|
|
|
| 159 |
bubbles_list.append(bubble(dialog=dialogue, bubble_offset_x=50, bubble_offset_y=50, type=b_type))
|
| 160 |
|
| 161 |
+
# 6. Pages
|
| 162 |
pages = []
|
| 163 |
for i in range(target_pages):
|
| 164 |
start_idx = i * 4
|
|
|
|
| 166 |
p_frames = frame_files_ordered[start_idx:end_idx]
|
| 167 |
p_bubbles = bubbles_list[start_idx:end_idx]
|
| 168 |
|
| 169 |
+
# Fill empty
|
| 170 |
while len(p_frames) < 4:
|
| 171 |
fname = f"empty_{i}_{len(p_frames)}.png"
|
| 172 |
img = np.zeros((720, 1280, 3), dtype=np.uint8); img[:] = (40,40,40)
|
|
|
|
| 178 |
pg_panels = [panel(image=f) for f in p_frames]
|
| 179 |
pages.append(Page(panels=pg_panels, bubbles=p_bubbles))
|
| 180 |
|
|
|
|
| 181 |
result = []
|
| 182 |
for pg in pages:
|
| 183 |
p_data = [p if isinstance(p, dict) else p.__dict__ for p in pg.panels]
|
|
|
|
| 198 |
t = meta[fname]['time'] if isinstance(meta[fname], dict) else meta[fname]
|
| 199 |
cap = cv2.VideoCapture(video_path)
|
| 200 |
fps = cap.get(cv2.CAP_PROP_FPS) or 25
|
| 201 |
+
offset = (1.0/fps) * (1 if direction == 'forward' else -1)
|
| 202 |
new_t = max(0, t + offset)
|
| 203 |
|
| 204 |
cap.set(cv2.CAP_PROP_POS_MSEC, new_t * 1000)
|
|
|
|
| 216 |
return {"success": True, "message": f"Time: {new_t:.2f}s"}
|
| 217 |
return {"success": False, "message": "End of video"}
|
| 218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
# ======================================================
|
| 220 |
# 💻 BACKEND CLASS
|
| 221 |
# ======================================================
|
|
|
|
| 238 |
|
| 239 |
def run(self, target_pages):
|
| 240 |
try:
|
| 241 |
+
self.write_status("Processing...", 10)
|
| 242 |
data = generate_comic_gpu(self.video_path, self.user_dir, self.frames_dir, self.metadata_path, int(target_pages))
|
| 243 |
with open(os.path.join(self.output_dir, 'pages.json'), 'w') as f:
|
| 244 |
json.dump(data, f, indent=2)
|
|
|
|
| 252 |
json.dump({'message': msg, 'progress': prog}, f)
|
| 253 |
|
| 254 |
# ======================================================
|
| 255 |
+
# 🌐 FRONTEND
|
| 256 |
# ======================================================
|
| 257 |
INDEX_HTML = '''
|
| 258 |
+
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Comic Generator (16:9)</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script> <link href="https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@700&display=swap" rel="stylesheet"> <style> * { box-sizing: border-box; } body { background-color: #222; font-family: 'Lato', sans-serif; color: #eee; margin: 0; min-height: 100vh; }
|
| 259 |
|
| 260 |
#upload-container { display: flex; flex-direction:column; justify-content: center; align-items: center; min-height: 100vh; width: 100%; padding: 20px; }
|
| 261 |
.upload-box { max-width: 500px; width: 100%; padding: 40px; background: #333; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.5); text-align: center; }
|
|
|
|
| 371 |
.reset-btn { background: #e74c3c; color: white; }
|
| 372 |
.secondary-btn { background: #f39c12; color: white; }
|
| 373 |
|
| 374 |
+
.tip { text-align:center; padding:10px; background:#e74c3c; color:white; font-weight:bold; margin-bottom:20px; border-radius:5px; }
|
|
|
|
|
|
|
| 375 |
</style>
|
| 376 |
</head> <body>
|
| 377 |
|
|
|
|
| 439 |
let interval, selectedBubble = null, selectedPanel = null;
|
| 440 |
let dragType = null, activeObj = null, dragStart = {x:0, y:0};
|
| 441 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
async function upload() {
|
| 443 |
const f = document.getElementById('file-upload').files[0];
|
| 444 |
const pCount = document.getElementById('page-count').value;
|
|
|
|
| 453 |
fd.append('target_pages', pCount);
|
| 454 |
|
| 455 |
const r = await fetch(`/uploader?sid=${sid}`, {method:'POST', body:fd});
|
| 456 |
+
if(r.ok) interval = setInterval(checkStatus, 1500);
|
| 457 |
+
else {
|
| 458 |
+
const d = await r.json();
|
| 459 |
+
alert("Upload failed: " + (d.message || "Server Error"));
|
| 460 |
+
location.reload();
|
| 461 |
+
}
|
| 462 |
}
|
| 463 |
|
| 464 |
async function checkStatus() {
|
|
|
|
| 642 |
|
| 643 |
@app.route('/uploader', methods=['POST'])
|
| 644 |
def upload():
|
| 645 |
+
# Robust checks
|
| 646 |
+
sid = request.args.get('sid') or request.form.get('sid')
|
| 647 |
if not sid: return jsonify({'success': False, 'message': 'Missing session ID'}), 400
|
| 648 |
+
|
| 649 |
+
file = None
|
| 650 |
+
if 'file' in request.files:
|
| 651 |
+
file = request.files['file']
|
| 652 |
+
|
| 653 |
+
if not file or file.filename == '':
|
| 654 |
+
# Debugging aid
|
| 655 |
+
print(f"Files keys: {list(request.files.keys())}")
|
| 656 |
+
return jsonify({'success': False, 'message': 'No file uploaded'}), 400
|
| 657 |
|
| 658 |
target_pages = request.form.get('target_pages', 4)
|
| 659 |
+
|
| 660 |
gen = EnhancedComicGenerator(sid)
|
| 661 |
gen.cleanup()
|
| 662 |
+
file.save(gen.video_path)
|
| 663 |
gen.write_status("Starting...", 5)
|
| 664 |
|
| 665 |
threading.Thread(target=gen.run, args=(target_pages,)).start()
|