tester343 commited on
Commit
35cfc09
·
verified ·
1 Parent(s): f461aed

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +44 -12
app_enhanced.py CHANGED
@@ -1,4 +1,4 @@
1
- import spaces # <--- CRITICAL: MUST BE FIRST
2
  import os
3
  import time
4
  import threading
@@ -116,8 +116,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
116
  selected_moments = []
117
  if not raw_moments:
118
  times = np.linspace(1, duration-1, total_panels_needed)
119
- for t in times:
120
- selected_moments.append({'text': '', 'start': t, 'end': t+1})
121
  elif len(raw_moments) <= total_panels_needed:
122
  selected_moments = raw_moments
123
  else:
@@ -133,7 +132,6 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
133
  for i, moment in enumerate(selected_moments):
134
  mid = (moment['start'] + moment['end']) / 2
135
  if mid > duration: mid = duration - 1
136
-
137
  cap.set(cv2.CAP_PROP_POS_FRAMES, int(mid * fps))
138
  ret, frame = cap.read()
139
  if ret:
@@ -141,7 +139,6 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
141
  p = os.path.join(frames_dir, fname)
142
  cv2.imwrite(p, frame)
143
  os.sync()
144
-
145
  frame_metadata[fname] = {'dialogue': moment['text'], 'time': mid}
146
  frame_files_ordered.append(fname)
147
  count += 1
@@ -298,7 +295,7 @@ class EnhancedComicGenerator:
298
  json.dump({'message': msg, 'progress': prog}, f)
299
 
300
  # ======================================================
301
- # 🌐 ROUTES & HTML (FIXED SELECTION LOGIC)
302
  # ======================================================
303
 
304
  INDEX_HTML = '''
@@ -340,6 +337,7 @@ INDEX_HTML = '''
340
  .loader { width: 120px; height: 20px; background: radial-gradient(circle 10px, #e67e22 100%, transparent 0); background-size: 20px 20px; animation: ball 1s infinite linear; margin: 20px auto; }
341
  @keyframes ball { 0%{background-position:0 50%} 100%{background-position:100px 50%} }
342
 
 
343
  .comic-wrapper { max-width: 1000px; margin: 0 auto; }
344
  .page-wrapper { margin: 30px auto; display: flex; flex-direction: column; align-items: center; }
345
  .page-title { text-align: center; color: #333; margin-bottom: 10px; font-size: 18px; font-weight: bold; }
@@ -351,7 +349,7 @@ INDEX_HTML = '''
351
  .panel img.pannable { cursor: grab; }
352
  .panel img.panning { cursor: grabbing; }
353
 
354
- /* BUBBLES - FIX: pointer-events none on text to fix selection */
355
  .speech-bubble {
356
  position: absolute; display: flex; justify-content: center; align-items: center;
357
  width: 150px; height: 80px; min-width: 50px; min-height: 30px; box-sizing: border-box;
@@ -359,11 +357,12 @@ INDEX_HTML = '''
359
  font-size: 13px; text-align: center; overflow: visible;
360
  --tail-pos: 50%;
361
  }
 
362
  .bubble-text { padding: 0.8em; word-wrap: break-word; position: relative; z-index: 5; pointer-events: none; user-select: none; }
363
  .speech-bubble.selected { outline: 2px dashed #4CAF50; z-index: 100; }
364
  .speech-bubble textarea { position: absolute; top:0; left:0; width:100%; height:100%; box-sizing:border-box; border:1px solid #4CAF50; background:rgba(255,255,255,0.95); text-align:center; padding:8px; z-index:102; resize:none; font: inherit; }
365
 
366
- /* BUBBLE TYPES & TAILS */
367
  .speech-bubble.speech {
368
  --b: 3em; --h: 1.8em; --t: 0.6; --p: var(--tail-pos, 50%); --r: 1.2em;
369
  background: var(--bubble-fill-color, #4ECDC4);
@@ -386,11 +385,25 @@ INDEX_HTML = '''
386
  .speech-bubble.speech.tail-right { border-radius: var(--r); }
387
  .speech-bubble.speech.tail-right:before { left: 100%; top: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b))); transform: rotate(-90deg); transform-origin: top left; }
388
 
 
389
  .speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
390
- .speech-bubble.thought::before { display:none; }
391
  .thought-dot { position: absolute; background-color: white; border: 2px solid #555; border-radius: 50%; z-index: -1; }
392
- .thought-dot-1 { width: 20px; height: 20px; bottom: -20px; left: 15px; }
393
- .thought-dot-2 { width: 12px; height: 12px; bottom: -32px; left: 5px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
394
 
395
  .speech-bubble.reaction { background: #FFD700; border: 3px solid #E53935; color: #D32F2F; font-weight: 900; text-transform: uppercase; clip-path: polygon(0% 25%, 17% 21%, 17% 0%, 31% 16%, 50% 4%, 69% 16%, 83% 0%, 83% 21%, 100% 25%, 85% 45%, 95% 62%, 82% 79%, 100% 97%, 79% 89%, 60% 98%, 46% 82%, 27% 95%, 15% 78%, 5% 62%, 15% 45%); }
396
  .speech-bubble.narration { background: #FAFAFA; border: 2px solid #BDBDBD; color: #424242; border-radius: 3px; }
@@ -700,6 +713,8 @@ INDEX_HTML = '''
700
  const b = document.createElement('div');
701
  const type = data.type || 'speech';
702
  b.className = data.classes || `speech-bubble ${type} tail-bottom`;
 
 
703
  b.dataset.type = type;
704
  b.style.left = data.left; b.style.top = data.top;
705
  if(data.width) b.style.width = data.width; if(data.height) b.style.height = data.height;
@@ -712,11 +727,12 @@ INDEX_HTML = '''
712
  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); } }
713
 
714
  ['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); });
 
715
  b.onmousedown = (e) => {
716
  if(e.target.classList.contains('resize-handle')) return;
717
  e.stopPropagation(); selectBubble(b); isDragging = true; startX = e.clientX; startY = e.clientY; initX = b.offsetLeft; initY = b.offsetTop;
718
  };
719
- b.onclick = (e) => { e.stopPropagation(); }; // Critical Fix for selection
720
  b.ondblclick = (e) => { e.stopPropagation(); editBubbleText(b); };
721
  return b;
722
  }
@@ -779,24 +795,40 @@ INDEX_HTML = '''
779
  if(!selectedBubble) return;
780
  selectedBubble.dataset.type = type;
781
  selectedBubble.className = 'speech-bubble ' + type + ' selected';
 
 
 
 
 
782
  selectedBubble.querySelectorAll('.thought-dot').forEach(d=>d.remove());
783
  if(type === 'thought') { for(let i=1; i<=2; i++){ const d = document.createElement('div'); d.className = `thought-dot thought-dot-${i}`; selectedBubble.appendChild(d); } }
784
  saveDraft();
785
  }
786
 
787
  function changeFont(font) { if(!selectedBubble) return; selectedBubble.style.fontFamily = font; saveDraft(); }
 
788
  function rotateTail() {
789
  if(!selectedBubble) return;
790
  const type = selectedBubble.dataset.type;
 
791
  if(type === 'speech') {
792
  const positions = ['tail-bottom', 'tail-right', 'tail-top', 'tail-left'];
793
  let current = 0;
794
  positions.forEach((pos, i) => { if(selectedBubble.classList.contains(pos)) current = i; });
795
  selectedBubble.classList.remove(positions[current]);
796
  selectedBubble.classList.add(positions[(current + 1) % 4]);
 
 
 
 
 
 
 
 
797
  }
798
  saveDraft();
799
  }
 
800
  function slideTail(v) { if(selectedBubble) { selectedBubble.style.setProperty('--tail-pos', v+'%'); saveDraft(); } }
801
 
802
  document.getElementById('bubble-text-color').addEventListener('input', (e) => { if(selectedBubble) { selectedBubble.style.setProperty('--bubble-text-color', e.target.value); saveDraft(); } });
 
1
+ import spaces # <--- CRITICAL: MUST BE THE FIRST IMPORT
2
  import os
3
  import time
4
  import threading
 
116
  selected_moments = []
117
  if not raw_moments:
118
  times = np.linspace(1, duration-1, total_panels_needed)
119
+ for t in times: selected_moments.append({'text': '', 'start': t, 'end': t+1})
 
120
  elif len(raw_moments) <= total_panels_needed:
121
  selected_moments = raw_moments
122
  else:
 
132
  for i, moment in enumerate(selected_moments):
133
  mid = (moment['start'] + moment['end']) / 2
134
  if mid > duration: mid = duration - 1
 
135
  cap.set(cv2.CAP_PROP_POS_FRAMES, int(mid * fps))
136
  ret, frame = cap.read()
137
  if ret:
 
139
  p = os.path.join(frames_dir, fname)
140
  cv2.imwrite(p, frame)
141
  os.sync()
 
142
  frame_metadata[fname] = {'dialogue': moment['text'], 'time': mid}
143
  frame_files_ordered.append(fname)
144
  count += 1
 
295
  json.dump({'message': msg, 'progress': prog}, f)
296
 
297
  # ======================================================
298
+ # 🌐 ROUTES & FULL UI
299
  # ======================================================
300
 
301
  INDEX_HTML = '''
 
337
  .loader { width: 120px; height: 20px; background: radial-gradient(circle 10px, #e67e22 100%, transparent 0); background-size: 20px 20px; animation: ball 1s infinite linear; margin: 20px auto; }
338
  @keyframes ball { 0%{background-position:0 50%} 100%{background-position:100px 50%} }
339
 
340
+ /* COMIC LAYOUT */
341
  .comic-wrapper { max-width: 1000px; margin: 0 auto; }
342
  .page-wrapper { margin: 30px auto; display: flex; flex-direction: column; align-items: center; }
343
  .page-title { text-align: center; color: #333; margin-bottom: 10px; font-size: 18px; font-weight: bold; }
 
349
  .panel img.pannable { cursor: grab; }
350
  .panel img.panning { cursor: grabbing; }
351
 
352
+ /* SPEECH BUBBLES */
353
  .speech-bubble {
354
  position: absolute; display: flex; justify-content: center; align-items: center;
355
  width: 150px; height: 80px; min-width: 50px; min-height: 30px; box-sizing: border-box;
 
357
  font-size: 13px; text-align: center; overflow: visible;
358
  --tail-pos: 50%;
359
  }
360
+ /* POINTER EVENTS NONE ON TEXT TO FIX SELECTION BUG */
361
  .bubble-text { padding: 0.8em; word-wrap: break-word; position: relative; z-index: 5; pointer-events: none; user-select: none; }
362
  .speech-bubble.selected { outline: 2px dashed #4CAF50; z-index: 100; }
363
  .speech-bubble textarea { position: absolute; top:0; left:0; width:100%; height:100%; box-sizing:border-box; border:1px solid #4CAF50; background:rgba(255,255,255,0.95); text-align:center; padding:8px; z-index:102; resize:none; font: inherit; }
364
 
365
+ /* SPEECH BUBBLE CSS (Tails) */
366
  .speech-bubble.speech {
367
  --b: 3em; --h: 1.8em; --t: 0.6; --p: var(--tail-pos, 50%); --r: 1.2em;
368
  background: var(--bubble-fill-color, #4ECDC4);
 
385
  .speech-bubble.speech.tail-right { border-radius: var(--r); }
386
  .speech-bubble.speech.tail-right:before { left: 100%; top: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b))); transform: rotate(-90deg); transform-origin: top left; }
387
 
388
+ /* THOUGHT BUBBLE CSS (Fixed Rotation) */
389
  .speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
390
+ .speech-bubble.thought::before { display:none; }
391
  .thought-dot { position: absolute; background-color: white; border: 2px solid #555; border-radius: 50%; z-index: -1; }
392
+ .thought-dot-1 { width: 20px; height: 20px; }
393
+ .thought-dot-2 { width: 12px; height: 12px; }
394
+
395
+ /* Thought Tail Positions */
396
+ .speech-bubble.thought.pos-bl .thought-dot-1 { left: 20px; bottom: -20px; }
397
+ .speech-bubble.thought.pos-bl .thought-dot-2 { left: 10px; bottom: -32px; }
398
+
399
+ .speech-bubble.thought.pos-br .thought-dot-1 { right: 20px; bottom: -20px; }
400
+ .speech-bubble.thought.pos-br .thought-dot-2 { right: 10px; bottom: -32px; }
401
+
402
+ .speech-bubble.thought.pos-tr .thought-dot-1 { right: 20px; top: -20px; }
403
+ .speech-bubble.thought.pos-tr .thought-dot-2 { right: 10px; top: -32px; }
404
+
405
+ .speech-bubble.thought.pos-tl .thought-dot-1 { left: 20px; top: -20px; }
406
+ .speech-bubble.thought.pos-tl .thought-dot-2 { left: 10px; top: -32px; }
407
 
408
  .speech-bubble.reaction { background: #FFD700; border: 3px solid #E53935; color: #D32F2F; font-weight: 900; text-transform: uppercase; clip-path: polygon(0% 25%, 17% 21%, 17% 0%, 31% 16%, 50% 4%, 69% 16%, 83% 0%, 83% 21%, 100% 25%, 85% 45%, 95% 62%, 82% 79%, 100% 97%, 79% 89%, 60% 98%, 46% 82%, 27% 95%, 15% 78%, 5% 62%, 15% 45%); }
409
  .speech-bubble.narration { background: #FAFAFA; border: 2px solid #BDBDBD; color: #424242; border-radius: 3px; }
 
713
  const b = document.createElement('div');
714
  const type = data.type || 'speech';
715
  b.className = data.classes || `speech-bubble ${type} tail-bottom`;
716
+ if (type === 'thought' && !b.className.includes('pos-')) b.className += ' pos-bl'; // Default position for thought
717
+
718
  b.dataset.type = type;
719
  b.style.left = data.left; b.style.top = data.top;
720
  if(data.width) b.style.width = data.width; if(data.height) b.style.height = data.height;
 
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) => {
732
  if(e.target.classList.contains('resize-handle')) return;
733
  e.stopPropagation(); selectBubble(b); isDragging = true; startX = e.clientX; startY = e.clientY; initX = b.offsetLeft; initY = b.offsetTop;
734
  };
735
+ b.onclick = (e) => { e.stopPropagation(); };
736
  b.ondblclick = (e) => { e.stopPropagation(); editBubbleText(b); };
737
  return b;
738
  }
 
795
  if(!selectedBubble) return;
796
  selectedBubble.dataset.type = type;
797
  selectedBubble.className = 'speech-bubble ' + type + ' selected';
798
+
799
+ // Default tail for thought is pos-bl if not set
800
+ if(type === 'thought') selectedBubble.classList.add('pos-bl');
801
+ else selectedBubble.classList.add('tail-bottom'); // Default for speech
802
+
803
  selectedBubble.querySelectorAll('.thought-dot').forEach(d=>d.remove());
804
  if(type === 'thought') { for(let i=1; i<=2; i++){ const d = document.createElement('div'); d.className = `thought-dot thought-dot-${i}`; selectedBubble.appendChild(d); } }
805
  saveDraft();
806
  }
807
 
808
  function changeFont(font) { if(!selectedBubble) return; selectedBubble.style.fontFamily = font; saveDraft(); }
809
+
810
  function rotateTail() {
811
  if(!selectedBubble) return;
812
  const type = selectedBubble.dataset.type;
813
+
814
  if(type === 'speech') {
815
  const positions = ['tail-bottom', 'tail-right', 'tail-top', 'tail-left'];
816
  let current = 0;
817
  positions.forEach((pos, i) => { if(selectedBubble.classList.contains(pos)) current = i; });
818
  selectedBubble.classList.remove(positions[current]);
819
  selectedBubble.classList.add(positions[(current + 1) % 4]);
820
+ }
821
+ else if (type === 'thought') {
822
+ // Cycle specifically through the 4 CSS positions we defined
823
+ const positions = ['pos-bl', 'pos-br', 'pos-tr', 'pos-tl'];
824
+ let current = 0;
825
+ positions.forEach((pos, i) => { if(selectedBubble.classList.contains(pos)) current = i; });
826
+ selectedBubble.classList.remove(positions[current]);
827
+ selectedBubble.classList.add(positions[(current + 1) % 4]);
828
  }
829
  saveDraft();
830
  }
831
+
832
  function slideTail(v) { if(selectedBubble) { selectedBubble.style.setProperty('--tail-pos', v+'%'); saveDraft(); } }
833
 
834
  document.getElementById('bubble-text-color').addEventListener('input', (e) => { if(selectedBubble) { selectedBubble.style.setProperty('--bubble-text-color', e.target.value); saveDraft(); } });