Update app_enhanced.py
Browse files- app_enhanced.py +54 -24
app_enhanced.py
CHANGED
|
@@ -5,9 +5,16 @@ import uuid
|
|
| 5 |
import shutil
|
| 6 |
import json
|
| 7 |
import traceback
|
|
|
|
| 8 |
from concurrent.futures import ThreadPoolExecutor
|
| 9 |
from flask import Flask, render_template, request, jsonify, send_from_directory, send_file
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# --- 1. CORE DEPENDENCY CHECKS ---
|
| 12 |
try:
|
| 13 |
import cv2
|
|
@@ -16,15 +23,12 @@ try:
|
|
| 16 |
import srt
|
| 17 |
except ImportError as e:
|
| 18 |
print(f"❌ CRITICAL ERROR: Missing python library. {e}")
|
| 19 |
-
# Define dummies to allow app to start (will fail gracefully later)
|
| 20 |
cv2 = None
|
| 21 |
np = None
|
| 22 |
Image = None
|
| 23 |
srt = None
|
| 24 |
|
| 25 |
# --- 2. BACKEND MODULE IMPORTS (WITH ROBUST FALLBACKS) ---
|
| 26 |
-
# This section ensures the app loads even if specific backend files are missing.
|
| 27 |
-
|
| 28 |
def dummy_black_bar_crop(): return 0, 0, None, None
|
| 29 |
|
| 30 |
try:
|
|
@@ -51,7 +55,6 @@ try:
|
|
| 51 |
from backend.class_def import bubble, panel, Page
|
| 52 |
print("✅ Core class definitions loaded.")
|
| 53 |
except Exception:
|
| 54 |
-
# Fallback definitions if backend/class_def.py is missing
|
| 55 |
def bubble(dialog="", bubble_offset_x=50, bubble_offset_y=20, lip_x=-1, lip_y=-1, emotion='normal'):
|
| 56 |
return {
|
| 57 |
'dialog': dialog, 'bubble_offset_x': bubble_offset_x, 'bubble_offset_y': bubble_offset_y,
|
|
@@ -65,9 +68,9 @@ try:
|
|
| 65 |
from backend.ai_enhanced_core import image_processor, comic_styler, face_detector, layout_optimizer
|
| 66 |
from backend.ai_bubble_placement import ai_bubble_placer
|
| 67 |
from backend.subtitles.subs_real import get_real_subtitles
|
|
|
|
| 68 |
print("✅ Core utility modules loaded.")
|
| 69 |
except Exception:
|
| 70 |
-
# Dummies for AI modules
|
| 71 |
def get_real_subtitles(v): pass
|
| 72 |
class DummyDetector:
|
| 73 |
def detect_faces(self, p): return []
|
|
@@ -82,7 +85,7 @@ except Exception:
|
|
| 82 |
app = Flask(__name__)
|
| 83 |
BASE_USER_DIR = "userdata"
|
| 84 |
|
| 85 |
-
# --- HTML INTERFACE
|
| 86 |
INDEX_HTML = '''
|
| 87 |
<!DOCTYPE html>
|
| 88 |
<html lang="en">
|
|
@@ -90,10 +93,7 @@ INDEX_HTML = '''
|
|
| 90 |
<meta charset="UTF-8">
|
| 91 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 92 |
<title>Movie to Comic Generator</title>
|
| 93 |
-
<!-- Export Library that supports CSS Masks/Gradients -->
|
| 94 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
|
| 95 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 96 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 97 |
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@700&family=Lato&display=swap" rel="stylesheet">
|
| 98 |
<style>
|
| 99 |
/* GLOBAL STYLES */
|
|
@@ -570,8 +570,9 @@ INDEX_HTML = '''
|
|
| 570 |
updateImageTransform(img);
|
| 571 |
}
|
| 572 |
|
|
|
|
| 573 |
function replacePanelImage() {
|
| 574 |
-
if(!currentlySelectedPanel) return alert("Select panel");
|
| 575 |
const img = currentlySelectedPanel.querySelector('img');
|
| 576 |
const inp = document.getElementById('image-uploader');
|
| 577 |
inp.onchange = async (e) => {
|
|
@@ -649,18 +650,23 @@ class EnhancedComicGenerator:
|
|
| 649 |
json.dump({'message': message, 'progress': progress}, f)
|
| 650 |
except: pass
|
| 651 |
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
os.
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 659 |
|
| 660 |
def generate_comic(self):
|
| 661 |
try:
|
| 662 |
if cv2 is None: raise Exception("OpenCV missing on server.")
|
| 663 |
-
self.cleanup_generated()
|
| 664 |
|
| 665 |
self.update_status("Processing Video...", 5)
|
| 666 |
cap = cv2.VideoCapture(self.video_path)
|
|
@@ -676,7 +682,7 @@ class EnhancedComicGenerator:
|
|
| 676 |
get_real_subtitles(self.video_path)
|
| 677 |
if os.path.exists('test1.srt'): shutil.move('test1.srt', user_srt)
|
| 678 |
except:
|
| 679 |
-
with open(user_srt, 'w') as f: f.write("1\n00:00:00,000 --> 00:00:05,000\
|
| 680 |
|
| 681 |
# 2. Keyframes
|
| 682 |
self.update_status("Generating Panels...", 40)
|
|
@@ -686,7 +692,7 @@ class EnhancedComicGenerator:
|
|
| 686 |
frame_files = []
|
| 687 |
bubbles = []
|
| 688 |
|
| 689 |
-
limit_subs = subs[:12] # Limit to 12 panels for demo
|
| 690 |
|
| 691 |
for i, sub in enumerate(limit_subs):
|
| 692 |
mid = (sub.start.total_seconds() + sub.end.total_seconds()) / 2
|
|
@@ -696,7 +702,6 @@ class EnhancedComicGenerator:
|
|
| 696 |
fname = f"frame_{i}.png"
|
| 697 |
cv2.imwrite(os.path.join(self.frames_dir, fname), frame)
|
| 698 |
frame_files.append(fname)
|
| 699 |
-
# Map file to time for regeneration
|
| 700 |
self.frame_metadata[fname] = mid
|
| 701 |
|
| 702 |
bubbles.append(bubble(
|
|
@@ -706,13 +711,13 @@ class EnhancedComicGenerator:
|
|
| 706 |
))
|
| 707 |
cap.release()
|
| 708 |
|
| 709 |
-
# Save metadata for features like "Next Frame"
|
| 710 |
with open(os.path.join(self.frames_dir, 'frame_metadata.json'), 'w') as f:
|
| 711 |
json.dump(self.frame_metadata, f)
|
| 712 |
|
| 713 |
# 3. Enhance
|
| 714 |
self.update_status("Enhancing...", 70)
|
| 715 |
-
|
|
|
|
| 716 |
|
| 717 |
# 4. Assemble
|
| 718 |
self.update_status("Finalizing...", 90)
|
|
@@ -758,6 +763,8 @@ class EnhancedComicGenerator:
|
|
| 758 |
cv2.imwrite(os.path.join(self.frames_dir, fname), frame)
|
| 759 |
meta[fname] = new_time
|
| 760 |
with open(meta_path,'w') as f: json.dump(meta, f)
|
|
|
|
|
|
|
| 761 |
return {"success":True}
|
| 762 |
return {"success":False, "message":"End of video"}
|
| 763 |
except Exception as e: return {"success":False, "message":str(e)}
|
|
@@ -770,15 +777,34 @@ class EnhancedComicGenerator:
|
|
| 770 |
cap.release()
|
| 771 |
if ret:
|
| 772 |
cv2.imwrite(os.path.join(self.frames_dir, fname), frame)
|
| 773 |
-
# Update meta
|
| 774 |
meta_path = os.path.join(self.frames_dir, 'frame_metadata.json')
|
| 775 |
if os.path.exists(meta_path):
|
| 776 |
with open(meta_path,'r') as f: meta = json.load(f)
|
| 777 |
meta[fname] = float(ts)
|
| 778 |
with open(meta_path,'w') as f: json.dump(meta, f)
|
|
|
|
|
|
|
| 779 |
return {"success":True}
|
| 780 |
return {"success":False, "message":"Invalid time"}
|
| 781 |
except Exception as e: return {"success":False, "message":str(e)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 782 |
|
| 783 |
# --- ROUTES ---
|
| 784 |
@app.route('/')
|
|
@@ -790,6 +816,10 @@ def upload():
|
|
| 790 |
if not sid: return "Missing SID", 400
|
| 791 |
f = request.files['file']
|
| 792 |
gen = EnhancedComicGenerator(sid)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 793 |
f.save(gen.video_path)
|
| 794 |
gen.update_status("Starting...", 5)
|
| 795 |
threading.Thread(target=gen.generate_comic).start()
|
|
|
|
| 5 |
import shutil
|
| 6 |
import json
|
| 7 |
import traceback
|
| 8 |
+
import logging
|
| 9 |
from concurrent.futures import ThreadPoolExecutor
|
| 10 |
from flask import Flask, render_template, request, jsonify, send_from_directory, send_file
|
| 11 |
|
| 12 |
+
# --- 0. SUPPRESS HUGGING FACE WARNINGS ---
|
| 13 |
+
import warnings
|
| 14 |
+
warnings.filterwarnings("ignore", category=UserWarning)
|
| 15 |
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
|
| 16 |
+
logging.getLogger("transformers").setLevel(logging.ERROR)
|
| 17 |
+
|
| 18 |
# --- 1. CORE DEPENDENCY CHECKS ---
|
| 19 |
try:
|
| 20 |
import cv2
|
|
|
|
| 23 |
import srt
|
| 24 |
except ImportError as e:
|
| 25 |
print(f"❌ CRITICAL ERROR: Missing python library. {e}")
|
|
|
|
| 26 |
cv2 = None
|
| 27 |
np = None
|
| 28 |
Image = None
|
| 29 |
srt = None
|
| 30 |
|
| 31 |
# --- 2. BACKEND MODULE IMPORTS (WITH ROBUST FALLBACKS) ---
|
|
|
|
|
|
|
| 32 |
def dummy_black_bar_crop(): return 0, 0, None, None
|
| 33 |
|
| 34 |
try:
|
|
|
|
| 55 |
from backend.class_def import bubble, panel, Page
|
| 56 |
print("✅ Core class definitions loaded.")
|
| 57 |
except Exception:
|
|
|
|
| 58 |
def bubble(dialog="", bubble_offset_x=50, bubble_offset_y=20, lip_x=-1, lip_y=-1, emotion='normal'):
|
| 59 |
return {
|
| 60 |
'dialog': dialog, 'bubble_offset_x': bubble_offset_x, 'bubble_offset_y': bubble_offset_y,
|
|
|
|
| 68 |
from backend.ai_enhanced_core import image_processor, comic_styler, face_detector, layout_optimizer
|
| 69 |
from backend.ai_bubble_placement import ai_bubble_placer
|
| 70 |
from backend.subtitles.subs_real import get_real_subtitles
|
| 71 |
+
from backend.keyframes.keyframes_simple import generate_keyframes_simple
|
| 72 |
print("✅ Core utility modules loaded.")
|
| 73 |
except Exception:
|
|
|
|
| 74 |
def get_real_subtitles(v): pass
|
| 75 |
class DummyDetector:
|
| 76 |
def detect_faces(self, p): return []
|
|
|
|
| 85 |
app = Flask(__name__)
|
| 86 |
BASE_USER_DIR = "userdata"
|
| 87 |
|
| 88 |
+
# --- HTML INTERFACE ---
|
| 89 |
INDEX_HTML = '''
|
| 90 |
<!DOCTYPE html>
|
| 91 |
<html lang="en">
|
|
|
|
| 93 |
<meta charset="UTF-8">
|
| 94 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 95 |
<title>Movie to Comic Generator</title>
|
|
|
|
| 96 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
|
|
|
|
|
|
|
| 97 |
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@700&family=Lato&display=swap" rel="stylesheet">
|
| 98 |
<style>
|
| 99 |
/* GLOBAL STYLES */
|
|
|
|
| 570 |
updateImageTransform(img);
|
| 571 |
}
|
| 572 |
|
| 573 |
+
// --- API Calls ---
|
| 574 |
function replacePanelImage() {
|
| 575 |
+
if (!currentlySelectedPanel) return alert("Select panel");
|
| 576 |
const img = currentlySelectedPanel.querySelector('img');
|
| 577 |
const inp = document.getElementById('image-uploader');
|
| 578 |
inp.onchange = async (e) => {
|
|
|
|
| 650 |
json.dump({'message': message, 'progress': progress}, f)
|
| 651 |
except: pass
|
| 652 |
|
| 653 |
+
# --- CLEANUP: Deletes OLD processing files on NEW upload ---
|
| 654 |
+
def cleanup_previous_run(self):
|
| 655 |
+
print(f"[{self.sid}] 🧹 Cleaning previous run...")
|
| 656 |
+
if os.path.exists(self.frames_dir):
|
| 657 |
+
for f in os.listdir(self.frames_dir):
|
| 658 |
+
try: os.remove(os.path.join(self.frames_dir, f))
|
| 659 |
+
except: pass
|
| 660 |
+
if os.path.exists(self.output_dir):
|
| 661 |
+
for f in os.listdir(self.output_dir):
|
| 662 |
+
try: os.remove(os.path.join(self.output_dir, f))
|
| 663 |
+
except: pass
|
| 664 |
+
srt_file = os.path.join(self.user_dir, 'subs.srt')
|
| 665 |
+
if os.path.exists(srt_file): os.remove(srt_file)
|
| 666 |
|
| 667 |
def generate_comic(self):
|
| 668 |
try:
|
| 669 |
if cv2 is None: raise Exception("OpenCV missing on server.")
|
|
|
|
| 670 |
|
| 671 |
self.update_status("Processing Video...", 5)
|
| 672 |
cap = cv2.VideoCapture(self.video_path)
|
|
|
|
| 682 |
get_real_subtitles(self.video_path)
|
| 683 |
if os.path.exists('test1.srt'): shutil.move('test1.srt', user_srt)
|
| 684 |
except:
|
| 685 |
+
with open(user_srt, 'w') as f: f.write("1\n00:00:00,000 --> 00:00:05,000\n...\n")
|
| 686 |
|
| 687 |
# 2. Keyframes
|
| 688 |
self.update_status("Generating Panels...", 40)
|
|
|
|
| 692 |
frame_files = []
|
| 693 |
bubbles = []
|
| 694 |
|
| 695 |
+
limit_subs = subs[:12] # Limit to 12 panels for demo
|
| 696 |
|
| 697 |
for i, sub in enumerate(limit_subs):
|
| 698 |
mid = (sub.start.total_seconds() + sub.end.total_seconds()) / 2
|
|
|
|
| 702 |
fname = f"frame_{i}.png"
|
| 703 |
cv2.imwrite(os.path.join(self.frames_dir, fname), frame)
|
| 704 |
frame_files.append(fname)
|
|
|
|
| 705 |
self.frame_metadata[fname] = mid
|
| 706 |
|
| 707 |
bubbles.append(bubble(
|
|
|
|
| 711 |
))
|
| 712 |
cap.release()
|
| 713 |
|
|
|
|
| 714 |
with open(os.path.join(self.frames_dir, 'frame_metadata.json'), 'w') as f:
|
| 715 |
json.dump(self.frame_metadata, f)
|
| 716 |
|
| 717 |
# 3. Enhance
|
| 718 |
self.update_status("Enhancing...", 70)
|
| 719 |
+
self._enhance_all_images()
|
| 720 |
+
self._enhance_quality_colors()
|
| 721 |
|
| 722 |
# 4. Assemble
|
| 723 |
self.update_status("Finalizing...", 90)
|
|
|
|
| 763 |
cv2.imwrite(os.path.join(self.frames_dir, fname), frame)
|
| 764 |
meta[fname] = new_time
|
| 765 |
with open(meta_path,'w') as f: json.dump(meta, f)
|
| 766 |
+
self._enhance_all_images(single_image_path=os.path.join(self.frames_dir, fname))
|
| 767 |
+
self._enhance_quality_colors(single_image_path=os.path.join(self.frames_dir, fname))
|
| 768 |
return {"success":True}
|
| 769 |
return {"success":False, "message":"End of video"}
|
| 770 |
except Exception as e: return {"success":False, "message":str(e)}
|
|
|
|
| 777 |
cap.release()
|
| 778 |
if ret:
|
| 779 |
cv2.imwrite(os.path.join(self.frames_dir, fname), frame)
|
|
|
|
| 780 |
meta_path = os.path.join(self.frames_dir, 'frame_metadata.json')
|
| 781 |
if os.path.exists(meta_path):
|
| 782 |
with open(meta_path,'r') as f: meta = json.load(f)
|
| 783 |
meta[fname] = float(ts)
|
| 784 |
with open(meta_path,'w') as f: json.dump(meta, f)
|
| 785 |
+
self._enhance_all_images(single_image_path=os.path.join(self.frames_dir, fname))
|
| 786 |
+
self._enhance_quality_colors(single_image_path=os.path.join(self.frames_dir, fname))
|
| 787 |
return {"success":True}
|
| 788 |
return {"success":False, "message":"Invalid time"}
|
| 789 |
except Exception as e: return {"success":False, "message":str(e)}
|
| 790 |
+
|
| 791 |
+
def _enhance_all_images(self, single_image_path=None):
|
| 792 |
+
try:
|
| 793 |
+
enhancer = SimpleColorEnhancer()
|
| 794 |
+
if single_image_path: enhancer.enhance_single(single_image_path)
|
| 795 |
+
else:
|
| 796 |
+
paths = [os.path.join(self.frames_dir, f) for f in os.listdir(self.frames_dir) if f.endswith('.png')]
|
| 797 |
+
with ThreadPoolExecutor() as ex: list(ex.map(enhancer.enhance_single, paths))
|
| 798 |
+
except: pass
|
| 799 |
+
|
| 800 |
+
def _enhance_quality_colors(self, single_image_path=None):
|
| 801 |
+
try:
|
| 802 |
+
enhancer = QualityColorEnhancer()
|
| 803 |
+
if single_image_path: enhancer.enhance_single(single_image_path)
|
| 804 |
+
else:
|
| 805 |
+
paths = [os.path.join(self.frames_dir, f) for f in os.listdir(self.frames_dir) if f.endswith('.png')]
|
| 806 |
+
with ThreadPoolExecutor() as ex: list(ex.map(enhancer.enhance_single, paths))
|
| 807 |
+
except: pass
|
| 808 |
|
| 809 |
# --- ROUTES ---
|
| 810 |
@app.route('/')
|
|
|
|
| 816 |
if not sid: return "Missing SID", 400
|
| 817 |
f = request.files['file']
|
| 818 |
gen = EnhancedComicGenerator(sid)
|
| 819 |
+
|
| 820 |
+
# CLEAN OLD FILES
|
| 821 |
+
gen.cleanup_previous_run()
|
| 822 |
+
|
| 823 |
f.save(gen.video_path)
|
| 824 |
gen.update_status("Starting...", 5)
|
| 825 |
threading.Thread(target=gen.generate_comic).start()
|