Update app_enhanced.py
Browse files- app_enhanced.py +34 -31
app_enhanced.py
CHANGED
|
@@ -15,15 +15,21 @@ import torch
|
|
| 15 |
from flask import Flask, jsonify, request, send_from_directory, send_file
|
| 16 |
|
| 17 |
# ======================================================
|
| 18 |
-
# π CUDA CONFIGURATION
|
| 19 |
# ======================================================
|
| 20 |
-
def
|
| 21 |
-
"""Checks
|
| 22 |
if torch.cuda.is_available():
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
return True
|
| 25 |
else:
|
| 26 |
-
print("β οΈ CUDA
|
| 27 |
return False
|
| 28 |
|
| 29 |
# ======================================================
|
|
@@ -60,8 +66,9 @@ app = Flask(__name__)
|
|
| 60 |
BASE_USER_DIR = "userdata"
|
| 61 |
SAVED_COMICS_DIR = "saved_comics"
|
| 62 |
|
| 63 |
-
|
| 64 |
-
os.makedirs(
|
|
|
|
| 65 |
|
| 66 |
def generate_save_code(length=8):
|
| 67 |
chars = string.ascii_uppercase + string.digits
|
|
@@ -71,16 +78,13 @@ def generate_save_code(length=8):
|
|
| 71 |
return code
|
| 72 |
|
| 73 |
# ======================================================
|
| 74 |
-
# π§ CORE
|
| 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
|
| 82 |
|
| 83 |
-
#
|
| 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
|
|
@@ -99,18 +103,17 @@ def generate_comic_core(video_path, user_dir, frames_dir, metadata_path, target_
|
|
| 99 |
# 2. Subtitles Generation
|
| 100 |
user_srt = os.path.join(user_dir, 'subs.srt')
|
| 101 |
try:
|
|
|
|
| 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
|
| 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 |
-
|
| 112 |
-
except:
|
| 113 |
-
all_subs = []
|
| 114 |
|
| 115 |
# 3. Smart Keyframe Selection
|
| 116 |
valid_subs = [s for s in all_subs if s.content.strip()]
|
|
@@ -152,11 +155,11 @@ def generate_comic_core(video_path, user_dir, frames_dir, metadata_path, target_
|
|
| 152 |
|
| 153 |
with open(metadata_path, 'w') as f: json.dump(frame_metadata, f, indent=2)
|
| 154 |
|
| 155 |
-
# 5. Image Enhancement
|
| 156 |
try: black_bar_crop()
|
| 157 |
except: pass
|
| 158 |
|
| 159 |
-
# Initialize enhancers
|
| 160 |
se = SimpleColorEnhancer()
|
| 161 |
qe = QualityColorEnhancer()
|
| 162 |
|
|
@@ -167,7 +170,7 @@ def generate_comic_core(video_path, user_dir, frames_dir, metadata_path, target_
|
|
| 167 |
try: qe.enhance_single(p, p)
|
| 168 |
except: pass
|
| 169 |
|
| 170 |
-
# 6. Bubble Placement
|
| 171 |
bubbles_list = []
|
| 172 |
for f in frame_files_ordered:
|
| 173 |
p = os.path.join(frames_dir, f)
|
|
@@ -280,8 +283,8 @@ class EnhancedComicGenerator:
|
|
| 280 |
|
| 281 |
def run(self, target_pages):
|
| 282 |
try:
|
| 283 |
-
self.write_status("Waiting for
|
| 284 |
-
#
|
| 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)
|
|
@@ -303,7 +306,7 @@ INDEX_HTML = '''
|
|
| 303 |
<head>
|
| 304 |
<meta charset="UTF-8">
|
| 305 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 306 |
-
<title>π¬
|
| 307 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
|
| 308 |
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@700&family=Gloria+Hallelujah&family=Lato&display=swap" rel="stylesheet">
|
| 309 |
<style>
|
|
@@ -446,7 +449,7 @@ INDEX_HTML = '''
|
|
| 446 |
<body>
|
| 447 |
<div id="upload-container">
|
| 448 |
<div class="upload-box">
|
| 449 |
-
<h1>π¬
|
| 450 |
<input type="file" id="file-upload" class="file-input" onchange="document.getElementById('fn').innerText=this.files[0].name">
|
| 451 |
<label for="file-upload" class="file-label">π Choose Video File</label>
|
| 452 |
<span id="fn" style="margin-bottom:10px; display:block; color:#666;">No file selected</span>
|
|
@@ -455,7 +458,7 @@ INDEX_HTML = '''
|
|
| 455 |
<input type="number" id="page-count" value="4" min="1" max="15" placeholder="e.g. 4">
|
| 456 |
<small style="color:#666; font-size:11px; display:block; margin-top:5px;">System calculates ~4 panels per page.</small>
|
| 457 |
</div>
|
| 458 |
-
<button class="submit-btn" onclick="upload()">π Generate Comic</button>
|
| 459 |
<button id="restore-draft-btn" class="restore-btn" style="display:none; margin-top:15px;" onclick="restoreDraft()">π Restore Unsaved Draft</button>
|
| 460 |
<div class="load-section">
|
| 461 |
<h3>π₯ Load Saved Comic</h3>
|
|
@@ -466,7 +469,7 @@ INDEX_HTML = '''
|
|
| 466 |
</div>
|
| 467 |
<div class="loading-view" id="loading-view" style="display:none; margin-top:20px;">
|
| 468 |
<div class="loader" style="margin:0 auto;"></div>
|
| 469 |
-
<p id="status-text" style="margin-top:10px;">
|
| 470 |
</div>
|
| 471 |
</div>
|
| 472 |
</div>
|
|
@@ -903,7 +906,7 @@ def upload():
|
|
| 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,7 +932,7 @@ def regen():
|
|
| 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'])
|
|
@@ -937,7 +940,7 @@ 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'])
|
|
@@ -1004,5 +1007,5 @@ def load_comic(code):
|
|
| 1004 |
return jsonify({'success': False, 'message': str(e)})
|
| 1005 |
|
| 1006 |
if __name__ == '__main__':
|
| 1007 |
-
|
| 1008 |
app.run(host='0.0.0.0', port=7860)
|
|
|
|
| 15 |
from flask import Flask, jsonify, request, send_from_directory, send_file
|
| 16 |
|
| 17 |
# ======================================================
|
| 18 |
+
# π H100 / CUDA CONFIGURATION
|
| 19 |
# ======================================================
|
| 20 |
+
def initialize_h100():
|
| 21 |
+
"""Checks and initializes the H100 GPU environment"""
|
| 22 |
if torch.cuda.is_available():
|
| 23 |
+
gpu_name = torch.cuda.get_device_name(0)
|
| 24 |
+
vram = torch.cuda.get_device_properties(0).total_memory / 1e9
|
| 25 |
+
print(f"\n{'='*40}")
|
| 26 |
+
print(f"β
H100 DETECTED: {gpu_name}")
|
| 27 |
+
print(f"π VRAM AVAILABLE: {vram:.2f} GB")
|
| 28 |
+
print(f"β
CUDA Version: {torch.version.cuda}")
|
| 29 |
+
print(f"{'='*40}\n")
|
| 30 |
return True
|
| 31 |
else:
|
| 32 |
+
print("\nβ οΈ WARNING: CUDA NOT DETECTED. Running on CPU (H100 not active?)\n")
|
| 33 |
return False
|
| 34 |
|
| 35 |
# ======================================================
|
|
|
|
| 66 |
BASE_USER_DIR = "userdata"
|
| 67 |
SAVED_COMICS_DIR = "saved_comics"
|
| 68 |
|
| 69 |
+
# Ensure write permissions for Docker environment
|
| 70 |
+
os.makedirs(BASE_USER_DIR, mode=0o777, exist_ok=True)
|
| 71 |
+
os.makedirs(SAVED_COMICS_DIR, mode=0o777, exist_ok=True)
|
| 72 |
|
| 73 |
def generate_save_code(length=8):
|
| 74 |
chars = string.ascii_uppercase + string.digits
|
|
|
|
| 78 |
return code
|
| 79 |
|
| 80 |
# ======================================================
|
| 81 |
+
# π§ CORE LOGIC (Native CUDA)
|
|
|
|
|
|
|
|
|
|
| 82 |
# ======================================================
|
| 83 |
|
| 84 |
def generate_comic_core(video_path, user_dir, frames_dir, metadata_path, target_pages):
|
| 85 |
+
print(f"π Processing Started on H100: {video_path}")
|
| 86 |
|
| 87 |
+
# Imports inside function to ensure they use the initialized CUDA context
|
| 88 |
from backend.keyframes.keyframes import black_bar_crop
|
| 89 |
from backend.simple_color_enhancer import SimpleColorEnhancer
|
| 90 |
from backend.quality_color_enhancer import QualityColorEnhancer
|
|
|
|
| 103 |
# 2. Subtitles Generation
|
| 104 |
user_srt = os.path.join(user_dir, 'subs.srt')
|
| 105 |
try:
|
| 106 |
+
# This will use the GPU for Whisper/ASR if implemented in backend
|
| 107 |
get_real_subtitles(video_path)
|
| 108 |
if os.path.exists('test1.srt'):
|
| 109 |
shutil.move('test1.srt', user_srt)
|
| 110 |
except Exception as e:
|
| 111 |
+
print(f"Subtitle generation skipped/failed: {e}")
|
| 112 |
with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:04,000\n...\n")
|
| 113 |
|
| 114 |
with open(user_srt, 'r', encoding='utf-8') as f:
|
| 115 |
+
try: all_subs = list(srt.parse(f.read()))
|
| 116 |
+
except: all_subs = []
|
|
|
|
|
|
|
| 117 |
|
| 118 |
# 3. Smart Keyframe Selection
|
| 119 |
valid_subs = [s for s in all_subs if s.content.strip()]
|
|
|
|
| 155 |
|
| 156 |
with open(metadata_path, 'w') as f: json.dump(frame_metadata, f, indent=2)
|
| 157 |
|
| 158 |
+
# 5. Image Enhancement (GPU Accelerated)
|
| 159 |
try: black_bar_crop()
|
| 160 |
except: pass
|
| 161 |
|
| 162 |
+
# Initialize enhancers - these should load to GPU automatically if backend supports it
|
| 163 |
se = SimpleColorEnhancer()
|
| 164 |
qe = QualityColorEnhancer()
|
| 165 |
|
|
|
|
| 170 |
try: qe.enhance_single(p, p)
|
| 171 |
except: pass
|
| 172 |
|
| 173 |
+
# 6. Bubble Placement (Uses Face Detection on GPU)
|
| 174 |
bubbles_list = []
|
| 175 |
for f in frame_files_ordered:
|
| 176 |
p = os.path.join(frames_dir, f)
|
|
|
|
| 283 |
|
| 284 |
def run(self, target_pages):
|
| 285 |
try:
|
| 286 |
+
self.write_status("Waiting for H100...", 5)
|
| 287 |
+
# Direct call to core function (no decorator)
|
| 288 |
data = generate_comic_core(self.video_path, self.user_dir, self.frames_dir, self.metadata_path, int(target_pages))
|
| 289 |
with open(os.path.join(self.output_dir, 'pages.json'), 'w') as f:
|
| 290 |
json.dump(data, f, indent=2)
|
|
|
|
| 306 |
<head>
|
| 307 |
<meta charset="UTF-8">
|
| 308 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 309 |
+
<title>π¬ H100 Comic Studio</title>
|
| 310 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
|
| 311 |
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@700&family=Gloria+Hallelujah&family=Lato&display=swap" rel="stylesheet">
|
| 312 |
<style>
|
|
|
|
| 449 |
<body>
|
| 450 |
<div id="upload-container">
|
| 451 |
<div class="upload-box">
|
| 452 |
+
<h1>π¬ H100 Comic Studio</h1>
|
| 453 |
<input type="file" id="file-upload" class="file-input" onchange="document.getElementById('fn').innerText=this.files[0].name">
|
| 454 |
<label for="file-upload" class="file-label">π Choose Video File</label>
|
| 455 |
<span id="fn" style="margin-bottom:10px; display:block; color:#666;">No file selected</span>
|
|
|
|
| 458 |
<input type="number" id="page-count" value="4" min="1" max="15" placeholder="e.g. 4">
|
| 459 |
<small style="color:#666; font-size:11px; display:block; margin-top:5px;">System calculates ~4 panels per page.</small>
|
| 460 |
</div>
|
| 461 |
+
<button class="submit-btn" onclick="upload()">π Generate Comic (H100 Speed)</button>
|
| 462 |
<button id="restore-draft-btn" class="restore-btn" style="display:none; margin-top:15px;" onclick="restoreDraft()">π Restore Unsaved Draft</button>
|
| 463 |
<div class="load-section">
|
| 464 |
<h3>π₯ Load Saved Comic</h3>
|
|
|
|
| 469 |
</div>
|
| 470 |
<div class="loading-view" id="loading-view" style="display:none; margin-top:20px;">
|
| 471 |
<div class="loader" style="margin:0 auto;"></div>
|
| 472 |
+
<p id="status-text" style="margin-top:10px;">Firing up the H100...</p>
|
| 473 |
</div>
|
| 474 |
</div>
|
| 475 |
</div>
|
|
|
|
| 906 |
f.save(gen.video_path)
|
| 907 |
gen.write_status("Starting...", 5)
|
| 908 |
|
| 909 |
+
# Run in thread - Standard H100 processing
|
| 910 |
threading.Thread(target=gen.run, args=(target_pages,)).start()
|
| 911 |
return jsonify({'success': True, 'message': 'Generation started.'})
|
| 912 |
|
|
|
|
| 932 |
sid = request.args.get('sid')
|
| 933 |
d = request.get_json()
|
| 934 |
gen = EnhancedComicGenerator(sid)
|
| 935 |
+
# Direct function call - H100
|
| 936 |
return jsonify(regen_frame_core(gen.video_path, gen.frames_dir, gen.metadata_path, d['filename'], d['direction']))
|
| 937 |
|
| 938 |
@app.route('/goto_timestamp', methods=['POST'])
|
|
|
|
| 940 |
sid = request.args.get('sid')
|
| 941 |
d = request.get_json()
|
| 942 |
gen = EnhancedComicGenerator(sid)
|
| 943 |
+
# Direct function call - H100
|
| 944 |
return jsonify(get_frame_at_ts_core(gen.video_path, gen.frames_dir, gen.metadata_path, d['filename'], float(d['timestamp'])))
|
| 945 |
|
| 946 |
@app.route('/replace_panel', methods=['POST'])
|
|
|
|
| 1007 |
return jsonify({'success': False, 'message': str(e)})
|
| 1008 |
|
| 1009 |
if __name__ == '__main__':
|
| 1010 |
+
initialize_h100()
|
| 1011 |
app.run(host='0.0.0.0', port=7860)
|