tester343 commited on
Commit
32a6ed4
·
verified ·
1 Parent(s): 396b6f1

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. 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
- # 🔧 APP CONFIG
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"🚀 Fast Generation Started: {video_path}")
82
 
83
  import cv2
84
  import srt
85
  import numpy as np
86
 
87
- # 1. Video Setup
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. Subtitle Processing
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. Time Selection
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. Fast Extraction Loop
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. Fast Bubble Creation (Skipped Face Detect for Speed)
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. Pagination
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
- # Pad with empty frames if needed
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) # 1 Frame jump
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("Waiting for GPU...", 5)
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
- # 🌐 ROUTES & FULL UI
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>Fast Comic Gen (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; }
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); // Faster polling
493
- else { alert("Upload failed"); location.reload(); }
 
 
 
 
494
  }
495
 
496
  async function checkStatus() {
@@ -674,15 +642,24 @@ def index():
674
 
675
  @app.route('/uploader', methods=['POST'])
676
  def upload():
677
- sid = request.args.get('sid')
 
678
  if not sid: return jsonify({'success': False, 'message': 'Missing session ID'}), 400
679
- if 'file' not in request.files: return jsonify({'success': False, 'message': 'No file'}), 400
 
 
 
 
 
 
 
 
680
 
681
  target_pages = request.form.get('target_pages', 4)
682
- f = request.files['file']
683
  gen = EnhancedComicGenerator(sid)
684
  gen.cleanup()
685
- f.save(gen.video_path)
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()