jhh6576 commited on
Commit
34b0d80
·
verified ·
1 Parent(s): 02bf691

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +44 -3
app_enhanced.py CHANGED
@@ -308,6 +308,13 @@ class EnhancedComicGenerator:
308
  return {"success": False, "message": f"No frame at {target_time:.2f}s."}
309
  new_path = os.path.join(self.frames_dir, frame_filename)
310
  cv2.imwrite(new_path, frame)
 
 
 
 
 
 
 
311
  if isinstance(frame_to_time[frame_filename], dict):
312
  frame_to_time[frame_filename]['time'] = target_time
313
  else:
@@ -338,6 +345,13 @@ class EnhancedComicGenerator:
338
  if not ret or frame is None: return {"success": False, "message": f"Could not retrieve frame at {timestamp_seconds:.2f}s."}
339
  new_path = os.path.join(self.frames_dir, frame_filename)
340
  cv2.imwrite(new_path, frame)
 
 
 
 
 
 
 
341
  with open(metadata_path, 'r') as f: frame_to_time = json.load(f)
342
  if frame_filename in frame_to_time:
343
  if isinstance(frame_to_time[frame_filename], dict):
@@ -488,6 +502,7 @@ class EnhancedComicGenerator:
488
 
489
  def _copy_template_files(self):
490
  try:
 
491
  template_html = '''<!DOCTYPE html>
492
  <html lang="en">
493
  <head>
@@ -519,8 +534,9 @@ class EnhancedComicGenerator:
519
  .speech-bubble.narration { background: #FAFAFA; border: 2px solid #BDBDBD; color: #424242; border-radius: 3px; }
520
  .speech-bubble.idea { background: linear-gradient(180deg,#FFFDD0 0%, #FFF8B5 100%); border: 2px solid #FFA500; color: #6a4b00; border-radius: 40% 60% 40% 60% / 60% 40% 60% 40%; }
521
  .speech-bubble.speech::after, .speech-bubble.idea::after { content: ''; position: absolute; width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent; }
522
- .speech-bubble.speech::after { border-top: 10px solid #333; bottom: -9px; left: 20px; }
523
- .speech-bubble.idea::after { border-top: 10px solid #FFA500; bottom: -9px; left: 20px; }
 
524
  .speech-bubble.thought::after { display: none; }
525
  .thought-dot { position: absolute; background-color: white; border: 2px solid #555; border-radius: 50%; z-index: -1; }
526
  .thought-dot-1 { width: 20px; height: 20px; bottom: -20px; left: 15px; }
@@ -528,7 +544,8 @@ class EnhancedComicGenerator:
528
  .speech-bubble.flipped.speech::after, .speech-bubble.flipped.idea::after { left: auto; right: 20px; }
529
  .speech-bubble.flipped.thought .thought-dot-1 { left: auto; right: 15px; }
530
  .speech-bubble.flipped.thought .thought-dot-2 { left: auto; right: 5px; }
531
- .speech-bubble.flipped-vertical.speech::after, .speech-bubble.flipped-vertical.idea::after { bottom: auto; top: -9px; transform: rotate(180deg); }
 
532
  .speech-bubble.flipped-vertical.thought .thought-dot-1 { bottom: auto; top: -20px; }
533
  .speech-bubble.flipped-vertical.thought .thought-dot-2 { bottom: auto; top: -32px; }
534
  .resize-handle { position: absolute; width: 10px; height: 10px; background: #2196F3; border: 1px solid white; border-radius: 50%; display: none; z-index: 11; }
@@ -676,6 +693,7 @@ class EnhancedComicGenerator:
676
  document.getElementById('bubble-fill-color').addEventListener('input', (e) => {
677
  if(currentlySelectedBubble) currentlySelectedBubble.style.backgroundColor = e.target.value;
678
  });
 
679
  document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
680
  document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
681
  document.addEventListener('mouseleave', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
@@ -692,6 +710,7 @@ class EnhancedComicGenerator:
692
  handle.addEventListener('mousedown', (e) => startResize(e, dir));
693
  });
694
  }
 
695
  function createBubbleElement(data) {
696
  const bubbleDiv = document.createElement('div');
697
  bubbleDiv.dataset.id = data.id;
@@ -715,6 +734,7 @@ class EnhancedComicGenerator:
715
  if (b.length == 1) b = "0" + b;
716
  return "#" + r + g + b;
717
  }
 
718
  function applyBubbleType(bubble, type) {
719
  bubble.querySelectorAll('.thought-dot').forEach(el => el.remove());
720
  let classesToKeep = 'speech-bubble';
@@ -759,10 +779,12 @@ class EnhancedComicGenerator:
759
  currentlySelectedPanel = panel;
760
  selectBubble(null);
761
  }
 
762
  function selectBubble(bubble) {
763
  if (currentlySelectedBubble) currentlySelectedBubble.classList.remove('selected');
764
  currentlySelectedBubble = bubble;
765
  const bubbleControls = ['bubble-text-color', 'bubble-fill-color', 'bubble-type-select', 'font-select'];
 
766
  if (currentlySelectedBubble) {
767
  currentlySelectedBubble.classList.add('selected');
768
  if (currentlySelectedPanel) currentlySelectedPanel.classList.remove('selected');
@@ -773,6 +795,7 @@ class EnhancedComicGenerator:
773
  document.getElementById('bubble-fill-color').value = rgbToHex(styles.backgroundColor);
774
  document.getElementById('bubble-type-select').value = currentlySelectedBubble.dataset.type || 'speech';
775
  document.getElementById('font-select').value = styles.fontFamily.split(',')[0].replace(/"/g, "").replace(/'/g, "").trim();
 
776
  document.getElementById('zoom-slider').disabled = true;
777
  bubbleControls.forEach(id => document.getElementById(id).disabled = false);
778
  } else {
@@ -788,6 +811,7 @@ class EnhancedComicGenerator:
788
  const textarea = document.createElement('textarea');
789
  const originalHeight = bubble.offsetHeight;
790
  bubble.style.height = `${originalHeight}px`;
 
791
  textarea.value = textSpan.textContent;
792
  bubble.appendChild(textarea);
793
  textSpan.style.display = 'none';
@@ -801,6 +825,7 @@ class EnhancedComicGenerator:
801
  textarea.addEventListener('blur', finishEditing, { once: true });
802
  textarea.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); textarea.blur(); }});
803
  }
 
804
  function startDrag(e) {
805
  const bubble = e.target.closest('.speech-bubble');
806
  if (!bubble || currentlyEditing) return;
@@ -809,13 +834,16 @@ class EnhancedComicGenerator:
809
  const rect = bubble.getBoundingClientRect();
810
  offset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
811
  }
 
812
  function drag(e) {
813
  if (!draggedBubble) return;
814
  const parentRect = draggedBubble.parentElement.getBoundingClientRect();
815
  draggedBubble.style.left = `${e.clientX - parentRect.left - offset.x}px`;
816
  draggedBubble.style.top = `${e.clientY - parentRect.top - offset.y}px`;
817
  }
 
818
  function stopDrag() { draggedBubble = null; }
 
819
  function deleteBubble() {
820
  if (!currentlySelectedBubble) {
821
  alert("Please select a bubble to delete.");
@@ -827,6 +855,7 @@ class EnhancedComicGenerator:
827
  selectBubble(null);
828
  }
829
  }
 
830
  function startResize(e, dir) {
831
  e.preventDefault();
832
  e.stopPropagation();
@@ -841,11 +870,13 @@ class EnhancedComicGenerator:
841
  originalMouseX = e.clientX;
842
  originalMouseY = e.clientY;
843
  }
 
844
  function resizeBubble(e) {
845
  if (!isResizing || !currentlySelectedBubble) return;
846
  const dx = e.clientX - originalMouseX;
847
  const dy = e.clientY - originalMouseY;
848
  const bubble = currentlySelectedBubble;
 
849
  if (resizeHandle.includes('e')) bubble.style.width = `${originalWidth + dx}px`;
850
  if (resizeHandle.includes('w')) {
851
  bubble.style.width = `${originalWidth - dx}px`;
@@ -857,13 +888,16 @@ class EnhancedComicGenerator:
857
  bubble.style.top = `${originalY + dy}px`;
858
  }
859
  }
 
860
  function stopResize() { isResizing = false; }
 
861
  function clearSavedState() {
862
  if (confirm("Reset all edits?")) {
863
  localStorage.removeItem('comicEditorState');
864
  window.location.reload();
865
  }
866
  }
 
867
  async function exportPagesToPNG() {
868
  const pages = document.querySelectorAll('.comic-page');
869
  if (pages.length === 0) return alert("No pages found.");
@@ -930,6 +964,7 @@ class EnhancedComicGenerator:
930
  img.style.opacity = '1';
931
  });
932
  }
 
933
  function updateImageTransform(img) {
934
  const zoom = (img.dataset.zoom || 100) / 100;
935
  const x = img.dataset.translateX || 0;
@@ -937,6 +972,7 @@ class EnhancedComicGenerator:
937
  img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${zoom})`;
938
  img.classList.toggle('pannable', zoom > 1);
939
  }
 
940
  function handleZoom(event) {
941
  if (!currentlySelectedPanel) return;
942
  const img = currentlySelectedPanel.querySelector('img');
@@ -953,6 +989,7 @@ class EnhancedComicGenerator:
953
  document.getElementById('zoom-slider').value = 100;
954
  updateImageTransform(img);
955
  }
 
956
  function startPan(event) {
957
  if (event.button !== 0) return;
958
  const img = event.target;
@@ -965,6 +1002,7 @@ class EnhancedComicGenerator:
965
  panStartTranslateX = parseFloat(img.dataset.translateX || 0);
966
  panStartTranslateY = parseFloat(img.dataset.translateY || 0);
967
  }
 
968
  function panImage(event) {
969
  if (!isPanning || !currentlySelectedPanel) return;
970
  const img = currentlySelectedPanel.querySelector('img');
@@ -972,11 +1010,13 @@ class EnhancedComicGenerator:
972
  img.dataset.translateY = panStartTranslateY + (event.clientY - panStartY);
973
  updateImageTransform(img);
974
  }
 
975
  function stopPan() {
976
  if (!isPanning) return;
977
  isPanning = false;
978
  currentlySelectedPanel?.querySelector('img')?.classList.remove('panning');
979
  }
 
980
  function addBubbleToPanel() {
981
  if (!currentlySelectedPanel) { alert("Please select a panel first."); return; }
982
  const newBubble = createBubbleElement({
@@ -1029,6 +1069,7 @@ class EnhancedComicGenerator:
1029
  </script>
1030
  </body>
1031
  </html>'''
 
1032
  with open(os.path.join(self.output_dir, 'page.html'), 'w', encoding='utf-8') as f:
1033
  f.write(template_html)
1034
  print("📄 Template files copied successfully!")
 
308
  return {"success": False, "message": f"No frame at {target_time:.2f}s."}
309
  new_path = os.path.join(self.frames_dir, frame_filename)
310
  cv2.imwrite(new_path, frame)
311
+
312
+ # <<< MODIFICATION START: Apply enhancements to the new frame for consistency >>>
313
+ print(f"🎨 Applying enhancements to the new frame: {frame_filename}")
314
+ self._enhance_all_images(single_image_path=new_path)
315
+ self._enhance_quality_colors(single_image_path=new_path)
316
+ # <<< MODIFICATION END >>>
317
+
318
  if isinstance(frame_to_time[frame_filename], dict):
319
  frame_to_time[frame_filename]['time'] = target_time
320
  else:
 
345
  if not ret or frame is None: return {"success": False, "message": f"Could not retrieve frame at {timestamp_seconds:.2f}s."}
346
  new_path = os.path.join(self.frames_dir, frame_filename)
347
  cv2.imwrite(new_path, frame)
348
+
349
+ # <<< MODIFICATION START: Apply enhancements to the new frame for consistency >>>
350
+ print(f"🎨 Applying enhancements to the new frame from timestamp: {frame_filename}")
351
+ self._enhance_all_images(single_image_path=new_path)
352
+ self._enhance_quality_colors(single_image_path=new_path)
353
+ # <<< MODIFICATION END >>>
354
+
355
  with open(metadata_path, 'r') as f: frame_to_time = json.load(f)
356
  if frame_filename in frame_to_time:
357
  if isinstance(frame_to_time[frame_filename], dict):
 
502
 
503
  def _copy_template_files(self):
504
  try:
505
+ # <<< MODIFICATION START: Updated CSS rules for a longer bubble tail >>>
506
  template_html = '''<!DOCTYPE html>
507
  <html lang="en">
508
  <head>
 
534
  .speech-bubble.narration { background: #FAFAFA; border: 2px solid #BDBDBD; color: #424242; border-radius: 3px; }
535
  .speech-bubble.idea { background: linear-gradient(180deg,#FFFDD0 0%, #FFF8B5 100%); border: 2px solid #FFA500; color: #6a4b00; border-radius: 40% 60% 40% 60% / 60% 40% 60% 40%; }
536
  .speech-bubble.speech::after, .speech-bubble.idea::after { content: ''; position: absolute; width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent; }
537
+ /* <<< CSS MODIFICATION: Made bubble tail longer >>> */
538
+ .speech-bubble.speech::after { border-top: 20px solid #333; bottom: -19px; left: 20px; }
539
+ .speech-bubble.idea::after { border-top: 20px solid #FFA500; bottom: -19px; left: 20px; }
540
  .speech-bubble.thought::after { display: none; }
541
  .thought-dot { position: absolute; background-color: white; border: 2px solid #555; border-radius: 50%; z-index: -1; }
542
  .thought-dot-1 { width: 20px; height: 20px; bottom: -20px; left: 15px; }
 
544
  .speech-bubble.flipped.speech::after, .speech-bubble.flipped.idea::after { left: auto; right: 20px; }
545
  .speech-bubble.flipped.thought .thought-dot-1 { left: auto; right: 15px; }
546
  .speech-bubble.flipped.thought .thought-dot-2 { left: auto; right: 5px; }
547
+ /* <<< CSS MODIFICATION: Adjusted vertically flipped tail position >>> */
548
+ .speech-bubble.flipped-vertical.speech::after, .speech-bubble.flipped-vertical.idea::after { bottom: auto; top: -19px; transform: rotate(180deg); }
549
  .speech-bubble.flipped-vertical.thought .thought-dot-1 { bottom: auto; top: -20px; }
550
  .speech-bubble.flipped-vertical.thought .thought-dot-2 { bottom: auto; top: -32px; }
551
  .resize-handle { position: absolute; width: 10px; height: 10px; background: #2196F3; border: 1px solid white; border-radius: 50%; display: none; z-index: 11; }
 
693
  document.getElementById('bubble-fill-color').addEventListener('input', (e) => {
694
  if(currentlySelectedBubble) currentlySelectedBubble.style.backgroundColor = e.target.value;
695
  });
696
+
697
  document.addEventListener('mousemove', e => { if (isPanning) panImage(e); if (draggedBubble) drag(e); if(isResizing) resizeBubble(e); });
698
  document.addEventListener('mouseup', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
699
  document.addEventListener('mouseleave', e => { if (isPanning) stopPan(e); if (draggedBubble) stopDrag(e); if(isResizing) stopResize(e);});
 
710
  handle.addEventListener('mousedown', (e) => startResize(e, dir));
711
  });
712
  }
713
+
714
  function createBubbleElement(data) {
715
  const bubbleDiv = document.createElement('div');
716
  bubbleDiv.dataset.id = data.id;
 
734
  if (b.length == 1) b = "0" + b;
735
  return "#" + r + g + b;
736
  }
737
+
738
  function applyBubbleType(bubble, type) {
739
  bubble.querySelectorAll('.thought-dot').forEach(el => el.remove());
740
  let classesToKeep = 'speech-bubble';
 
779
  currentlySelectedPanel = panel;
780
  selectBubble(null);
781
  }
782
+
783
  function selectBubble(bubble) {
784
  if (currentlySelectedBubble) currentlySelectedBubble.classList.remove('selected');
785
  currentlySelectedBubble = bubble;
786
  const bubbleControls = ['bubble-text-color', 'bubble-fill-color', 'bubble-type-select', 'font-select'];
787
+
788
  if (currentlySelectedBubble) {
789
  currentlySelectedBubble.classList.add('selected');
790
  if (currentlySelectedPanel) currentlySelectedPanel.classList.remove('selected');
 
795
  document.getElementById('bubble-fill-color').value = rgbToHex(styles.backgroundColor);
796
  document.getElementById('bubble-type-select').value = currentlySelectedBubble.dataset.type || 'speech';
797
  document.getElementById('font-select').value = styles.fontFamily.split(',')[0].replace(/"/g, "").replace(/'/g, "").trim();
798
+
799
  document.getElementById('zoom-slider').disabled = true;
800
  bubbleControls.forEach(id => document.getElementById(id).disabled = false);
801
  } else {
 
811
  const textarea = document.createElement('textarea');
812
  const originalHeight = bubble.offsetHeight;
813
  bubble.style.height = `${originalHeight}px`;
814
+
815
  textarea.value = textSpan.textContent;
816
  bubble.appendChild(textarea);
817
  textSpan.style.display = 'none';
 
825
  textarea.addEventListener('blur', finishEditing, { once: true });
826
  textarea.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); textarea.blur(); }});
827
  }
828
+
829
  function startDrag(e) {
830
  const bubble = e.target.closest('.speech-bubble');
831
  if (!bubble || currentlyEditing) return;
 
834
  const rect = bubble.getBoundingClientRect();
835
  offset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
836
  }
837
+
838
  function drag(e) {
839
  if (!draggedBubble) return;
840
  const parentRect = draggedBubble.parentElement.getBoundingClientRect();
841
  draggedBubble.style.left = `${e.clientX - parentRect.left - offset.x}px`;
842
  draggedBubble.style.top = `${e.clientY - parentRect.top - offset.y}px`;
843
  }
844
+
845
  function stopDrag() { draggedBubble = null; }
846
+
847
  function deleteBubble() {
848
  if (!currentlySelectedBubble) {
849
  alert("Please select a bubble to delete.");
 
855
  selectBubble(null);
856
  }
857
  }
858
+
859
  function startResize(e, dir) {
860
  e.preventDefault();
861
  e.stopPropagation();
 
870
  originalMouseX = e.clientX;
871
  originalMouseY = e.clientY;
872
  }
873
+
874
  function resizeBubble(e) {
875
  if (!isResizing || !currentlySelectedBubble) return;
876
  const dx = e.clientX - originalMouseX;
877
  const dy = e.clientY - originalMouseY;
878
  const bubble = currentlySelectedBubble;
879
+
880
  if (resizeHandle.includes('e')) bubble.style.width = `${originalWidth + dx}px`;
881
  if (resizeHandle.includes('w')) {
882
  bubble.style.width = `${originalWidth - dx}px`;
 
888
  bubble.style.top = `${originalY + dy}px`;
889
  }
890
  }
891
+
892
  function stopResize() { isResizing = false; }
893
+
894
  function clearSavedState() {
895
  if (confirm("Reset all edits?")) {
896
  localStorage.removeItem('comicEditorState');
897
  window.location.reload();
898
  }
899
  }
900
+
901
  async function exportPagesToPNG() {
902
  const pages = document.querySelectorAll('.comic-page');
903
  if (pages.length === 0) return alert("No pages found.");
 
964
  img.style.opacity = '1';
965
  });
966
  }
967
+
968
  function updateImageTransform(img) {
969
  const zoom = (img.dataset.zoom || 100) / 100;
970
  const x = img.dataset.translateX || 0;
 
972
  img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${zoom})`;
973
  img.classList.toggle('pannable', zoom > 1);
974
  }
975
+
976
  function handleZoom(event) {
977
  if (!currentlySelectedPanel) return;
978
  const img = currentlySelectedPanel.querySelector('img');
 
989
  document.getElementById('zoom-slider').value = 100;
990
  updateImageTransform(img);
991
  }
992
+
993
  function startPan(event) {
994
  if (event.button !== 0) return;
995
  const img = event.target;
 
1002
  panStartTranslateX = parseFloat(img.dataset.translateX || 0);
1003
  panStartTranslateY = parseFloat(img.dataset.translateY || 0);
1004
  }
1005
+
1006
  function panImage(event) {
1007
  if (!isPanning || !currentlySelectedPanel) return;
1008
  const img = currentlySelectedPanel.querySelector('img');
 
1010
  img.dataset.translateY = panStartTranslateY + (event.clientY - panStartY);
1011
  updateImageTransform(img);
1012
  }
1013
+
1014
  function stopPan() {
1015
  if (!isPanning) return;
1016
  isPanning = false;
1017
  currentlySelectedPanel?.querySelector('img')?.classList.remove('panning');
1018
  }
1019
+
1020
  function addBubbleToPanel() {
1021
  if (!currentlySelectedPanel) { alert("Please select a panel first."); return; }
1022
  const newBubble = createBubbleElement({
 
1069
  </script>
1070
  </body>
1071
  </html>'''
1072
+ # <<< MODIFICATION END >>>
1073
  with open(os.path.join(self.output_dir, 'page.html'), 'w', encoding='utf-8') as f:
1074
  f.write(template_html)
1075
  print("📄 Template files copied successfully!")