tester343 commited on
Commit
c7a0992
·
verified ·
1 Parent(s): e829592

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +44 -24
app_enhanced.py CHANGED
@@ -1,4 +1,4 @@
1
- import spaces
2
  import os
3
  import time
4
  import threading
@@ -28,8 +28,10 @@ def gpu_warmup():
28
  # ======================================================
29
  if os.path.exists('/data'):
30
  BASE_STORAGE_PATH = '/data'
 
31
  else:
32
  BASE_STORAGE_PATH = '.'
 
33
 
34
  BASE_USER_DIR = os.path.join(BASE_STORAGE_PATH, "userdata")
35
  SAVED_COMICS_DIR = os.path.join(BASE_STORAGE_PATH, "saved_comics")
@@ -70,24 +72,32 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
70
  from backend.subtitles.subs_real import get_real_subtitles
71
 
72
  cap = cv2.VideoCapture(video_path)
73
- if not cap.isOpened(): raise Exception("Cannot open video")
 
 
74
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
75
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
76
  duration = total_frames / fps
77
  cap.release()
78
 
79
- # Subtitles
80
  user_srt = os.path.join(user_dir, 'subs.srt')
81
  try:
82
  get_real_subtitles(video_path)
83
- if os.path.exists('test1.srt'): shutil.move('test1.srt', user_srt)
84
- elif not os.path.exists(user_srt): with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
 
 
 
85
  except:
86
- with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
 
87
 
88
  with open(user_srt, 'r', encoding='utf-8') as f:
89
- try: all_subs = list(srt.parse(f.read()))
90
- except: all_subs = []
 
 
91
 
92
  valid_subs = [s for s in all_subs if s.content.strip()]
93
  if valid_subs:
@@ -102,7 +112,8 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
102
  selected_moments = []
103
  if not raw_moments:
104
  times = np.linspace(1, max(1, duration-1), total_panels_needed)
105
- for t in times: selected_moments.append({'text': '', 'start': t, 'end': t+1})
 
106
  elif len(raw_moments) <= total_panels_needed:
107
  selected_moments = raw_moments
108
  else:
@@ -120,7 +131,6 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
120
  ret, frame = cap.read()
121
  if ret:
122
  # 🎯 EXTRACT FULL HD (1280x720) - NO CROP
123
- # Frontend handles Zoom/Fit
124
  frame = cv2.resize(frame, (1280, 720))
125
  fname = f"frame_{count:04d}.png"
126
  p = os.path.join(frames_dir, fname)
@@ -130,7 +140,8 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
130
  count += 1
131
  cap.release()
132
 
133
- with open(metadata_path, 'w') as f: json.dump(frame_metadata, f, indent=2)
 
134
 
135
  bubbles_list = []
136
  for f in frame_files_ordered:
@@ -149,13 +160,13 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
149
 
150
  while len(p_frames) < 4:
151
  fname = f"empty_{i}_{len(p_frames)}.png"
152
- img = np.zeros((720, 1280, 3), dtype=np.uint8); img[:] = (30,30,30)
 
153
  cv2.imwrite(os.path.join(frames_dir, fname), img)
154
  p_frames.append(fname)
155
  p_bubbles.append(bubble(dialog="", type='speech'))
156
 
157
  if p_frames:
158
- # Create Page Object
159
  pg_panels = [{'image': f} for f in p_frames]
160
  pages.append({'panels': pg_panels, 'bubbles': p_bubbles})
161
 
@@ -165,8 +176,11 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
165
  def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
166
  import cv2
167
  import json
168
- if not os.path.exists(metadata_path): return {"success": False, "message": "No metadata"}
169
- with open(metadata_path, 'r') as f: meta = json.load(f)
 
 
 
170
 
171
  t = meta[fname]['time'] if isinstance(meta[fname], dict) else meta[fname]
172
  cap = cv2.VideoCapture(video_path)
@@ -181,9 +195,12 @@ def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
181
  if ret:
182
  frame = cv2.resize(frame, (1280, 720))
183
  cv2.imwrite(os.path.join(frames_dir, fname), frame)
184
- if isinstance(meta[fname], dict): meta[fname]['time'] = new_t
185
- else: meta[fname] = new_t
186
- with open(metadata_path, 'w') as f: json.dump(meta, f, indent=2)
 
 
 
187
  return {"success": True, "message": f"Time: {new_t:.2f}s"}
188
  return {"success": False}
189
 
@@ -202,8 +219,10 @@ class EnhancedComicGenerator:
202
  self.metadata_path = os.path.join(self.frames_dir, 'frame_metadata.json')
203
 
204
  def cleanup(self):
205
- if os.path.exists(self.frames_dir): shutil.rmtree(self.frames_dir)
206
- if os.path.exists(self.output_dir): shutil.rmtree(self.output_dir)
 
 
207
  os.makedirs(self.frames_dir, exist_ok=True)
208
  os.makedirs(self.output_dir, exist_ok=True)
209
 
@@ -240,19 +259,20 @@ INDEX_HTML = '''
240
 
241
  .page-input-group { margin: 20px 0; text-align: left; }
242
  .page-input-group label { font-weight: bold; font-size: 14px; display: block; margin-bottom: 5px; color: #ccc; }
243
- .page-input-group input { width: 100%; padding: 12px; border: 2px solid #555; background: #2c3e50; color: white; border-radius: 8px; }
244
 
245
  .submit-btn { width: 100%; padding: 15px; background: #2980b9; color: white; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; }
246
  .loader { width: 100px; height: 10px; background: #e67e22; margin: 20px auto; animation: load 1s infinite alternate; }
247
  @keyframes load { from { width: 20px; } to { width: 100px; } }
248
 
249
- /* === SQUARE LAYOUT === */
250
  .comic-wrapper { max-width: 1000px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; gap: 40px; }
251
  .page-wrapper { display: flex; flex-direction: column; align-items: center; }
252
  .page-title { text-align: center; color: #eee; margin-bottom: 10px; font-size: 20px; font-weight: bold; }
253
 
254
  .comic-page {
255
- width: 800px; height: 800px;
 
256
  background: white;
257
  box-shadow: 0 5px 30px rgba(0,0,0,0.6);
258
  position: relative; overflow: hidden;
@@ -292,7 +312,6 @@ INDEX_HTML = '''
292
  .bubble-text { padding: 0.8em; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; white-space: pre-wrap; pointer-events: none; border-radius: inherit; }
293
  .speech-bubble.selected { outline: 2px dashed #4CAF50; z-index: 100; }
294
 
295
- /* Bubble Styles */
296
  .speech-bubble.speech { background: var(--bubble-fill, #fff); color: var(--bubble-text, #000); border: 2px solid #000; border-radius: 50%; width: 180px; height: 100px; }
297
  .speech-bubble.speech::after { content: ''; position: absolute; bottom: -12px; left: var(--tail-pos); border: 12px solid transparent; border-top-color: #000; border-bottom: 0; margin-left: -12px; }
298
 
@@ -316,6 +335,7 @@ INDEX_HTML = '''
316
  .color-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
317
  .action-btn { background: #27ae60; color: white; }
318
  .reset-btn { background: #c0392b; color: white; }
 
319
  .save-btn { background: #8e44ad; color: white; }
320
 
321
  .tip { text-align:center; padding:10px; background:#e74c3c; color:white; font-weight:bold; margin-bottom:20px; border-radius:5px; }
 
1
+ import spaces # <--- CRITICAL: MUST BE THE FIRST IMPORT
2
  import os
3
  import time
4
  import threading
 
28
  # ======================================================
29
  if os.path.exists('/data'):
30
  BASE_STORAGE_PATH = '/data'
31
+ print("✅ Using Persistent Storage at /data")
32
  else:
33
  BASE_STORAGE_PATH = '.'
34
+ print("⚠️ Using Ephemeral Storage")
35
 
36
  BASE_USER_DIR = os.path.join(BASE_STORAGE_PATH, "userdata")
37
  SAVED_COMICS_DIR = os.path.join(BASE_STORAGE_PATH, "saved_comics")
 
72
  from backend.subtitles.subs_real import get_real_subtitles
73
 
74
  cap = cv2.VideoCapture(video_path)
75
+ if not cap.isOpened():
76
+ raise Exception("Cannot open video")
77
+
78
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
79
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
80
  duration = total_frames / fps
81
  cap.release()
82
 
83
+ # Subtitles logic with FIXED SYNTAX
84
  user_srt = os.path.join(user_dir, 'subs.srt')
85
  try:
86
  get_real_subtitles(video_path)
87
+ if os.path.exists('test1.srt'):
88
+ shutil.move('test1.srt', user_srt)
89
+ elif not os.path.exists(user_srt):
90
+ with open(user_srt, 'w') as f:
91
+ f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
92
  except:
93
+ with open(user_srt, 'w') as f:
94
+ f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
95
 
96
  with open(user_srt, 'r', encoding='utf-8') as f:
97
+ try:
98
+ all_subs = list(srt.parse(f.read()))
99
+ except:
100
+ all_subs = []
101
 
102
  valid_subs = [s for s in all_subs if s.content.strip()]
103
  if valid_subs:
 
112
  selected_moments = []
113
  if not raw_moments:
114
  times = np.linspace(1, max(1, duration-1), total_panels_needed)
115
+ for t in times:
116
+ selected_moments.append({'text': '', 'start': t, 'end': t+1})
117
  elif len(raw_moments) <= total_panels_needed:
118
  selected_moments = raw_moments
119
  else:
 
131
  ret, frame = cap.read()
132
  if ret:
133
  # 🎯 EXTRACT FULL HD (1280x720) - NO CROP
 
134
  frame = cv2.resize(frame, (1280, 720))
135
  fname = f"frame_{count:04d}.png"
136
  p = os.path.join(frames_dir, fname)
 
140
  count += 1
141
  cap.release()
142
 
143
+ with open(metadata_path, 'w') as f:
144
+ json.dump(frame_metadata, f, indent=2)
145
 
146
  bubbles_list = []
147
  for f in frame_files_ordered:
 
160
 
161
  while len(p_frames) < 4:
162
  fname = f"empty_{i}_{len(p_frames)}.png"
163
+ img = np.zeros((720, 1280, 3), dtype=np.uint8)
164
+ img[:] = (30,30,30)
165
  cv2.imwrite(os.path.join(frames_dir, fname), img)
166
  p_frames.append(fname)
167
  p_bubbles.append(bubble(dialog="", type='speech'))
168
 
169
  if p_frames:
 
170
  pg_panels = [{'image': f} for f in p_frames]
171
  pages.append({'panels': pg_panels, 'bubbles': p_bubbles})
172
 
 
176
  def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
177
  import cv2
178
  import json
179
+ if not os.path.exists(metadata_path):
180
+ return {"success": False, "message": "No metadata"}
181
+
182
+ with open(metadata_path, 'r') as f:
183
+ meta = json.load(f)
184
 
185
  t = meta[fname]['time'] if isinstance(meta[fname], dict) else meta[fname]
186
  cap = cv2.VideoCapture(video_path)
 
195
  if ret:
196
  frame = cv2.resize(frame, (1280, 720))
197
  cv2.imwrite(os.path.join(frames_dir, fname), frame)
198
+ if isinstance(meta[fname], dict):
199
+ meta[fname]['time'] = new_t
200
+ else:
201
+ meta[fname] = new_t
202
+ with open(metadata_path, 'w') as f:
203
+ json.dump(meta, f, indent=2)
204
  return {"success": True, "message": f"Time: {new_t:.2f}s"}
205
  return {"success": False}
206
 
 
219
  self.metadata_path = os.path.join(self.frames_dir, 'frame_metadata.json')
220
 
221
  def cleanup(self):
222
+ if os.path.exists(self.frames_dir):
223
+ shutil.rmtree(self.frames_dir)
224
+ if os.path.exists(self.output_dir):
225
+ shutil.rmtree(self.output_dir)
226
  os.makedirs(self.frames_dir, exist_ok=True)
227
  os.makedirs(self.output_dir, exist_ok=True)
228
 
 
259
 
260
  .page-input-group { margin: 20px 0; text-align: left; }
261
  .page-input-group label { font-weight: bold; font-size: 14px; display: block; margin-bottom: 5px; color: #ccc; }
262
+ .page-input-group input { width: 100%; padding: 12px; border: 2px solid #555; background: #2c3e50; color: white; border-radius: 8px; font-size: 16px; box-sizing: border-box; }
263
 
264
  .submit-btn { width: 100%; padding: 15px; background: #2980b9; color: white; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; }
265
  .loader { width: 100px; height: 10px; background: #e67e22; margin: 20px auto; animation: load 1s infinite alternate; }
266
  @keyframes load { from { width: 20px; } to { width: 100px; } }
267
 
268
+ /* === SQUARE COMIC LAYOUT (800x800) === */
269
  .comic-wrapper { max-width: 1000px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; gap: 40px; }
270
  .page-wrapper { display: flex; flex-direction: column; align-items: center; }
271
  .page-title { text-align: center; color: #eee; margin-bottom: 10px; font-size: 20px; font-weight: bold; }
272
 
273
  .comic-page {
274
+ width: 800px;
275
+ height: 800px;
276
  background: white;
277
  box-shadow: 0 5px 30px rgba(0,0,0,0.6);
278
  position: relative; overflow: hidden;
 
312
  .bubble-text { padding: 0.8em; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; white-space: pre-wrap; pointer-events: none; border-radius: inherit; }
313
  .speech-bubble.selected { outline: 2px dashed #4CAF50; z-index: 100; }
314
 
 
315
  .speech-bubble.speech { background: var(--bubble-fill, #fff); color: var(--bubble-text, #000); border: 2px solid #000; border-radius: 50%; width: 180px; height: 100px; }
316
  .speech-bubble.speech::after { content: ''; position: absolute; bottom: -12px; left: var(--tail-pos); border: 12px solid transparent; border-top-color: #000; border-bottom: 0; margin-left: -12px; }
317
 
 
335
  .color-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
336
  .action-btn { background: #27ae60; color: white; }
337
  .reset-btn { background: #c0392b; color: white; }
338
+ .secondary-btn { background: #f39c12; color: white; }
339
  .save-btn { background: #8e44ad; color: white; }
340
 
341
  .tip { text-align:center; padding:10px; background:#e74c3c; color:white; font-weight:bold; margin-bottom:20px; border-radius:5px; }