jhh6576 commited on
Commit
9e393d9
·
verified ·
1 Parent(s): a7ede2e

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +66 -41
app_enhanced.py CHANGED
@@ -14,9 +14,6 @@ from typing import List
14
  import traceback
15
 
16
  # --- ROBUST IMPORTS WITH FALLBACKS ---
17
- # This structure prevents the app from crashing if a module is missing.
18
- # It will log a warning and skip the feature instead.
19
-
20
  try:
21
  from backend.keyframes.keyframes import black_bar_crop
22
  print("✅ Black bar cropping module loaded.")
@@ -66,7 +63,6 @@ try:
66
  except Exception as e:
67
  print(f"⚠️ Could not load a core utility module: {e}")
68
 
69
- # Import smart comic generation
70
  try:
71
  from backend.emotion_aware_comic import EmotionAwareComicGenerator
72
  from backend.story_analyzer import SmartComicGenerator
@@ -76,7 +72,6 @@ except Exception as e:
76
  SMART_COMIC_AVAILABLE = False
77
  print(f"⚠️ Smart comic generation not available: {e}")
78
 
79
- # Import panel extractor
80
  try:
81
  from backend.panel_extractor import PanelExtractor
82
  PANEL_EXTRACTOR_AVAILABLE = True
@@ -85,7 +80,6 @@ except Exception as e:
85
  PANEL_EXTRACTOR_AVAILABLE = False
86
  print(f"⚠️ Panel extractor not available: {e}")
87
 
88
- # Import smart story extractor
89
  try:
90
  from backend.smart_story_extractor import SmartStoryExtractor
91
  STORY_EXTRACTOR_AVAILABLE = True
@@ -96,7 +90,6 @@ except Exception as e:
96
 
97
  app = Flask(__name__)
98
 
99
- # Import editor routes
100
  try:
101
  from comic_editor_server import add_editor_routes
102
  add_editor_routes(app)
@@ -104,7 +97,6 @@ try:
104
  except Exception as e:
105
  print(f"⚠️ Could not load comic editor: {e}")
106
 
107
- # Ensure directories exist
108
  os.makedirs('video', exist_ok=True)
109
  os.makedirs('frames/final', exist_ok=True)
110
  os.makedirs('output', exist_ok=True)
@@ -116,6 +108,7 @@ class EnhancedComicGenerator:
116
  self.frames_dir = 'frames/final'
117
  self.output_dir = 'output'
118
  self.apply_comic_style = False
 
119
 
120
  def cleanup_generated(self):
121
  """Deletes all old files to ensure a fresh start."""
@@ -155,12 +148,14 @@ class EnhancedComicGenerator:
155
  except:
156
  return 'open'
157
 
158
- def regenerate_frame(self, frame_filename):
159
  """
160
- Regenerate frame by moving +0.1s forward in the original video.
161
- Updates metadata so repeated clicks keep advancing.
162
  """
163
  try:
 
 
 
164
  metadata_path = 'frames/frame_metadata.json'
165
  if not os.path.exists(metadata_path):
166
  return {"success": False, "message": "Frame metadata missing."}
@@ -170,13 +165,25 @@ class EnhancedComicGenerator:
170
 
171
  if frame_filename not in frame_to_time:
172
  return {"success": False, "message": "Panel not linked to original video."}
173
-
174
  if isinstance(frame_to_time[frame_filename], dict):
175
  current_time = frame_to_time[frame_filename]['time']
176
  else:
177
  current_time = frame_to_time[frame_filename]
178
-
179
- target_time = current_time + 0.1
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  cap = cv2.VideoCapture(self.video_path)
182
  if not cap.isOpened():
@@ -187,24 +194,27 @@ class EnhancedComicGenerator:
187
  cap.release()
188
 
189
  if not ret or frame is None:
190
- return {"success": False, "message": "No next frame available at +0.1s."}
191
 
 
192
  new_path = os.path.join(self.frames_dir, frame_filename)
193
  cv2.imwrite(new_path, frame)
194
 
 
195
  if isinstance(frame_to_time[frame_filename], dict):
196
  frame_to_time[frame_filename]['time'] = target_time
197
  else:
198
  frame_to_time[frame_filename] = target_time
199
-
200
  with open(metadata_path, 'w') as f:
201
  json.dump(frame_to_time, f, indent=2)
202
 
203
- print(f" Regenerated {frame_filename} to time {target_time:.2f}s without enhancement.")
 
204
 
205
  return {
206
  "success": True,
207
- "message": f"Advanced to {target_time:.2f}s (+0.1s)",
208
  "new_filename": frame_filename
209
  }
210
 
@@ -222,11 +232,8 @@ class EnhancedComicGenerator:
222
  print("❌ Cannot open video for keyframe extraction")
223
  return False
224
 
225
- fps = cap.get(cv2.CAP_PROP_FPS)
226
- if fps == 0:
227
- print("⚠️ Video FPS is 0, defaulting to 25. Keyframe extraction might be inaccurate.")
228
- fps = 25
229
-
230
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
231
  duration = total_frames / fps
232
 
@@ -300,6 +307,17 @@ class EnhancedComicGenerator:
300
  self.cleanup_generated()
301
  print("🎬 Starting Enhanced Comic Generation...")
302
  try:
 
 
 
 
 
 
 
 
 
 
 
303
  print("📝 Generating subtitles...")
304
  get_real_subtitles(self.video_path)
305
  all_subs = []
@@ -518,6 +536,7 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
518
  .edit-controls .reset-button { background-color: #e74c3c; }
519
  .edit-controls .action-button { background-color: #4CAF50; }
520
  .edit-controls .secondary-button { background-color: #f39c12; }
 
521
  </style>
522
  </head>
523
  <body>
@@ -537,11 +556,17 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
537
  </div>
538
  <div class="control-group">
539
  <button onclick="replacePanelImage()" class="action-button">🖼️ Replace Panel Image</button>
540
- <button onclick="regenerateFrame()" class="action-button">🔄 Regenerate Frame</button>
541
- <button onclick="exportPagesToPNG()" class="action-button" style="background-color: #2196F3;">🖨️ Export Pages to PNG</button>
542
  </div>
543
  <div class="control-group">
544
- <button onclick="clearSavedState()" class="reset-button">🔄 Clear Edits & Reset</button>
 
 
 
 
 
 
 
 
545
  </div>
546
  </div>
547
  <script>
@@ -627,7 +652,7 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
627
  bubbleDiv.appendChild(textSpan);
628
  bubbleDiv.style.left = data.left;
629
  bubbleDiv.style.top = data.top;
630
- applyBubbleType(bubbleDiv, 'speech'); // Default to speech
631
  return bubbleDiv;
632
  }
633
 
@@ -658,13 +683,13 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
658
  if (!currentlySelectedBubble) return alert("Please select a bubble to rotate.");
659
  const isFlippedH = currentlySelectedBubble.classList.contains('flipped');
660
  const isFlippedV = currentlySelectedBubble.classList.contains('flipped-vertical');
661
- if (!isFlippedH && !isFlippedV) { // State 0 -> 1
662
  currentlySelectedBubble.classList.add('flipped');
663
- } else if (isFlippedH && !isFlippedV) { // State 1 -> 2
664
  currentlySelectedBubble.classList.add('flipped-vertical');
665
- } else if (isFlippedH && isFlippedV) { // State 2 -> 3
666
  currentlySelectedBubble.classList.remove('flipped');
667
- } else { // State 3 -> 0
668
  currentlySelectedBubble.classList.remove('flipped-vertical');
669
  }
670
  }
@@ -787,9 +812,9 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
787
  uploader.click();
788
  }
789
 
790
- function regenerateFrame() {
791
  if (!currentlySelectedPanel) {
792
- alert("Please select a panel first.");
793
  return;
794
  }
795
  const img = currentlySelectedPanel.querySelector('img');
@@ -800,27 +825,24 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
800
  filename = filename.split('?')[0];
801
  }
802
 
803
- if (!confirm(`Regenerate frame "${filename}" with a better version?`)) {
804
- return;
805
- }
806
  img.style.opacity = '0.5';
807
  fetch('/regenerate_frame', {
808
  method: 'POST',
809
  headers: { 'Content-Type': 'application/json' },
810
- body: JSON.stringify({ filename: filename })
811
  })
812
  .then(response => response.json())
813
  .then(data => {
814
  if (data.success) {
815
  img.src = `/frames/final/${filename}?t=${new Date().getTime()}`;
816
- alert(data.message);
817
  } else {
818
  alert('Error: ' + data.message);
819
  }
820
  img.style.opacity = '1';
821
  })
822
  .catch(error => {
823
- alert('An error occurred during regeneration.');
824
  img.style.opacity = '1';
825
  });
826
  }
@@ -896,8 +918,11 @@ def regenerate_frame_route():
896
  try:
897
  data = request.get_json()
898
  filename = data.get('filename')
899
- if not filename: return jsonify({'success': False, 'message': 'No filename provided'})
900
- result = comic_generator.regenerate_frame(filename)
 
 
 
901
  return jsonify(result)
902
  except Exception as e:
903
  traceback.print_exc()
 
14
  import traceback
15
 
16
  # --- ROBUST IMPORTS WITH FALLBACKS ---
 
 
 
17
  try:
18
  from backend.keyframes.keyframes import black_bar_crop
19
  print("✅ Black bar cropping module loaded.")
 
63
  except Exception as e:
64
  print(f"⚠️ Could not load a core utility module: {e}")
65
 
 
66
  try:
67
  from backend.emotion_aware_comic import EmotionAwareComicGenerator
68
  from backend.story_analyzer import SmartComicGenerator
 
72
  SMART_COMIC_AVAILABLE = False
73
  print(f"⚠️ Smart comic generation not available: {e}")
74
 
 
75
  try:
76
  from backend.panel_extractor import PanelExtractor
77
  PANEL_EXTRACTOR_AVAILABLE = True
 
80
  PANEL_EXTRACTOR_AVAILABLE = False
81
  print(f"⚠️ Panel extractor not available: {e}")
82
 
 
83
  try:
84
  from backend.smart_story_extractor import SmartStoryExtractor
85
  STORY_EXTRACTOR_AVAILABLE = True
 
90
 
91
  app = Flask(__name__)
92
 
 
93
  try:
94
  from comic_editor_server import add_editor_routes
95
  add_editor_routes(app)
 
97
  except Exception as e:
98
  print(f"⚠️ Could not load comic editor: {e}")
99
 
 
100
  os.makedirs('video', exist_ok=True)
101
  os.makedirs('frames/final', exist_ok=True)
102
  os.makedirs('output', exist_ok=True)
 
108
  self.frames_dir = 'frames/final'
109
  self.output_dir = 'output'
110
  self.apply_comic_style = False
111
+ self.video_fps = None # Will store the video's FPS
112
 
113
  def cleanup_generated(self):
114
  """Deletes all old files to ensure a fresh start."""
 
148
  except:
149
  return 'open'
150
 
151
+ def regenerate_frame(self, frame_filename, direction):
152
  """
153
+ Regenerate a frame by moving one frame forward or backward in the video.
 
154
  """
155
  try:
156
+ if not self.video_fps:
157
+ return {"success": False, "message": "Video FPS not found. Please regenerate the comic first."}
158
+
159
  metadata_path = 'frames/frame_metadata.json'
160
  if not os.path.exists(metadata_path):
161
  return {"success": False, "message": "Frame metadata missing."}
 
165
 
166
  if frame_filename not in frame_to_time:
167
  return {"success": False, "message": "Panel not linked to original video."}
168
+
169
  if isinstance(frame_to_time[frame_filename], dict):
170
  current_time = frame_to_time[frame_filename]['time']
171
  else:
172
  current_time = frame_to_time[frame_filename]
173
+
174
+ # Calculate the duration of a single frame
175
+ frame_duration = 1.0 / self.video_fps
176
+
177
+ # Calculate the new target time based on direction
178
+ if direction == 'forward':
179
+ target_time = current_time + frame_duration
180
+ elif direction == 'backward':
181
+ target_time = current_time - frame_duration
182
+ else:
183
+ return {"success": False, "message": "Invalid direction specified."}
184
+
185
+ # Ensure the timestamp doesn't go below zero
186
+ target_time = max(0, target_time)
187
 
188
  cap = cv2.VideoCapture(self.video_path)
189
  if not cap.isOpened():
 
194
  cap.release()
195
 
196
  if not ret or frame is None:
197
+ return {"success": False, "message": f"No frame available at {target_time:.2f}s."}
198
 
199
+ # Overwrite the existing frame file
200
  new_path = os.path.join(self.frames_dir, frame_filename)
201
  cv2.imwrite(new_path, frame)
202
 
203
+ # Update metadata with the new exact time
204
  if isinstance(frame_to_time[frame_filename], dict):
205
  frame_to_time[frame_filename]['time'] = target_time
206
  else:
207
  frame_to_time[frame_filename] = target_time
208
+
209
  with open(metadata_path, 'w') as f:
210
  json.dump(frame_to_time, f, indent=2)
211
 
212
+ message = f"Adjusted {direction} to {target_time:.3f}s"
213
+ print(f"✅ {message}")
214
 
215
  return {
216
  "success": True,
217
+ "message": message,
218
  "new_filename": frame_filename
219
  }
220
 
 
232
  print("❌ Cannot open video for keyframe extraction")
233
  return False
234
 
235
+ # Use the stored FPS
236
+ fps = self.video_fps
 
 
 
237
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
238
  duration = total_frames / fps
239
 
 
307
  self.cleanup_generated()
308
  print("🎬 Starting Enhanced Comic Generation...")
309
  try:
310
+ cap = cv2.VideoCapture(self.video_path)
311
+ if not cap.isOpened():
312
+ print("❌ Cannot open video to get FPS.")
313
+ return False
314
+ self.video_fps = cap.get(cv2.CAP_PROP_FPS)
315
+ if self.video_fps == 0:
316
+ print("⚠️ Video FPS is 0, defaulting to 25.")
317
+ self.video_fps = 25
318
+ cap.release()
319
+ print(f"✅ Video FPS detected: {self.video_fps:.2f}")
320
+
321
  print("📝 Generating subtitles...")
322
  get_real_subtitles(self.video_path)
323
  all_subs = []
 
536
  .edit-controls .reset-button { background-color: #e74c3c; }
537
  .edit-controls .action-button { background-color: #4CAF50; }
538
  .edit-controls .secondary-button { background-color: #f39c12; }
539
+ .button-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
540
  </style>
541
  </head>
542
  <body>
 
556
  </div>
557
  <div class="control-group">
558
  <button onclick="replacePanelImage()" class="action-button">🖼️ Replace Panel Image</button>
 
 
559
  </div>
560
  <div class="control-group">
561
+ <label>Adjust Selected Panel Frame:</label>
562
+ <div class="button-grid">
563
+ <button onclick="adjustFrame('backward')" class="secondary-button">⬅️ Previous</button>
564
+ <button onclick="adjustFrame('forward')" class="action-button">Next ➡️</button>
565
+ </div>
566
+ </div>
567
+ <div class="control-group">
568
+ <button onclick="exportPagesToPNG()" class="action-button" style="background-color: #2196F3;">🖨️ Export Pages to PNG</button>
569
+ <button onclick="clearSavedState()" class="reset-button">🔄 Clear Edits & Reset</button>
570
  </div>
571
  </div>
572
  <script>
 
652
  bubbleDiv.appendChild(textSpan);
653
  bubbleDiv.style.left = data.left;
654
  bubbleDiv.style.top = data.top;
655
+ applyBubbleType(bubbleDiv, 'speech');
656
  return bubbleDiv;
657
  }
658
 
 
683
  if (!currentlySelectedBubble) return alert("Please select a bubble to rotate.");
684
  const isFlippedH = currentlySelectedBubble.classList.contains('flipped');
685
  const isFlippedV = currentlySelectedBubble.classList.contains('flipped-vertical');
686
+ if (!isFlippedH && !isFlippedV) {
687
  currentlySelectedBubble.classList.add('flipped');
688
+ } else if (isFlippedH && !isFlippedV) {
689
  currentlySelectedBubble.classList.add('flipped-vertical');
690
+ } else if (isFlippedH && isFlippedV) {
691
  currentlySelectedBubble.classList.remove('flipped');
692
+ } else {
693
  currentlySelectedBubble.classList.remove('flipped-vertical');
694
  }
695
  }
 
812
  uploader.click();
813
  }
814
 
815
+ function adjustFrame(direction) {
816
  if (!currentlySelectedPanel) {
817
+ alert("Please select a panel first to adjust its frame.");
818
  return;
819
  }
820
  const img = currentlySelectedPanel.querySelector('img');
 
825
  filename = filename.split('?')[0];
826
  }
827
 
 
 
 
828
  img.style.opacity = '0.5';
829
  fetch('/regenerate_frame', {
830
  method: 'POST',
831
  headers: { 'Content-Type': 'application/json' },
832
+ body: JSON.stringify({ filename: filename, direction: direction })
833
  })
834
  .then(response => response.json())
835
  .then(data => {
836
  if (data.success) {
837
  img.src = `/frames/final/${filename}?t=${new Date().getTime()}`;
838
+ console.log(data.message);
839
  } else {
840
  alert('Error: ' + data.message);
841
  }
842
  img.style.opacity = '1';
843
  })
844
  .catch(error => {
845
+ alert('An error occurred during frame adjustment.');
846
  img.style.opacity = '1';
847
  });
848
  }
 
918
  try:
919
  data = request.get_json()
920
  filename = data.get('filename')
921
+ direction = data.get('direction')
922
+ if not filename or not direction:
923
+ return jsonify({'success': False, 'message': 'Filename or direction missing.'})
924
+
925
+ result = comic_generator.regenerate_frame(filename, direction)
926
  return jsonify(result)
927
  except Exception as e:
928
  traceback.print_exc()