jhh6576 commited on
Commit
4a94eb3
·
verified ·
1 Parent(s): 3dd2e50

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +63 -48
app_enhanced.py CHANGED
@@ -12,13 +12,10 @@ import json
12
  import shutil
13
  from typing import List
14
  import traceback
15
- # <<< MODIFICATION START: Import for parallel processing >>>
16
  from concurrent.futures import ThreadPoolExecutor
17
- # <<< MODIFICATION END >>>
18
 
19
 
20
  # --- ROBUST IMPORTS WITH FALLBACKS ---
21
- # (Assuming these modules have a method to process a single image, e.g., enhance_single)
22
  try:
23
  from backend.keyframes.keyframes import black_bar_crop
24
  print("✅ Black bar cropping module loaded.")
@@ -416,9 +413,7 @@ class EnhancedComicGenerator:
416
  with open('test1.srt', 'r', encoding='utf-8') as f:
417
  all_subs = list(srt.parse(f.read()))
418
  key_moments = [{'index': s.index, 'text': s.content, 'start': s.start.total_seconds(), 'end': s.end.total_seconds()} for s in all_subs]
419
- # <<< MODIFICATION START: Reduced default max_frames for speed >>>
420
  if not self.generate_keyframes_from_moments(self.video_path, key_moments, max_frames=32):
421
- # <<< MODIFICATION END >>>
422
  raise Exception("Keyframe extraction failed.")
423
  update_status("Cropping black bars...", 45)
424
  black_x, black_y, _, _ = black_bar_crop()
@@ -441,7 +436,6 @@ class EnhancedComicGenerator:
441
  update_status(f"Error: {e}", -1)
442
  return False
443
 
444
- # <<< MODIFICATION START: Parallelized enhancement functions for speed >>>
445
  def _enhance_all_images(self, single_image_path=None):
446
  try:
447
  enhancer = SimpleColorEnhancer()
@@ -465,11 +459,8 @@ class EnhancedComicGenerator:
465
  list(executor.map(enhancer.enhance_single, frame_paths))
466
  except Exception as e:
467
  print(f"⚠️ Quality enhancement failed: {e}")
468
- # <<< MODIFICATION END >>>
469
 
470
- # <<< MODIFICATION START: Parallelized bubble placement for speed >>>
471
  def _process_bubble_for_frame(self, frame_file):
472
- """Helper function to process a single frame for bubble placement."""
473
  frame_path = os.path.join(self.frames_dir, frame_file)
474
  dialogue = self.frame_metadata.get(frame_file, {}).get('dialogue', "")
475
  try:
@@ -491,11 +482,9 @@ class EnhancedComicGenerator:
491
  self.frame_metadata = json.load(f)
492
 
493
  with ThreadPoolExecutor() as executor:
494
- # Map the processing function to each frame file and collect results
495
  bubbles = list(executor.map(self._process_bubble_for_frame, frame_files))
496
 
497
  return bubbles
498
- # <<< MODIFICATION END >>>
499
 
500
  def _generate_pages(self, bubbles):
501
  try:
@@ -549,36 +538,67 @@ class EnhancedComicGenerator:
549
  .panel img { width: 100%; height: 100%; object-fit: cover; object-position: center; transition: transform 0.1s ease-out; }
550
  .panel img.pannable { cursor: grab; }
551
  .panel img.panning { cursor: grabbing; }
552
- .speech-bubble { position: absolute; display: flex; justify-content: center; align-items: center; width: 150px; height: 80px; min-width: 50px; min-height: 30px; box-sizing: border-box; padding: 8px; box-shadow: 2px 2px 5px rgba(0,0,0,0.3); z-index: 10; cursor: move; overflow: visible; font-size: 13px; font-weight: bold; text-align: center; font-family: 'Comic Neue', cursive; }
553
- .bubble-text { padding: 2px; word-wrap: break-word; }
554
  .speech-bubble.selected { outline: 2px dashed #4CAF50; }
555
  .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); font: inherit; text-align: center; resize: none; padding: 8px; z-index: 102; }
556
 
557
- /* <<< MODIFICATION START: New CSS for 'speech' bubble with curvy tail >>> */
558
  .speech-bubble.speech {
559
- /* Use a CSS variable for dynamic color changes from JS */
560
- background-color: var(--bubble-fill-color, white);
561
- border: 2px solid #333;
562
- color: #333;
563
- border-radius: 15px;
 
 
 
564
  }
565
 
566
- .speech-bubble.speech::after {
567
- content: '';
 
568
  position: absolute;
569
- width: 20px;
570
- height: 20px;
571
- background: var(--bubble-fill-color, white);
572
- border-right: 2px solid #333;
573
- border-bottom: 2px solid #333;
574
- /* The combination of rotate and border-radius creates the curve */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  }
576
-
577
- /* 4-WAY CURVY TAIL POSITIONING */
578
- .speech-bubble.speech.tail-bl::after { bottom: -11px; left: 30px; transform: rotate(45deg); border-radius: 0 0 12px 0; border-left: none; border-top: none;}
579
- .speech-bubble.speech.tail-br::after { bottom: -11px; right: 30px; transform: rotate(135deg); border-radius: 0 0 12px 0; border-left: none; border-top: none;}
580
- .speech-bubble.speech.tail-tr::after { top: -11px; right: 30px; transform: rotate(225deg); border-radius: 0 0 12px 0; border-left: none; border-top: none;}
581
- .speech-bubble.speech.tail-tl::after { top: -11px; left: 30px; transform: rotate(315deg); border-radius: 0 0 12px 0; border-left: none; border-top: none;}
582
  /* <<< MODIFICATION END >>> */
583
 
584
  .speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
@@ -635,7 +655,7 @@ class EnhancedComicGenerator:
635
  <div class="color-picker-grid">
636
  <div>
637
  <label for="bubble-text-color">Text</label>
638
- <input type="color" id="bubble-text-color" value="#000000" disabled>
639
  </div>
640
  <div>
641
  <label for="bubble-fill-color">Fill</label>
@@ -730,17 +750,13 @@ class EnhancedComicGenerator:
730
  document.getElementById('zoom-slider').addEventListener('input', handleZoom);
731
 
732
  document.getElementById('bubble-text-color').addEventListener('input', (e) => {
733
- if(currentlySelectedBubble) currentlySelectedBubble.style.color = e.target.value;
734
  });
735
- // <<< MODIFICATION START: Re-enabled fill color picker for all bubble types >>>
736
  document.getElementById('bubble-fill-color').addEventListener('input', (e) => {
737
  if(currentlySelectedBubble) {
738
- // This now works for all bubbles, including the new 'speech' one.
739
- // We use a CSS variable to color the bubble and its pseudo-element tail.
740
  currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
741
  }
742
  });
743
- // <<< MODIFICATION END >>>
744
 
745
  document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
746
  document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
@@ -773,7 +789,7 @@ class EnhancedComicGenerator:
773
  }
774
 
775
  const rgbToHex = (rgb) => {
776
- if (!rgb || !rgb.startsWith('rgb')) return '#000000';
777
  let sep = rgb.indexOf(",") > -1 ? "," : " ";
778
  rgb = rgb.substr(4).split(")")[0].split(sep);
779
  let r = (+rgb[0]).toString(16), g = (+rgb[1]).toString(16), b = (+rgb[2]).toString(16);
@@ -848,15 +864,14 @@ class EnhancedComicGenerator:
848
  currentlySelectedPanel = null;
849
 
850
  const styles = window.getComputedStyle(currentlySelectedBubble);
851
- document.getElementById('bubble-text-color').value = rgbToHex(styles.color);
852
-
853
- // <<< MODIFICATION START: Updated color picker logic >>>
 
854
  const fillColorPicker = document.getElementById('bubble-fill-color');
855
- fillColorPicker.disabled = false; // Always enabled
856
- // Read the value from the CSS variable if it exists, otherwise from the computed style
857
- const currentFill = styles.getPropertyValue('--bubble-fill-color').trim();
858
- fillColorPicker.value = currentFill ? currentFill : rgbToHex(styles.backgroundColor);
859
- // <<< MODIFICATION END >>>
860
 
861
  document.getElementById('bubble-type-select').value = currentlySelectedBubble.dataset.type || 'speech';
862
  document.getElementById('font-select').value = styles.fontFamily.split(',')[0].replace(/"/g, "").replace(/'/g, "").trim();
 
12
  import shutil
13
  from typing import List
14
  import traceback
 
15
  from concurrent.futures import ThreadPoolExecutor
 
16
 
17
 
18
  # --- ROBUST IMPORTS WITH FALLBACKS ---
 
19
  try:
20
  from backend.keyframes.keyframes import black_bar_crop
21
  print("✅ Black bar cropping module loaded.")
 
413
  with open('test1.srt', 'r', encoding='utf-8') as f:
414
  all_subs = list(srt.parse(f.read()))
415
  key_moments = [{'index': s.index, 'text': s.content, 'start': s.start.total_seconds(), 'end': s.end.total_seconds()} for s in all_subs]
 
416
  if not self.generate_keyframes_from_moments(self.video_path, key_moments, max_frames=32):
 
417
  raise Exception("Keyframe extraction failed.")
418
  update_status("Cropping black bars...", 45)
419
  black_x, black_y, _, _ = black_bar_crop()
 
436
  update_status(f"Error: {e}", -1)
437
  return False
438
 
 
439
  def _enhance_all_images(self, single_image_path=None):
440
  try:
441
  enhancer = SimpleColorEnhancer()
 
459
  list(executor.map(enhancer.enhance_single, frame_paths))
460
  except Exception as e:
461
  print(f"⚠️ Quality enhancement failed: {e}")
 
462
 
 
463
  def _process_bubble_for_frame(self, frame_file):
 
464
  frame_path = os.path.join(self.frames_dir, frame_file)
465
  dialogue = self.frame_metadata.get(frame_file, {}).get('dialogue', "")
466
  try:
 
482
  self.frame_metadata = json.load(f)
483
 
484
  with ThreadPoolExecutor() as executor:
 
485
  bubbles = list(executor.map(self._process_bubble_for_frame, frame_files))
486
 
487
  return bubbles
 
488
 
489
  def _generate_pages(self, bubbles):
490
  try:
 
538
  .panel img { width: 100%; height: 100%; object-fit: cover; object-position: center; transition: transform 0.1s ease-out; }
539
  .panel img.pannable { cursor: grab; }
540
  .panel img.panning { cursor: grabbing; }
541
+ .speech-bubble { position: absolute; display: flex; justify-content: center; align-items: center; width: 150px; height: 80px; min-width: 50px; min-height: 30px; box-sizing: border-box; z-index: 10; cursor: move; overflow: visible; font-size: 13px; font-weight: bold; text-align: center; font-family: 'Comic Neue', cursive; }
542
+ .bubble-text { padding: 1em; word-wrap: break-word; }
543
  .speech-bubble.selected { outline: 2px dashed #4CAF50; }
544
  .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); font: inherit; text-align: center; resize: none; padding: 8px; z-index: 102; }
545
 
546
+ /* <<< MODIFICATION START: New Advanced CSS for 'speech' bubble >>> */
547
  .speech-bubble.speech {
548
+ /* Main bubble properties */
549
+ color: var(--bubble-text-color, #333);
550
+ font-size: 16px;
551
+ padding: 0; /* Padding is handled by the bubble-text span now */
552
+ border-radius: 1.2em;
553
+ background: var(--bubble-fill-color, white);
554
+ box-shadow: 0 0 5px rgba(0,0,0,0.2);
555
+ border: none;
556
  }
557
 
558
+ .speech-bubble.speech::before {
559
+ /* This pseudo-element creates the complex tail */
560
+ content: "";
561
  position: absolute;
562
+ background: inherit; /* Inherits the dynamic color from the parent */
563
+
564
+ /* Tail dimensions */
565
+ width: 3em; /* base */
566
+ height: 1.8em; /* height */
567
+
568
+ /* The magic mask that carves the tail shape */
569
+ -webkit-mask: radial-gradient(calc(0.6 * 100%) 105% at 100% 0, #0000 99%, #000 101%);
570
+ mask: radial-gradient(calc(0.6 * 100%) 105% at 100% 0, #0000 99%, #000 101%);
571
+ }
572
+
573
+ /* 4-WAY TAIL ROTATION CLASSES */
574
+ .speech-bubble.speech.tail-bl::before { /* Bottom-Left */
575
+ bottom: -0.9em;
576
+ left: 1.5em;
577
+ transform-origin: bottom left;
578
+ transform: skewX(30deg) rotate(-10deg);
579
+ border-bottom-right-radius: 80%;
580
+ }
581
+ .speech-bubble.speech.tail-br::before { /* Bottom-Right */
582
+ bottom: -0.9em;
583
+ right: 1.5em;
584
+ transform-origin: bottom right;
585
+ transform: skewX(-30deg) rotate(10deg) scaleX(-1);
586
+ border-bottom-left-radius: 80%;
587
+ }
588
+ .speech-bubble.speech.tail-tr::before { /* Top-Right */
589
+ top: -0.9em;
590
+ right: 1.5em;
591
+ transform-origin: top right;
592
+ transform: skewX(30deg) rotate(10deg) scale(-1, -1);
593
+ border-top-left-radius: 80%;
594
+ }
595
+ .speech-bubble.speech.tail-tl::before { /* Top-Left */
596
+ top: -0.9em;
597
+ left: 1.5em;
598
+ transform-origin: top left;
599
+ transform: skewX(-30deg) rotate(-10deg) scaleY(-1);
600
+ border-top-right-radius: 80%;
601
  }
 
 
 
 
 
 
602
  /* <<< MODIFICATION END >>> */
603
 
604
  .speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
 
655
  <div class="color-picker-grid">
656
  <div>
657
  <label for="bubble-text-color">Text</label>
658
+ <input type="color" id="bubble-text-color" value="#333333" disabled>
659
  </div>
660
  <div>
661
  <label for="bubble-fill-color">Fill</label>
 
750
  document.getElementById('zoom-slider').addEventListener('input', handleZoom);
751
 
752
  document.getElementById('bubble-text-color').addEventListener('input', (e) => {
753
+ if(currentlySelectedBubble) currentlySelectedBubble.style.setProperty('--bubble-text-color', e.target.value);
754
  });
 
755
  document.getElementById('bubble-fill-color').addEventListener('input', (e) => {
756
  if(currentlySelectedBubble) {
 
 
757
  currentlySelectedBubble.style.setProperty('--bubble-fill-color', e.target.value);
758
  }
759
  });
 
760
 
761
  document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
762
  document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
 
789
  }
790
 
791
  const rgbToHex = (rgb) => {
792
+ if (!rgb || !rgb.startsWith('rgb')) return '#ffffff';
793
  let sep = rgb.indexOf(",") > -1 ? "," : " ";
794
  rgb = rgb.substr(4).split(")")[0].split(sep);
795
  let r = (+rgb[0]).toString(16), g = (+rgb[1]).toString(16), b = (+rgb[2]).toString(16);
 
864
  currentlySelectedPanel = null;
865
 
866
  const styles = window.getComputedStyle(currentlySelectedBubble);
867
+ const textColorPicker = document.getElementById('bubble-text-color');
868
+ const currentTextColor = styles.getPropertyValue('--bubble-text-color').trim();
869
+ textColorPicker.value = currentTextColor ? currentTextColor : rgbToHex(styles.color);
870
+
871
  const fillColorPicker = document.getElementById('bubble-fill-color');
872
+ fillColorPicker.disabled = false;
873
+ const currentFillColor = styles.getPropertyValue('--bubble-fill-color').trim();
874
+ fillColorPicker.value = currentFillColor ? currentFillColor : rgbToHex(styles.backgroundColor);
 
 
875
 
876
  document.getElementById('bubble-type-select').value = currentlySelectedBubble.dataset.type || 'speech';
877
  document.getElementById('font-select').value = styles.fontFamily.split(',')[0].replace(/"/g, "").replace(/'/g, "").trim();