tester343 commited on
Commit
777f379
·
verified ·
1 Parent(s): ab47677

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +45 -34
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")
@@ -51,7 +53,7 @@ def generate_save_code(length=8):
51
  # 🧱 DATA CLASSES
52
  # ======================================================
53
  def bubble(dialog="", x=50, y=20, type='speech'):
54
- # Determine CSS classes based on type (Logic from safwe.py)
55
  classes = f"speech-bubble {type}"
56
  if type == 'speech':
57
  classes += " tail-bottom"
@@ -102,15 +104,19 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
102
  if os.path.exists('test1.srt'):
103
  shutil.move('test1.srt', user_srt)
104
  elif not os.path.exists(user_srt):
105
- with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
 
106
  except:
107
- with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
 
108
 
109
  with open(user_srt, 'r', encoding='utf-8') as f:
110
- try: all_subs = list(srt.parse(f.read()))
111
- except: all_subs = []
 
 
112
 
113
- valid_subs = [s for s in all_subs if s.content.strip()]
114
  if valid_subs:
115
  raw_moments = [{'text': s.content.strip(), 'start': s.start.total_seconds(), 'end': s.end.total_seconds()} for s in valid_subs]
116
  else:
@@ -140,14 +146,14 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
140
  cap.set(cv2.CAP_PROP_POS_MSEC, mid * 1000)
141
  ret, frame = cap.read()
142
  if ret:
143
- # 🎯 SQUARE PADDING (0% Cut)
144
  h, w = frame.shape[:2]
145
  sq_dim = max(h, w)
146
  square_img = np.zeros((sq_dim, sq_dim, 3), dtype=np.uint8)
147
  x_off = (sq_dim - w) // 2
148
  y_off = (sq_dim - h) // 2
149
  square_img[y_off:y_off+h, x_off:x_off+w] = frame
150
- # Resize to standard high res
151
  square_img = cv2.resize(square_img, (1024, 1024))
152
 
153
  fname = f"frame_{count:04d}.png"
@@ -185,7 +191,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
185
  bx, by = 550, 450
186
  else:
187
  bx, by = 50, 50
188
-
189
  bubbles_list.append(bubble(dialog=dialogue, x=bx, y=by, type=b_type))
190
 
191
  pages = []
@@ -200,21 +206,14 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
200
  img = np.zeros((1024, 1024, 3), dtype=np.uint8); img[:] = (30,30,30)
201
  cv2.imwrite(os.path.join(frames_dir, fname), img)
202
  p_frames.append(fname)
203
- # Add hidden empty bubble
204
  p_bubbles.append(bubble(dialog="", x=-999, y=-999, type='speech'))
205
 
206
  if p_frames:
207
- # FIX: Use Page Class to avoid AttributeError
208
- pg_panels = [panel(image=f) for f in p_frames]
209
- pages.append(Page(panels=pg_panels, bubbles=p_bubbles))
210
-
211
- result = []
212
- for pg in pages:
213
- p_data = [p if isinstance(p, dict) else p.__dict__ for p in pg.panels]
214
- b_data = [b if isinstance(b, dict) else b.__dict__ for b in pg.bubbles]
215
- result.append({'panels': p_data, 'bubbles': b_data})
216
-
217
- return result
218
 
219
  @spaces.GPU
220
  def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
@@ -341,22 +340,34 @@ INDEX_HTML = '''
341
  .page-wrapper { display: flex; flex-direction: column; align-items: center; }
342
  .page-title { text-align: center; color: #eee; margin-bottom: 10px; font-size: 20px; font-weight: bold; }
343
 
 
344
  .comic-page {
345
  width: 800px;
346
  height: 800px;
347
  background: white;
348
  box-shadow: 0 5px 30px rgba(0,0,0,0.6);
349
  position: relative; overflow: hidden;
350
- border: 6px solid #000;
351
  }
352
 
 
353
  .comic-grid {
354
- width: 100%; height: 100%; position: relative; background: #000;
355
- --y: 50%; --t1: 100%; --t2: 100%; --b1: 100%; --b2: 100%; --gap: 3px;
 
 
356
  }
357
 
358
  .panel { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; background: #1a1a1a; cursor: grab; }
359
- .panel img { width: 100%; height: 100%; object-fit: cover; transform-origin: center; transition: transform 0.05s ease-out; display: block; }
 
 
 
 
 
 
 
 
360
  .panel img.panning { cursor: grabbing; transition: none; }
361
  .panel.selected { outline: 4px solid #3498db; z-index: 5; }
362
 
@@ -368,12 +379,13 @@ INDEX_HTML = '''
368
 
369
  /* Handles */
370
  .handle { position: absolute; width: 26px; height: 26px; border: 3px solid white; border-radius: 50%; transform: translate(-50%, -50%); z-index: 101; cursor: ew-resize; box-shadow: 0 2px 5px rgba(0,0,0,0.8); }
 
371
  .h-t1 { background: #3498db; left: var(--t1); top: 0%; margin-top: 15px; }
372
  .h-t2 { background: #3498db; left: var(--t2); top: 50%; margin-top: -15px; }
373
  .h-b1 { background: #2ecc71; left: var(--b1); top: 50%; margin-top: 15px; }
374
  .h-b2 { background: #2ecc71; left: var(--b2); top: 100%; margin-top: -15px; }
375
 
376
- /* SPEECH BUBBLES - STYLED */
377
  .speech-bubble {
378
  position: absolute; display: flex; justify-content: center; align-items: center;
379
  min-width: 60px; min-height: 40px; box-sizing: border-box;
@@ -702,8 +714,8 @@ INDEX_HTML = '''
702
  const type = data.type || 'speech';
703
  let className = data.classes || `speech-bubble ${type} tail-bottom`;
704
  if (type === 'thought' && !className.includes('pos-')) className += ' pos-bl';
705
- b.className = className;
706
 
 
707
  b.dataset.type = type;
708
  b.style.left = data.left; b.style.top = data.top;
709
  if(data.width) b.style.width = data.width;
@@ -714,12 +726,9 @@ INDEX_HTML = '''
714
 
715
  if(type === 'thought') { for(let i=1; i<=2; i++){ const d = document.createElement('div'); d.className = `thought-dot thought-dot-${i}`; b.appendChild(d); } }
716
 
717
- const textSpan = document.createElement('span'); textSpan.className = 'bubble-text'; textSpan.textContent = data.text || 'Text'; b.appendChild(textSpan);
718
- const resizer = document.createElement('div'); resizer.className = 'resize-handle';
719
- resizer.onmousedown = (e) => { e.stopPropagation(); dragType='resize'; activeObj={b:b, startW:b.offsetWidth, startH:b.offsetHeight, mx:e.clientX, my:e.clientY}; };
720
- b.appendChild(resizer);
721
 
722
- b.onmousedown = (e) => { if(e.target === resizer) return; e.stopPropagation(); selectBubble(b); dragType = 'bubble'; activeObj = b; dragStart = {x: e.clientX, y: e.clientY}; };
723
  b.ondblclick = (e) => { e.stopPropagation(); editBubbleText(b); };
724
  return b;
725
  }
@@ -759,6 +768,8 @@ INDEX_HTML = '''
759
  dragType = null; activeObj = null;
760
  });
761
 
 
 
762
  function selectBubble(el) {
763
  if(selectedBubble) selectedBubble.classList.remove('selected');
764
  selectedBubble = el; el.classList.add('selected');
@@ -857,7 +868,7 @@ INDEX_HTML = '''
857
  const layout = { t1: grid.style.getPropertyValue('--t1')||'100%', t2: grid.style.getPropertyValue('--t2')||'100%', b1: grid.style.getPropertyValue('--b1')||'100%', b2: grid.style.getPropertyValue('--b2')||'100%' };
858
  const bubbles = [];
859
  grid.querySelectorAll('.speech-bubble').forEach(b => {
860
- bubbles.push({ text: b.querySelector('.bubble-text').textContent, left: b.style.left, top: b.style.top, width: b.style.width, height: b.style.height, type: b.dataset.type, colors: { fill: b.style.getPropertyValue('--bubble-fill'), text: b.style.getPropertyValue('--bubble-text') }, tailPos: b.style.getPropertyValue('--tail-pos') });
861
  });
862
  const panels = [];
863
  grid.querySelectorAll('.panel').forEach(pan => {
 
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")
 
53
  # 🧱 DATA CLASSES
54
  # ======================================================
55
  def bubble(dialog="", x=50, y=20, type='speech'):
56
+ # Apply classes
57
  classes = f"speech-bubble {type}"
58
  if type == 'speech':
59
  classes += " tail-bottom"
 
104
  if os.path.exists('test1.srt'):
105
  shutil.move('test1.srt', user_srt)
106
  elif not os.path.exists(user_srt):
107
+ with open(user_srt, 'w') as f:
108
+ f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
109
  except:
110
+ with open(user_srt, 'w') as f:
111
+ f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
112
 
113
  with open(user_srt, 'r', encoding='utf-8') as f:
114
+ try:
115
+ all_subs = list(srt.parse(f.read()))
116
+ except:
117
+ all_subs = []
118
 
119
+ valid_subs = [s for s in all_subs if s.content and s.content.strip()]
120
  if valid_subs:
121
  raw_moments = [{'text': s.content.strip(), 'start': s.start.total_seconds(), 'end': s.end.total_seconds()} for s in valid_subs]
122
  else:
 
146
  cap.set(cv2.CAP_PROP_POS_MSEC, mid * 1000)
147
  ret, frame = cap.read()
148
  if ret:
149
+ # 🎯 SQUARE PADDING (0% Cut) - HD Output
150
  h, w = frame.shape[:2]
151
  sq_dim = max(h, w)
152
  square_img = np.zeros((sq_dim, sq_dim, 3), dtype=np.uint8)
153
  x_off = (sq_dim - w) // 2
154
  y_off = (sq_dim - h) // 2
155
  square_img[y_off:y_off+h, x_off:x_off+w] = frame
156
+ # Resize to standard high res (1024x1024)
157
  square_img = cv2.resize(square_img, (1024, 1024))
158
 
159
  fname = f"frame_{count:04d}.png"
 
191
  bx, by = 550, 450
192
  else:
193
  bx, by = 50, 50
194
+
195
  bubbles_list.append(bubble(dialog=dialogue, x=bx, y=by, type=b_type))
196
 
197
  pages = []
 
206
  img = np.zeros((1024, 1024, 3), dtype=np.uint8); img[:] = (30,30,30)
207
  cv2.imwrite(os.path.join(frames_dir, fname), img)
208
  p_frames.append(fname)
209
+ # Add dummy bubble to keep count synced, but off-screen or empty
210
  p_bubbles.append(bubble(dialog="", x=-999, y=-999, type='speech'))
211
 
212
  if p_frames:
213
+ pg_panels = [{'image': f} for f in p_frames]
214
+ pages.append({'panels': pg_panels, 'bubbles': p_bubbles})
215
+
216
+ return pages
 
 
 
 
 
 
 
217
 
218
  @spaces.GPU
219
  def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
 
340
  .page-wrapper { display: flex; flex-direction: column; align-items: center; }
341
  .page-title { text-align: center; color: #eee; margin-bottom: 10px; font-size: 20px; font-weight: bold; }
342
 
343
+ /* 🎯 5px White Border on Page */
344
  .comic-page {
345
  width: 800px;
346
  height: 800px;
347
  background: white;
348
  box-shadow: 0 5px 30px rgba(0,0,0,0.6);
349
  position: relative; overflow: hidden;
350
+ border: 5px solid #ffffff;
351
  }
352
 
353
+ /* 🎯 5px White Borders between panels */
354
  .comic-grid {
355
+ width: 100%; height: 100%; position: relative;
356
+ background: #ffffff; /* White background makes white lines */
357
+ --y: 50%; --t1: 100%; --t2: 100%; --b1: 100%; --b2: 100%;
358
+ --gap: 5px; /* 5px gap size */
359
  }
360
 
361
  .panel { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; background: #1a1a1a; cursor: grab; }
362
+
363
+ /* IMAGE: Cover ensures it fills. Zoom allows control. */
364
+ .panel img {
365
+ width: 100%; height: 100%;
366
+ object-fit: cover;
367
+ transform-origin: center;
368
+ transition: transform 0.05s ease-out;
369
+ display: block;
370
+ }
371
  .panel img.panning { cursor: grabbing; transition: none; }
372
  .panel.selected { outline: 4px solid #3498db; z-index: 5; }
373
 
 
379
 
380
  /* Handles */
381
  .handle { position: absolute; width: 26px; height: 26px; border: 3px solid white; border-radius: 50%; transform: translate(-50%, -50%); z-index: 101; cursor: ew-resize; box-shadow: 0 2px 5px rgba(0,0,0,0.8); }
382
+ .handle:hover { transform: translate(-50%, -50%) scale(1.3); }
383
  .h-t1 { background: #3498db; left: var(--t1); top: 0%; margin-top: 15px; }
384
  .h-t2 { background: #3498db; left: var(--t2); top: 50%; margin-top: -15px; }
385
  .h-b1 { background: #2ecc71; left: var(--b1); top: 50%; margin-top: 15px; }
386
  .h-b2 { background: #2ecc71; left: var(--b2); top: 100%; margin-top: -15px; }
387
 
388
+ /* SPEECH BUBBLES */
389
  .speech-bubble {
390
  position: absolute; display: flex; justify-content: center; align-items: center;
391
  min-width: 60px; min-height: 40px; box-sizing: border-box;
 
714
  const type = data.type || 'speech';
715
  let className = data.classes || `speech-bubble ${type} tail-bottom`;
716
  if (type === 'thought' && !className.includes('pos-')) className += ' pos-bl';
 
717
 
718
+ b.className = className;
719
  b.dataset.type = type;
720
  b.style.left = data.left; b.style.top = data.top;
721
  if(data.width) b.style.width = data.width;
 
726
 
727
  if(type === 'thought') { for(let i=1; i<=2; i++){ const d = document.createElement('div'); d.className = `thought-dot thought-dot-${i}`; b.appendChild(d); } }
728
 
729
+ ['nw', 'ne', 'sw', 'se'].forEach(dir => { const handle = document.createElement('div'); handle.className = `resize-handle ${dir}`; handle.onmousedown = (e) => startResize(e, dir); b.appendChild(handle); });
 
 
 
730
 
731
+ b.onmousedown = (e) => { if(e.target.classList.contains('resize-handle')) return; e.stopPropagation(); selectBubble(b); dragType = 'bubble'; activeObj = b; dragStart = {x: e.clientX, y: e.clientY}; };
732
  b.ondblclick = (e) => { e.stopPropagation(); editBubbleText(b); };
733
  return b;
734
  }
 
768
  dragType = null; activeObj = null;
769
  });
770
 
771
+ function startResize(e, dir) { e.preventDefault(); e.stopPropagation(); dragType = 'resize'; activeObj = { b: e.target.parentElement, startW: e.target.parentElement.offsetWidth, startH: e.target.parentElement.offsetHeight, mx: e.clientX, my: e.clientY }; }
772
+
773
  function selectBubble(el) {
774
  if(selectedBubble) selectedBubble.classList.remove('selected');
775
  selectedBubble = el; el.classList.add('selected');
 
868
  const layout = { t1: grid.style.getPropertyValue('--t1')||'100%', t2: grid.style.getPropertyValue('--t2')||'100%', b1: grid.style.getPropertyValue('--b1')||'100%', b2: grid.style.getPropertyValue('--b2')||'100%' };
869
  const bubbles = [];
870
  grid.querySelectorAll('.speech-bubble').forEach(b => {
871
+ bubbles.push({ text: b.querySelector('.bubble-text').textContent, left: b.style.left, top: b.style.top, width: b.style.width, height: b.style.height, type: b.dataset.type, colors: { fill: b.style.getPropertyValue('--bubble-fill'), text: b.style.getPropertyValue('--bubble-text') }, tailPos: b.style.getPropertyValue('--tail-pos'), classes: b.className });
872
  });
873
  const panels = [];
874
  grid.querySelectorAll('.panel').forEach(pan => {