Update app_enhanced.py
Browse files- app_enhanced.py +37 -44
app_enhanced.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
import spaces # <--- CRITICAL: MUST BE THE FIRST IMPORT
|
| 2 |
import os
|
| 3 |
import time
|
| 4 |
import threading
|
|
@@ -12,16 +11,20 @@ import cv2
|
|
| 12 |
import math
|
| 13 |
import numpy as np
|
| 14 |
import srt
|
|
|
|
| 15 |
from flask import Flask, jsonify, request, send_from_directory, send_file
|
| 16 |
|
| 17 |
# ======================================================
|
| 18 |
-
# 🚀
|
| 19 |
# ======================================================
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
# ======================================================
|
| 27 |
# 🧱 DATA CLASSES
|
|
@@ -68,15 +71,16 @@ def generate_save_code(length=8):
|
|
| 68 |
return code
|
| 69 |
|
| 70 |
# ======================================================
|
| 71 |
-
# 🧠
|
|
|
|
|
|
|
|
|
|
| 72 |
# ======================================================
|
| 73 |
-
|
| 74 |
-
def
|
| 75 |
-
print(f"🚀
|
| 76 |
|
| 77 |
-
|
| 78 |
-
import srt
|
| 79 |
-
import numpy as np
|
| 80 |
from backend.keyframes.keyframes import black_bar_crop
|
| 81 |
from backend.simple_color_enhancer import SimpleColorEnhancer
|
| 82 |
from backend.quality_color_enhancer import QualityColorEnhancer
|
|
@@ -98,11 +102,15 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 98 |
get_real_subtitles(video_path)
|
| 99 |
if os.path.exists('test1.srt'):
|
| 100 |
shutil.move('test1.srt', user_srt)
|
| 101 |
-
except:
|
|
|
|
| 102 |
with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
|
| 103 |
|
| 104 |
with open(user_srt, 'r', encoding='utf-8') as f:
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
# 3. Smart Keyframe Selection
|
| 108 |
valid_subs = [s for s in all_subs if s.content.strip()]
|
|
@@ -137,7 +145,6 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 137 |
fname = f"frame_{count:04d}.png"
|
| 138 |
p = os.path.join(frames_dir, fname)
|
| 139 |
cv2.imwrite(p, frame)
|
| 140 |
-
os.sync()
|
| 141 |
frame_metadata[fname] = {'dialogue': moment['text'], 'time': mid}
|
| 142 |
frame_files_ordered.append(fname)
|
| 143 |
count += 1
|
|
@@ -149,6 +156,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 149 |
try: black_bar_crop()
|
| 150 |
except: pass
|
| 151 |
|
|
|
|
| 152 |
se = SimpleColorEnhancer()
|
| 153 |
qe = QualityColorEnhancer()
|
| 154 |
|
|
@@ -198,10 +206,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
|
|
| 198 |
|
| 199 |
return result
|
| 200 |
|
| 201 |
-
|
| 202 |
-
def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
|
| 203 |
-
import cv2
|
| 204 |
-
import json
|
| 205 |
from backend.simple_color_enhancer import SimpleColorEnhancer
|
| 206 |
|
| 207 |
if not os.path.exists(metadata_path): return {"success": False, "message": "No metadata"}
|
|
@@ -221,7 +226,6 @@ def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
|
|
| 221 |
if ret:
|
| 222 |
p = os.path.join(frames_dir, fname)
|
| 223 |
cv2.imwrite(p, frame)
|
| 224 |
-
os.sync()
|
| 225 |
try: SimpleColorEnhancer().enhance_single(p, p)
|
| 226 |
except: pass
|
| 227 |
|
|
@@ -231,10 +235,7 @@ def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
|
|
| 231 |
return {"success": True, "message": f"Adjusted to {new_t:.2f}s"}
|
| 232 |
return {"success": False, "message": "End of video"}
|
| 233 |
|
| 234 |
-
|
| 235 |
-
def get_frame_at_ts_gpu(video_path, frames_dir, metadata_path, fname, ts):
|
| 236 |
-
import cv2
|
| 237 |
-
import json
|
| 238 |
from backend.simple_color_enhancer import SimpleColorEnhancer
|
| 239 |
|
| 240 |
cap = cv2.VideoCapture(video_path)
|
|
@@ -245,7 +246,6 @@ def get_frame_at_ts_gpu(video_path, frames_dir, metadata_path, fname, ts):
|
|
| 245 |
if ret:
|
| 246 |
p = os.path.join(frames_dir, fname)
|
| 247 |
cv2.imwrite(p, frame)
|
| 248 |
-
os.sync()
|
| 249 |
try: SimpleColorEnhancer().enhance_single(p, p)
|
| 250 |
except: pass
|
| 251 |
|
|
@@ -280,8 +280,9 @@ class EnhancedComicGenerator:
|
|
| 280 |
|
| 281 |
def run(self, target_pages):
|
| 282 |
try:
|
| 283 |
-
self.write_status("Waiting for GPU...", 5)
|
| 284 |
-
|
|
|
|
| 285 |
with open(os.path.join(self.output_dir, 'pages.json'), 'w') as f:
|
| 286 |
json.dump(data, f, indent=2)
|
| 287 |
self.write_status("Complete!", 100)
|
|
@@ -485,7 +486,7 @@ INDEX_HTML = '''
|
|
| 485 |
</div>
|
| 486 |
</div>
|
| 487 |
|
| 488 |
-
<!--
|
| 489 |
<div class="control-group">
|
| 490 |
<label>📐 Page Layout (Template):</label>
|
| 491 |
<select id="template-select" onchange="changeTemplate(this.value)">
|
|
@@ -629,7 +630,6 @@ INDEX_HTML = '''
|
|
| 629 |
function getCurrentState() {
|
| 630 |
const pages = [];
|
| 631 |
document.querySelectorAll('.comic-page').forEach(p => {
|
| 632 |
-
// Get Grid Class
|
| 633 |
const grid = p.querySelector('.comic-grid');
|
| 634 |
let template = 't-classic';
|
| 635 |
if(grid) {
|
|
@@ -665,12 +665,9 @@ INDEX_HTML = '''
|
|
| 665 |
|
| 666 |
function saveDraft() { localStorage.setItem('comic_draft_'+sid, JSON.stringify({ pages: getCurrentState(), saveCode: currentSaveCode, savedAt: new Date().toISOString() })); }
|
| 667 |
|
| 668 |
-
// FUNCTION ADDED: Change Template
|
| 669 |
function changeTemplate(tpl) {
|
| 670 |
document.querySelectorAll('.comic-grid').forEach(g => {
|
| 671 |
-
// Remove existing template classes
|
| 672 |
g.classList.remove('t-classic', 't-feat-top', 't-side', 't-cinematic');
|
| 673 |
-
// Add new
|
| 674 |
g.classList.add(tpl);
|
| 675 |
});
|
| 676 |
saveDraft();
|
|
@@ -685,11 +682,9 @@ INDEX_HTML = '''
|
|
| 685 |
const div = document.createElement('div'); div.className = 'comic-page';
|
| 686 |
|
| 687 |
const grid = document.createElement('div');
|
| 688 |
-
// Apply saved template or default
|
| 689 |
const tpl = page.template || 't-classic';
|
| 690 |
grid.className = `comic-grid ${tpl}`;
|
| 691 |
|
| 692 |
-
// Set dropdown to current template (of first page)
|
| 693 |
if(pageIdx === 0) document.getElementById('template-select').value = tpl;
|
| 694 |
|
| 695 |
page.panels.forEach((pan) => {
|
|
@@ -900,7 +895,6 @@ def upload():
|
|
| 900 |
if 'file' not in request.files or not request.files['file'].filename:
|
| 901 |
return jsonify({'success': False, 'message': 'No file selected'}), 400
|
| 902 |
|
| 903 |
-
# GET PAGE COUNT FROM FORM
|
| 904 |
target_pages = request.form.get('target_pages', 4)
|
| 905 |
|
| 906 |
f = request.files['file']
|
|
@@ -909,7 +903,7 @@ def upload():
|
|
| 909 |
f.save(gen.video_path)
|
| 910 |
gen.write_status("Starting...", 5)
|
| 911 |
|
| 912 |
-
# Run in thread
|
| 913 |
threading.Thread(target=gen.run, args=(target_pages,)).start()
|
| 914 |
return jsonify({'success': True, 'message': 'Generation started.'})
|
| 915 |
|
|
@@ -935,16 +929,16 @@ def regen():
|
|
| 935 |
sid = request.args.get('sid')
|
| 936 |
d = request.get_json()
|
| 937 |
gen = EnhancedComicGenerator(sid)
|
| 938 |
-
#
|
| 939 |
-
return jsonify(
|
| 940 |
|
| 941 |
@app.route('/goto_timestamp', methods=['POST'])
|
| 942 |
def go_time():
|
| 943 |
sid = request.args.get('sid')
|
| 944 |
d = request.get_json()
|
| 945 |
gen = EnhancedComicGenerator(sid)
|
| 946 |
-
#
|
| 947 |
-
return jsonify(
|
| 948 |
|
| 949 |
@app.route('/replace_panel', methods=['POST'])
|
| 950 |
def rep_panel():
|
|
@@ -1010,6 +1004,5 @@ def load_comic(code):
|
|
| 1010 |
return jsonify({'success': False, 'message': str(e)})
|
| 1011 |
|
| 1012 |
if __name__ == '__main__':
|
| 1013 |
-
|
| 1014 |
-
except: pass
|
| 1015 |
app.run(host='0.0.0.0', port=7860)
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import time
|
| 3 |
import threading
|
|
|
|
| 11 |
import math
|
| 12 |
import numpy as np
|
| 13 |
import srt
|
| 14 |
+
import torch
|
| 15 |
from flask import Flask, jsonify, request, send_from_directory, send_file
|
| 16 |
|
| 17 |
# ======================================================
|
| 18 |
+
# 🚀 CUDA CONFIGURATION
|
| 19 |
# ======================================================
|
| 20 |
+
def check_cuda():
|
| 21 |
+
"""Checks for CUDA availability without ZeroGPU decorators"""
|
| 22 |
+
if torch.cuda.is_available():
|
| 23 |
+
print(f"✅ CUDA Engine Active: {torch.cuda.get_device_name(0)}")
|
| 24 |
+
return True
|
| 25 |
+
else:
|
| 26 |
+
print("⚠️ CUDA Not Detected. Running on CPU (Expect slower generation)")
|
| 27 |
+
return False
|
| 28 |
|
| 29 |
# ======================================================
|
| 30 |
# 🧱 DATA CLASSES
|
|
|
|
| 71 |
return code
|
| 72 |
|
| 73 |
# ======================================================
|
| 74 |
+
# 🧠 CORE PROCESSING FUNCTIONS (Standard CUDA)
|
| 75 |
+
# ======================================================
|
| 76 |
+
# Removed @spaces.GPU decorators. These functions now run
|
| 77 |
+
# directly in the thread/process invoking them.
|
| 78 |
# ======================================================
|
| 79 |
+
|
| 80 |
+
def generate_comic_core(video_path, user_dir, frames_dir, metadata_path, target_pages):
|
| 81 |
+
print(f"🚀 Processing Task Started: {video_path} | Pages: {target_pages}")
|
| 82 |
|
| 83 |
+
# Import backend logic here to ensure they load into the correct context
|
|
|
|
|
|
|
| 84 |
from backend.keyframes.keyframes import black_bar_crop
|
| 85 |
from backend.simple_color_enhancer import SimpleColorEnhancer
|
| 86 |
from backend.quality_color_enhancer import QualityColorEnhancer
|
|
|
|
| 102 |
get_real_subtitles(video_path)
|
| 103 |
if os.path.exists('test1.srt'):
|
| 104 |
shutil.move('test1.srt', user_srt)
|
| 105 |
+
except Exception as e:
|
| 106 |
+
print(f"Subtitle error (using placeholder): {e}")
|
| 107 |
with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
|
| 108 |
|
| 109 |
with open(user_srt, 'r', encoding='utf-8') as f:
|
| 110 |
+
try:
|
| 111 |
+
all_subs = list(srt.parse(f.read()))
|
| 112 |
+
except:
|
| 113 |
+
all_subs = []
|
| 114 |
|
| 115 |
# 3. Smart Keyframe Selection
|
| 116 |
valid_subs = [s for s in all_subs if s.content.strip()]
|
|
|
|
| 145 |
fname = f"frame_{count:04d}.png"
|
| 146 |
p = os.path.join(frames_dir, fname)
|
| 147 |
cv2.imwrite(p, frame)
|
|
|
|
| 148 |
frame_metadata[fname] = {'dialogue': moment['text'], 'time': mid}
|
| 149 |
frame_files_ordered.append(fname)
|
| 150 |
count += 1
|
|
|
|
| 156 |
try: black_bar_crop()
|
| 157 |
except: pass
|
| 158 |
|
| 159 |
+
# Initialize enhancers (they usually load models to CUDA if available)
|
| 160 |
se = SimpleColorEnhancer()
|
| 161 |
qe = QualityColorEnhancer()
|
| 162 |
|
|
|
|
| 206 |
|
| 207 |
return result
|
| 208 |
|
| 209 |
+
def regen_frame_core(video_path, frames_dir, metadata_path, fname, direction):
|
|
|
|
|
|
|
|
|
|
| 210 |
from backend.simple_color_enhancer import SimpleColorEnhancer
|
| 211 |
|
| 212 |
if not os.path.exists(metadata_path): return {"success": False, "message": "No metadata"}
|
|
|
|
| 226 |
if ret:
|
| 227 |
p = os.path.join(frames_dir, fname)
|
| 228 |
cv2.imwrite(p, frame)
|
|
|
|
| 229 |
try: SimpleColorEnhancer().enhance_single(p, p)
|
| 230 |
except: pass
|
| 231 |
|
|
|
|
| 235 |
return {"success": True, "message": f"Adjusted to {new_t:.2f}s"}
|
| 236 |
return {"success": False, "message": "End of video"}
|
| 237 |
|
| 238 |
+
def get_frame_at_ts_core(video_path, frames_dir, metadata_path, fname, ts):
|
|
|
|
|
|
|
|
|
|
| 239 |
from backend.simple_color_enhancer import SimpleColorEnhancer
|
| 240 |
|
| 241 |
cap = cv2.VideoCapture(video_path)
|
|
|
|
| 246 |
if ret:
|
| 247 |
p = os.path.join(frames_dir, fname)
|
| 248 |
cv2.imwrite(p, frame)
|
|
|
|
| 249 |
try: SimpleColorEnhancer().enhance_single(p, p)
|
| 250 |
except: pass
|
| 251 |
|
|
|
|
| 280 |
|
| 281 |
def run(self, target_pages):
|
| 282 |
try:
|
| 283 |
+
self.write_status("Waiting for GPU/Processor...", 5)
|
| 284 |
+
# Directly call the core function (standard CUDA)
|
| 285 |
+
data = generate_comic_core(self.video_path, self.user_dir, self.frames_dir, self.metadata_path, int(target_pages))
|
| 286 |
with open(os.path.join(self.output_dir, 'pages.json'), 'w') as f:
|
| 287 |
json.dump(data, f, indent=2)
|
| 288 |
self.write_status("Complete!", 100)
|
|
|
|
| 486 |
</div>
|
| 487 |
</div>
|
| 488 |
|
| 489 |
+
<!-- TEMPLATE OPTIONS -->
|
| 490 |
<div class="control-group">
|
| 491 |
<label>📐 Page Layout (Template):</label>
|
| 492 |
<select id="template-select" onchange="changeTemplate(this.value)">
|
|
|
|
| 630 |
function getCurrentState() {
|
| 631 |
const pages = [];
|
| 632 |
document.querySelectorAll('.comic-page').forEach(p => {
|
|
|
|
| 633 |
const grid = p.querySelector('.comic-grid');
|
| 634 |
let template = 't-classic';
|
| 635 |
if(grid) {
|
|
|
|
| 665 |
|
| 666 |
function saveDraft() { localStorage.setItem('comic_draft_'+sid, JSON.stringify({ pages: getCurrentState(), saveCode: currentSaveCode, savedAt: new Date().toISOString() })); }
|
| 667 |
|
|
|
|
| 668 |
function changeTemplate(tpl) {
|
| 669 |
document.querySelectorAll('.comic-grid').forEach(g => {
|
|
|
|
| 670 |
g.classList.remove('t-classic', 't-feat-top', 't-side', 't-cinematic');
|
|
|
|
| 671 |
g.classList.add(tpl);
|
| 672 |
});
|
| 673 |
saveDraft();
|
|
|
|
| 682 |
const div = document.createElement('div'); div.className = 'comic-page';
|
| 683 |
|
| 684 |
const grid = document.createElement('div');
|
|
|
|
| 685 |
const tpl = page.template || 't-classic';
|
| 686 |
grid.className = `comic-grid ${tpl}`;
|
| 687 |
|
|
|
|
| 688 |
if(pageIdx === 0) document.getElementById('template-select').value = tpl;
|
| 689 |
|
| 690 |
page.panels.forEach((pan) => {
|
|
|
|
| 895 |
if 'file' not in request.files or not request.files['file'].filename:
|
| 896 |
return jsonify({'success': False, 'message': 'No file selected'}), 400
|
| 897 |
|
|
|
|
| 898 |
target_pages = request.form.get('target_pages', 4)
|
| 899 |
|
| 900 |
f = request.files['file']
|
|
|
|
| 903 |
f.save(gen.video_path)
|
| 904 |
gen.write_status("Starting...", 5)
|
| 905 |
|
| 906 |
+
# Run in thread - Standard processing
|
| 907 |
threading.Thread(target=gen.run, args=(target_pages,)).start()
|
| 908 |
return jsonify({'success': True, 'message': 'Generation started.'})
|
| 909 |
|
|
|
|
| 929 |
sid = request.args.get('sid')
|
| 930 |
d = request.get_json()
|
| 931 |
gen = EnhancedComicGenerator(sid)
|
| 932 |
+
# Direct function call
|
| 933 |
+
return jsonify(regen_frame_core(gen.video_path, gen.frames_dir, gen.metadata_path, d['filename'], d['direction']))
|
| 934 |
|
| 935 |
@app.route('/goto_timestamp', methods=['POST'])
|
| 936 |
def go_time():
|
| 937 |
sid = request.args.get('sid')
|
| 938 |
d = request.get_json()
|
| 939 |
gen = EnhancedComicGenerator(sid)
|
| 940 |
+
# Direct function call
|
| 941 |
+
return jsonify(get_frame_at_ts_core(gen.video_path, gen.frames_dir, gen.metadata_path, d['filename'], float(d['timestamp'])))
|
| 942 |
|
| 943 |
@app.route('/replace_panel', methods=['POST'])
|
| 944 |
def rep_panel():
|
|
|
|
| 1004 |
return jsonify({'success': False, 'message': str(e)})
|
| 1005 |
|
| 1006 |
if __name__ == '__main__':
|
| 1007 |
+
check_cuda()
|
|
|
|
| 1008 |
app.run(host='0.0.0.0', port=7860)
|