""" Enhanced Comic Generation Application High-quality comic generation using AI-enhanced processing """ import os import webbrowser import time import threading from flask import Flask, render_template, request, jsonify, send_from_directory, send_file from pathlib import Path import cv2 import numpy as np from PIL import Image import srt import json import shutil from typing import List import traceback # Import enhanced modules from backend.ai_enhanced_core import ( image_processor, comic_styler, face_detector, layout_optimizer ) from backend.ai_bubble_placement import ai_bubble_placer from backend.subtitles.subs_real import get_real_subtitles from backend.keyframes.keyframes_simple import generate_keyframes_simple from backend.keyframes.keyframes import black_bar_crop from backend.class_def import bubble, panel, Page # Import smart comic generation try: from backend.emotion_aware_comic import EmotionAwareComicGenerator from backend.story_analyzer import SmartComicGenerator SMART_COMIC_AVAILABLE = True print("✅ Smart comic generation available!") except Exception as e: SMART_COMIC_AVAILABLE = False print(f"⚠️ Smart comic generation not available: {e}") # Import panel extractor try: from backend.panel_extractor import PanelExtractor PANEL_EXTRACTOR_AVAILABLE = True print("✅ Panel extractor available!") except Exception as e: PANEL_EXTRACTOR_AVAILABLE = False print(f"⚠️ Panel extractor not available: {e}") # Import smart story extractor try: from backend.smart_story_extractor import SmartStoryExtractor STORY_EXTRACTOR_AVAILABLE = True print("✅ Smart story extractor available!") except Exception as e: STORY_EXTRACTOR_AVAILABLE = False print(f"⚠️ Smart story extractor not available: {e}") app = Flask(__name__) # Import editor routes try: from comic_editor_server import add_editor_routes add_editor_routes(app) print("✅ Comic editor integrated!") except Exception as e: print(f"⚠️ Could not load comic editor: {e}") # Ensure directories exist os.makedirs('video', exist_ok=True) os.makedirs('frames/final', exist_ok=True) os.makedirs('output', exist_ok=True) class EnhancedComicGenerator: """High-quality comic generation with AI enhancement""" def __init__(self): self.video_path = 'video/uploaded.mp4' self.frames_dir = 'frames/final' self.output_dir = 'output' self.apply_comic_style = False def cleanup_generated(self): """Deletes all old files to ensure a fresh start.""" print("🧹 Performing full cleanup of previous run...") if os.path.isdir(self.frames_dir): shutil.rmtree(self.frames_dir) if os.path.isdir(self.output_dir): shutil.rmtree(self.output_dir) if os.path.isdir('temp'): shutil.rmtree('temp') if os.path.exists('test1.srt'): os.remove('test1.srt') os.makedirs(self.frames_dir, exist_ok=True) os.makedirs(self.output_dir, exist_ok=True) print("✅ Cleanup complete.") def generate_comic(self, smart_mode=False, emotion_match=False): """Main comic generation pipeline""" start_time = time.time() self.cleanup_generated() print("🎬 Starting Enhanced Comic Generation...") try: get_real_subtitles(self.video_path) all_subs = [] filtered_subs = None if os.path.exists('test1.srt'): with open('test1.srt', 'r', encoding='utf-8') as f: all_subs = list(srt.parse(f.read())) try: from backend.full_story_extractor import FullStoryExtractor extractor = FullStoryExtractor() sub_list = [{'index': s.index, 'text': s.content, 'start': s.start.total_seconds(), 'end': s.end.total_seconds()} for s in all_subs] os.makedirs('temp', exist_ok=True) with open('temp/all_subs.json', 'w') as f: json.dump(sub_list, f) story_subs = extractor.extract_full_story('temp/all_subs.json') story_indices = {s.get('index') for s in story_subs} filtered_subs = [sub for sub in all_subs if sub.index in story_indices] print(f"📚 Full story: {len(filtered_subs)} key moments from {len(all_subs)} total") except Exception as e: print(f"⚠️ Full story extraction failed, using all subtitles: {e}") filtered_subs = all_subs subs_for_keyframes = filtered_subs if filtered_subs is not None else all_subs from backend.keyframes.keyframes_engaging import generate_keyframes_engaging generate_keyframes_engaging(self.video_path, subs_for_keyframes, max_frames=48) black_x, black_y, _, _ = black_bar_crop() self._enhance_all_images() self._enhance_quality_colors() bubbles = self._create_ai_bubbles(black_x, black_y, subs_for_keyframes) pages = self._generate_pages(bubbles) self._save_results(pages) execution_time = (time.time() - start_time) / 60 print(f"✅ Comic generation completed in {execution_time:.2f} minutes") return True except Exception as e: print(f"❌ Comic generation failed: {e}") traceback.print_exc() return False def _enhance_all_images(self, single_image_path=None): """Enhances all images in the frames dir, or a single image if specified.""" target_dir = self.frames_dir if single_image_path: target_dir = os.path.dirname(single_image_path) if not os.path.exists(target_dir): return try: from backend.simple_color_enhancer import SimpleColorEnhancer enhancer = SimpleColorEnhancer() if single_image_path: enhancer.enhance_image(single_image_path, single_image_path) else: enhancer.enhance_batch(target_dir) except Exception as e: print(f"❌ Simple enhancement failed: {e}") def _enhance_quality_colors(self, single_image_path=None): """Enhances colors for all images, or a single image if specified.""" target_dir = self.frames_dir if single_image_path: target_dir = os.path.dirname(single_image_path) try: from backend.quality_color_enhancer import QualityColorEnhancer enhancer = QualityColorEnhancer() if single_image_path: enhancer.enhance_image(single_image_path, single_image_path) else: enhancer.batch_enhance(target_dir) except Exception as e: print(f"⚠️ Quality enhancement failed: {e}") def _create_ai_bubbles(self, black_x, black_y, subs_for_bubbles): bubbles = [] frame_files = sorted([f for f in os.listdir(self.frames_dir) if f.endswith('.png')]) subs_to_use = subs_for_bubbles[:len(frame_files)] for i, frame_file in enumerate(frame_files): dialogue = subs_to_use[i].content if i < len(subs_to_use) else "" frame_path = os.path.join(self.frames_dir, frame_file) try: lip_x, lip_y = -1, -1 faces = face_detector.detect_faces(frame_path) if faces: lip_x, lip_y = face_detector.get_lip_position(frame_path, faces[0]) bubble_x, bubble_y = ai_bubble_placer.place_bubble_ai(frame_path, (lip_x, lip_y)) bubbles.append(bubble( bubble_offset_x=bubble_x, bubble_offset_y=bubble_y, lip_x=lip_x, lip_y=lip_y, dialog=dialogue, emotion='normal' )) except Exception: bubbles.append(bubble( bubble_offset_x=50, bubble_offset_y=20, lip_x=-1, lip_y=-1, dialog=dialogue, emotion='normal' )) return bubbles def _generate_pages(self, bubbles): """Generates pages using an external function or a fallback.""" try: from backend.fixed_12_pages_800x1080 import generate_12_pages_800x1080 frame_files = sorted([f for f in os.listdir(self.frames_dir) if f.endswith('.png')]) return generate_12_pages_800x1080(frame_files, bubbles) except ImportError: pages = [] frame_files = sorted([f for f in os.listdir(self.frames_dir) if f.endswith('.png')]) frames_per_page = 4 num_pages = (len(frame_files) + frames_per_page - 1) // frames_per_page frame_counter = 0 for i in range(num_pages): page_panels, page_bubbles = [], [] for _ in range(frames_per_page): if frame_counter < len(frame_files): page_panels.append(panel( image=frame_files[frame_counter], row_span=6, col_span=6 )) page_bubbles.append(bubbles[frame_counter] if frame_counter < len(bubbles) else bubble(dialog="")) frame_counter += 1 if page_panels: pages.append(Page(panels=page_panels, bubbles=page_bubbles)) return pages def _save_results(self, pages): """Safely saves results to a JSON file.""" try: os.makedirs(self.output_dir, exist_ok=True) pages_data = [] for page in pages: page_dict = { 'panels': [p if isinstance(p, dict) else p.__dict__ for p in page.panels], 'bubbles': [b if isinstance(b, dict) else b.__dict__ for b in page.bubbles] } pages_data.append(page_dict) with open(os.path.join(self.output_dir, 'pages.json'), 'w', encoding='utf-8') as f: json.dump(pages_data, f, indent=2) self._copy_template_files() print("✅ Results saved successfully!") except Exception as e: print(f"Save results failed: {e}") traceback.print_exc() def _copy_template_files(self): """This function now includes the working 'Replace Image', 'Flip Bubble', and Panel Gaps.""" try: template_html = ''' Generated Comic - Interactive Editor

🎬 Generated Comic

Loading comic...

✏️ Interactive Editor

''' with open(os.path.join(self.output_dir, 'page.html'), 'w', encoding='utf-8') as f: f.write(template_html) print("📄 Template files copied successfully!") except Exception as e: print(f"Template copy failed: {e}") # (Flask routes start here) # ... # Global comic generator instance comic_generator = EnhancedComicGenerator() @app.route('/') def index(): return render_template('index.html') @app.route('/uploader', methods=['POST']) def upload_file(): try: if 'file' not in request.files or request.files['file'].filename == '': return "❌ No file selected" f = request.files['file'] if os.path.exists(comic_generator.video_path): os.remove(comic_generator.video_path) f.save(comic_generator.video_path) success = comic_generator.generate_comic() if success: webbrowser.open("http://localhost:5000/comic") return "🎉 Enhanced Comic Created Successfully!" else: return "❌ Comic generation failed" except Exception as e: return f"❌ Error: {str(e)}" @app.route('/handle_link', methods=['POST']) def handle_link(): try: link = request.form.get('link', '') if not link: return "❌ No link provided" import yt_dlp ydl_opts = {'outtmpl': comic_generator.video_path, 'format': 'best[height<=720]'} with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([link]) success = comic_generator.generate_comic() if success: webbrowser.open("http://localhost:5000/comic") return "🎉 Enhanced Comic Created Successfully!" else: return "❌ Comic generation failed" except Exception as e: return f"❌ Error: {str(e)}" # NEW: Server-side route to handle image replacement and enhancement @app.route('/replace_panel', methods=['POST']) def replace_panel(): try: if 'image' not in request.files: return jsonify({'success': False, 'error': 'No image file provided.'}) file = request.files['image'] if file.filename == '': return jsonify({'success': False, 'error': 'No image file selected.'}) # Create a unique filename to avoid browser caching issues timestamp = int(time.time() * 1000) filename = f"replaced_panel_{timestamp}.png" save_path = os.path.join(comic_generator.frames_dir, filename) file.save(save_path) # Now, run the same enhancement functions on this new image print(f"🖼️ Enhancing replaced panel image: {filename}") comic_generator._enhance_all_images(single_image_path=save_path) comic_generator._enhance_quality_colors(single_image_path=save_path) print(f"✅ Enhancement complete for {filename}") return jsonify({'success': True, 'new_filename': filename}) except Exception as e: traceback.print_exc() return jsonify({'success': False, 'error': str(e)}) @app.route('/comic') def view_comic(): return send_from_directory('output', 'page.html') @app.route('/output/') def output_file(filename): return send_from_directory('output', filename) @app.route('/frames/final/') def frame_file(filename): return send_from_directory('frames/final', fiif __name__ == '__main_