tester343 commited on
Commit
fa2d7e4
·
verified ·
1 Parent(s): b50a23d

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +201 -308
app_enhanced.py CHANGED
@@ -1,20 +1,14 @@
1
- import spaces # <--- CRITICAL: MUST BE THE FIRST IMPORT
2
  import os
3
- import time
4
- import threading
5
  import json
6
- import traceback
7
- import logging
8
- import string
9
- import random
10
- import shutil
11
  import cv2
12
  import numpy as np
13
  import srt
14
- from flask import Flask, jsonify, request, send_from_directory, send_file
15
 
16
  # ======================================================
17
- # 🚀 ZEROGPU CONFIGURATION
18
  # ======================================================
19
  @spaces.GPU
20
  def gpu_warmup():
@@ -22,342 +16,241 @@ def gpu_warmup():
22
  return torch.cuda.is_available()
23
 
24
  # ======================================================
25
- # 💾 STORAGE SETUP
26
  # ======================================================
27
- BASE_STORAGE_PATH = '/data' if os.path.exists('/data') else '.'
28
- BASE_USER_DIR = os.path.join(BASE_STORAGE_PATH, "userdata")
29
- os.makedirs(BASE_USER_DIR, exist_ok=True)
30
 
31
  # ======================================================
32
- # 🔧 JSON SANITIZER (FIX FOR int64 ERROR)
33
  # ======================================================
34
- def sanitize_json(obj):
35
  if isinstance(obj, dict):
36
- return {k: sanitize_json(v) for k, v in obj.items()}
37
- elif isinstance(obj, list):
38
- return [sanitize_json(v) for v in obj]
39
- elif isinstance(obj, (np.int64, np.int32, np.int16)):
40
  return int(obj)
41
- elif isinstance(obj, (np.float64, np.float32)):
42
- return float(obj)
43
- elif isinstance(obj, np.ndarray):
44
- return sanitize_json(obj.tolist())
45
  return obj
46
 
47
  # ======================================================
48
- # 🧠 CORE GPU GENERATOR
49
  # ======================================================
50
  @spaces.GPU(duration=300)
51
- def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages):
52
- import cv2
53
- import srt
54
- import numpy as np
55
- from backend.keyframes.keyframes import black_bar_crop
56
- from backend.simple_color_enhancer import SimpleColorEnhancer
57
- from backend.subtitles.subs_real import get_real_subtitles
58
- from backend.ai_bubble_placement import ai_bubble_placer
59
- from backend.ai_enhanced_core import face_detector
60
-
61
- cap = cv2.VideoCapture(video_path)
62
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
63
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
64
- duration = total_frames / fps
65
  cap.release()
66
 
67
- # 1. Subtitles
68
- user_srt = os.path.join(user_dir, 'subs.srt')
69
- try:
70
- get_real_subtitles(video_path)
71
- if os.path.exists('test1.srt'): shutil.move('test1.srt', user_srt)
72
- except:
73
- with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:05,000\n...\n")
74
-
75
- with open(user_srt, 'r', encoding='utf-8') as f:
76
- try: all_subs = list(srt.parse(f.read()))
77
- except: all_subs = []
78
-
79
- # 2. Logic for 5 Panels Per Page
80
  panels_per_page = 5
81
- target_pages = int(target_pages)
82
- total_needed = target_pages * panels_per_page
83
-
84
- if not all_subs:
85
- times = np.linspace(1, max(1.1, duration-1), total_needed)
86
- moments = [{'text': '', 'start': t} for t in times]
87
- elif len(all_subs) <= total_needed:
88
- moments = [{'text': s.content, 'start': s.start.total_seconds()} for s in all_subs]
89
- while len(moments) < total_needed: moments.append({'text': '', 'start': duration/2})
90
- else:
91
- indices = np.linspace(0, len(all_subs) - 1, total_needed, dtype=int)
92
- moments = [{'text': all_subs[i].content, 'start': all_subs[i].start.total_seconds()} for i in indices]
93
-
94
- # 3. Frame Extraction
95
- frame_metadata = {}
96
- cap = cv2.VideoCapture(video_path)
97
- frame_files = []
98
- for i, m in enumerate(moments):
99
- cap.set(cv2.CAP_PROP_POS_MSEC, m['start'] * 1000)
100
- ret, frame = cap.read()
101
- if ret:
102
- fname = f"frame_{i:04d}.png"
103
- p = os.path.join(frames_dir, fname)
104
- cv2.imwrite(p, frame)
105
- frame_metadata[fname] = {'dialogue': m['text'], 'time': m['start']}
106
- frame_files.append(fname)
107
  cap.release()
108
 
109
- with open(metadata_path, 'w') as f:
110
- json.dump(sanitize_json(frame_metadata), f)
 
 
 
 
 
 
111
 
112
- try: black_bar_crop()
113
- except: pass
114
-
115
- # 4. Enhance & Assemble
116
- se = SimpleColorEnhancer()
117
  pages_data = []
118
- for p_idx in range(target_pages):
119
- p_p = []
120
- p_b = []
121
- start = p_idx * 5
122
- for i in range(start, start + 5):
123
- if i >= len(frame_files): break
124
- f_name = frame_files[i]
125
- img_p = os.path.join(frames_dir, f_name)
126
- try: se.enhance_single(img_p, img_p)
127
- except: pass
128
-
129
- txt = frame_metadata[f_name]['dialogue']
130
- try:
131
- faces = face_detector.detect_faces(img_p)
132
- lip = face_detector.get_lip_position(img_p, faces[0]) if faces else (-1, -1)
133
- bx, by = ai_bubble_placer.place_bubble_ai(img_p, lip)
134
- item = {'dialog': txt, 'x': bx, 'y': by}
135
- except:
136
- item = {'dialog': txt, 'x': 50, 'y': 25}
137
- p_p.append({'image': f_name})
138
- p_b.append(item)
139
- pages_data.append({'panels': p_p, 'bubbles': p_b})
140
-
141
- return sanitize_json(pages_data)
142
 
143
  # ======================================================
144
- # 🔧 APP ENGINE
145
  # ======================================================
146
  app = Flask(__name__)
147
 
148
- @app.route('/')
149
- def index(): return INDEX_HTML
 
 
 
 
 
 
 
 
 
 
150
 
151
- @app.route('/uploader', methods=['POST'])
152
- def uploader():
153
- sid = request.args.get('sid')
154
- u_dir = os.path.join(BASE_USER_DIR, sid)
155
- f_dir = os.path.join(u_dir, 'frames'); o_dir = os.path.join(u_dir, 'output')
156
- os.makedirs(f_dir, exist_ok=True); os.makedirs(o_dir, exist_ok=True)
157
- vid_p = os.path.join(u_dir, 'video.mp4')
158
- request.files['file'].save(vid_p)
159
- pages = request.form.get('pages', 2)
160
 
161
  def task():
162
- try:
163
- with open(os.path.join(o_dir, 'status.json'), 'w') as f:
164
- json.dump({'message': 'Generating 0.1px Hairlines...', 'progress': 30}, f)
165
- data = generate_comic_gpu(vid_p, u_dir, f_dir, os.path.join(f_dir, 'meta.json'), pages)
166
- with open(os.path.join(o_dir, 'pages.json'), 'w') as f: json.dump(data, f)
167
- with open(os.path.join(o_dir, 'status.json'), 'w') as f:
168
- json.dump({'message': 'Complete', 'progress': 100}, f)
169
- except Exception as e:
170
- with open(os.path.join(o_dir, 'status.json'), 'w') as f:
171
- json.dump({'message': f'Error: {str(e)}', 'progress': -1}, f)
172
 
173
  threading.Thread(target=task).start()
174
- return jsonify({'success': True})
175
 
176
- @app.route('/status')
177
- def status():
178
- sid = request.args.get('sid')
179
- p = os.path.join(BASE_USER_DIR, sid, 'output', 'status.json')
180
- if os.path.exists(p): return send_file(p)
181
- return jsonify({'progress': 0})
182
 
183
- @app.route('/frames/<sid>/<path:filename>')
184
- def get_frame(sid, filename):
185
- return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'frames'), filename)
186
-
187
- @app.route('/output/<sid>/<path:filename>')
188
- def get_output(sid, filename):
189
- return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'output'), filename)
190
 
191
  # ======================================================
192
- # 🌐 UI HTML (0.1px HAIRLINE + ENGLISH UI)
193
  # ======================================================
194
- INDEX_HTML = '''
195
- <!DOCTYPE html><html lang="en">
 
196
  <head>
197
- <meta charset="UTF-8"><title>Elite Razor-Thin Comic Generator</title>
198
- <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
199
- <link href="https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&family=Lato:wght@400;900&display=swap" rel="stylesheet">
200
- <style>
201
- :root {
202
- --slant: 40px;
203
- --gutter: 0.1px; /* RAZOR THIN HAIRLINE */
204
- --page-border: 12px;
205
- --panel-ink: 1px;
206
- }
207
-
208
- body { background: #000; font-family: 'Lato', sans-serif; margin: 0; padding: 20px; color: white; }
209
- .setup-box { max-width: 450px; margin: 80px auto; background: white; padding: 40px; border-radius: 12px; color: black; text-align: center; }
210
-
211
- /* ⚡ THE 0.1px PIXEL-PERFECT TEMPLATE ⚡ */
212
- .comic-page {
213
- background: white; width: 1000px; height: 720px; margin: 40px auto;
214
- border: var(--page-border) solid black; padding: 2px; box-sizing: border-box;
215
- display: grid; gap: var(--gutter);
216
- grid-template-columns: repeat(6, 1fr);
217
- grid-template-rows: 1.35fr 1fr;
218
- position: relative; overflow: hidden;
219
- }
220
-
221
- .panel { position: relative; background: #000; overflow: hidden; cursor: pointer; border: var(--panel-ink) solid black; }
222
- /* Oversize images slightly and center them top-weighted for faces */
223
- .panel img { width: 118%; height: 118%; object-fit: cover; position: absolute; top: -9%; left: -9%; object-position: center 15%; pointer-events: none; }
224
-
225
- /* ROW 1: Slant LEFT \ (Panel 1 Wide, Panel 2 Narrow) */
226
- .panel:nth-child(1) {
227
- grid-column: span 4;
228
- clip-path: polygon(0 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%);
229
- }
230
- .panel:nth-child(2) {
231
- grid-column: span 2;
232
- clip-path: polygon(var(--slant) 0, 100% 0, 100% 100%, 0 100%);
233
- }
234
-
235
- /* ROW 2: Slant RIGHT / (Three equal action panels) */
236
- .panel:nth-child(3) {
237
- grid-column: span 2;
238
- clip-path: polygon(0 0, calc(100% - var(--slant)) 0, 100% 100%, 0 100%);
239
- }
240
- .panel:nth-child(4) {
241
- grid-column: span 2;
242
- clip-path: polygon(var(--slant) 0, calc(100% - var(--slant)) 0, 100% 100%, var(--slant) 100%);
243
- }
244
- .panel:nth-child(5) {
245
- grid-column: span 2;
246
- clip-path: polygon(var(--slant) 0, 100% 0, 100% 100%, var(--slant) 100%);
247
- }
248
-
249
- .panel.selected { outline: 6px solid #00d2ff; z-index: 5; filter: brightness(1.1); }
250
-
251
- /* BUBBLE STYLING - PRECISE WHITE CAPSULE */
252
- .bubble {
253
- position: absolute; background: white; border: 2.5px solid black; border-radius: 25px;
254
- padding: 10px 20px; font-family: 'Comic Neue'; font-weight: bold; font-size: 15px;
255
- color: black; min-width: 110px; text-align: center; cursor: move; z-index: 10;
256
- box-shadow: 4px 4px 0 rgba(0,0,0,0.1);
257
- }
258
- .bubble::after {
259
- content: ""; position: absolute; bottom: -18px; left: 30px;
260
- width: 0; height: 0; border-left: 10px solid transparent;
261
- border-right: 10px solid transparent; border-top: 20px solid black;
262
- }
263
- .bubble::before {
264
- content: ""; position: absolute; bottom: -13px; left: 31px;
265
- width: 0; height: 0; border-left: 9px solid transparent;
266
- border-right: 9px solid transparent; border-top: 17px solid white;
267
- z-index: 2;
268
- }
269
-
270
- .controls { position: fixed; bottom: 20px; right: 20px; background: #000; padding: 25px; border-radius: 12px; width: 240px; border: 2px solid #333; }
271
- button { width: 100%; padding: 12px; margin-top: 10px; cursor: pointer; font-weight: bold; border-radius: 6px; border: none; transition: 0.2s; }
272
- .hidden { display: none; }
273
- .loader { border: 5px solid #333; border-top: 5px solid #00d2ff; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 20px auto; }
274
- @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
275
- </style>
276
  </head>
 
277
  <body>
278
- <div id="upload-zone" class="setup-box">
279
- <h1>🎬 Hairline Comic Maker</h1>
280
- <p>0.1px Gap Precision & Parallel Geometry</p>
281
- <input type="file" id="vid" accept="video/mp4"><br><br>
282
- <label>Total Pages: </label><input type="number" id="pg" value="2" style="width:50px">
283
- <br><br>
284
- <button onclick="start()" style="background:#00d2ff; color:black; font-size:16px;">🚀 GENERATE COMIC</button>
285
- <div id="loading" class="hidden"><div class="loader"></div><p id="st">Acquiring GPU...</p></div>
286
- </div>
287
-
288
- <div id="editor-zone" class="hidden">
289
- <div id="output"></div>
290
- <div class="controls">
291
- <h4 style="margin:0; color:#00d2ff;">EDITOR</h4>
292
- <button onclick="addB()" style="background:#2ecc71; color:white;">💬 Add Bubble</button>
293
- <button onclick="exportPNG()" style="background:#3498db; color:white;">📥 Download PNGs</button>
294
- <button onclick="location.reload()" style="background:#e74c3c; color:white;">🏠 Reset</button>
295
- </div>
296
- </div>
297
 
298
  <script>
299
- let sid = 's' + Math.random().toString(36).substr(2,9);
300
- let selP = null;
301
-
302
- async function start() {
303
- const f = document.getElementById('vid').files[0];
304
- if(!f) return alert("Select a video!");
305
- document.getElementById('loading').classList.remove('hidden');
306
- const fd = new FormData(); fd.append('file', f); fd.append('pages', document.getElementById('pg').value);
307
- await fetch(`/uploader?sid=${sid}`, {method: 'POST', body: fd});
308
- const itv = setInterval(async () => {
309
- const r = await fetch(`/status?sid=${sid}`); const d = await r.json();
310
- document.getElementById('st').innerText = d.message || "Processing...";
311
- if(d.progress >= 100) { clearInterval(itv); load(); }
312
- }, 2000);
313
- }
314
-
315
- async function load() {
316
- const r = await fetch(`/output/${sid}/pages.json`); const pages = await r.json();
317
- document.getElementById('upload-zone').classList.add('hidden');
318
- document.getElementById('editor-zone').classList.remove('hidden');
319
- const out = document.getElementById('output');
320
- pages.forEach(p => {
321
- const pgDiv = document.createElement('div'); pgDiv.className = 'comic-page';
322
- p.panels.forEach((pan, i) => {
323
- const pDiv = document.createElement('div'); pDiv.className = 'panel';
324
- pDiv.onclick = (e) => { e.stopPropagation(); if(selP) selP.classList.remove('selected'); selP=pDiv; pDiv.classList.add('selected'); };
325
- const img = document.createElement('img'); img.src = `/frames/${sid}/${pan.image}`;
326
- pDiv.appendChild(img);
327
- if(p.bubbles[i]) pDiv.appendChild(createB(p.bubbles[i].dialog, p.bubbles[i].x, p.bubbles[i].y));
328
- pgDiv.appendChild(pDiv);
329
- });
330
- out.appendChild(pgDiv);
331
- });
332
- }
333
-
334
- function createB(txt, x, y) {
335
- const b = document.createElement('div'); b.className = 'bubble';
336
- b.innerText = txt || '...'; b.style.left = (x || 50) + 'px'; b.style.top = (y || 20) + 'px';
337
- b.onmousedown = (e) => {
338
- e.stopPropagation();
339
- let ox = e.clientX - b.offsetLeft, oy = e.clientY - b.offsetTop;
340
- document.onmousemove = (ev) => { b.style.left=(ev.clientX-ox)+'px'; b.style.top=(ev.clientY-oy)+'px'; };
341
- document.onmouseup = () => { document.onmousemove = null; };
342
- };
343
- b.ondblclick = () => { let n = prompt("Edit Text:", b.innerText); if(n) b.innerText = n; };
344
- return b;
345
- }
346
-
347
- function addB() { if(selP) selP.appendChild(createB("Dialogue", 60, 60)); else alert("Select a panel first!"); }
348
-
349
- async function exportPNG() {
350
- const pgs = document.querySelectorAll('.comic-page');
351
- for(let pg of pgs) {
352
- const url = await htmlToImage.toPng(pg, {pixelRatio: 2});
353
- const l = document.createElement('a'); l.download='Elite_Comic.png'; l.href=url; l.click();
354
- }
355
- }
356
  </script>
357
- </body></html>
358
- '''
 
359
 
360
- if __name__ == '__main__':
361
- try: gpu_warmup()
362
- except: pass
363
- app.run(host='0.0.0.0', port=7860)
 
1
+ import spaces
2
  import os
 
 
3
  import json
4
+ import threading
 
 
 
 
5
  import cv2
6
  import numpy as np
7
  import srt
8
+ from flask import Flask, request, jsonify, send_from_directory
9
 
10
  # ======================================================
11
+ # GPU
12
  # ======================================================
13
  @spaces.GPU
14
  def gpu_warmup():
 
16
  return torch.cuda.is_available()
17
 
18
  # ======================================================
19
+ # PATHS
20
  # ======================================================
21
+ BASE = "/data" if os.path.exists("/data") else "."
22
+ USERDATA = os.path.join(BASE, "userdata")
23
+ os.makedirs(USERDATA, exist_ok=True)
24
 
25
  # ======================================================
26
+ # SAFE JSON
27
  # ======================================================
28
+ def sanitize(obj):
29
  if isinstance(obj, dict):
30
+ return {k: sanitize(v) for k, v in obj.items()}
31
+ if isinstance(obj, list):
32
+ return [sanitize(v) for v in obj]
33
+ if isinstance(obj, np.integer):
34
  return int(obj)
 
 
 
 
35
  return obj
36
 
37
  # ======================================================
38
+ # COMIC GENERATOR (5 PANEL TEMPLATE)
39
  # ======================================================
40
  @spaces.GPU(duration=300)
41
+ def generate_comic(video, user_dir, frames_dir, pages):
42
+ cap = cv2.VideoCapture(video)
 
 
 
 
 
 
 
 
 
43
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
44
+ total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
45
+ duration = total / fps
46
  cap.release()
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  panels_per_page = 5
49
+ pages = int(pages)
50
+ needed = pages * panels_per_page
51
+
52
+ # Simple timestamps
53
+ times = np.linspace(1, max(1, duration - 1), needed)
54
+
55
+ cap = cv2.VideoCapture(video)
56
+ frames = []
57
+ for i, t in enumerate(times):
58
+ cap.set(cv2.CAP_PROP_POS_MSEC, t * 1000)
59
+ ok, frame = cap.read()
60
+ if ok:
61
+ name = f"frame_{i}.png"
62
+ cv2.imwrite(os.path.join(frames_dir, name), frame)
63
+ frames.append(name)
 
 
 
 
 
 
 
 
 
 
 
64
  cap.release()
65
 
66
+ # Fixed bubble positions per panel
67
+ bubble_slots = [
68
+ {"x": 60, "y": 320}, # panel 1
69
+ {"x": 40, "y": 60}, # panel 2
70
+ {"x": 30, "y": 240}, # panel 3
71
+ {"x": 30, "y": 60}, # panel 4
72
+ {"x": 30, "y": 240}, # panel 5
73
+ ]
74
 
 
 
 
 
 
75
  pages_data = []
76
+ for p in range(pages):
77
+ start = p * 5
78
+ page = []
79
+ for i in range(5):
80
+ idx = start + i
81
+ if idx >= len(frames):
82
+ continue
83
+ page.append({
84
+ "image": frames[idx],
85
+ "bubble": {
86
+ "text": "...",
87
+ **bubble_slots[i]
88
+ }
89
+ })
90
+ pages_data.append(page)
91
+
92
+ return sanitize(pages_data)
 
 
 
 
 
 
 
93
 
94
  # ======================================================
95
+ # APP
96
  # ======================================================
97
  app = Flask(__name__)
98
 
99
+ @app.route("/")
100
+ def index():
101
+ return INDEX_HTML
102
+
103
+ @app.route("/upload", methods=["POST"])
104
+ def upload():
105
+ sid = request.args.get("sid")
106
+ user = os.path.join(USERDATA, sid)
107
+ frames = os.path.join(user, "frames")
108
+ out = os.path.join(user, "output")
109
+ os.makedirs(frames, exist_ok=True)
110
+ os.makedirs(out, exist_ok=True)
111
 
112
+ video = os.path.join(user, "video.mp4")
113
+ request.files["file"].save(video)
114
+ pages = request.form.get("pages", 1)
 
 
 
 
 
 
115
 
116
  def task():
117
+ data = generate_comic(video, user, frames, pages)
118
+ with open(os.path.join(out, "pages.json"), "w") as f:
119
+ json.dump(data, f)
 
 
 
 
 
 
 
120
 
121
  threading.Thread(target=task).start()
122
+ return jsonify({"ok": True})
123
 
124
+ @app.route("/frames/<sid>/<name>")
125
+ def frames(sid, name):
126
+ return send_from_directory(os.path.join(USERDATA, sid, "frames"), name)
 
 
 
127
 
128
+ @app.route("/output/<sid>/<name>")
129
+ def output(sid, name):
130
+ return send_from_directory(os.path.join(USERDATA, sid, "output"), name)
 
 
 
 
131
 
132
  # ======================================================
133
+ # FRONTEND
134
  # ======================================================
135
+ INDEX_HTML = """
136
+ <!DOCTYPE html>
137
+ <html lang="hi">
138
  <head>
139
+ <meta charset="UTF-8">
140
+ <title>Comic Generator</title>
141
+
142
+ <style>
143
+ body { background:#111; margin:0; font-family:sans-serif; }
144
+
145
+ .comic-page {
146
+ width:1000px;
147
+ height:1400px;
148
+ margin:40px auto;
149
+ background:#fff;
150
+ border:10px solid #000;
151
+ padding:10px;
152
+ display:grid;
153
+ grid-template-columns:2fr 1fr;
154
+ grid-template-rows:1fr 1fr;
155
+ gap:10px;
156
+ }
157
+
158
+ .panel { position:relative; border:5px solid #000; overflow:hidden; }
159
+ .panel img { width:100%; height:100%; object-fit:cover; }
160
+
161
+ .bottom {
162
+ grid-column:1 / span 2;
163
+ display:grid;
164
+ grid-template-columns:1fr 1fr 1fr;
165
+ gap:10px;
166
+ }
167
+
168
+ .title {
169
+ position:absolute;
170
+ top:20px; left:20px; right:20px;
171
+ padding:20px;
172
+ border:4px solid #000;
173
+ background:#fff;
174
+ font-size:32px;
175
+ font-weight:900;
176
+ }
177
+
178
+ .bubble {
179
+ position:absolute;
180
+ background:#fff;
181
+ border:4px solid #000;
182
+ border-radius:30px;
183
+ padding:12px 20px;
184
+ font-weight:bold;
185
+ }
186
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  </head>
188
+
189
  <body>
190
+ <input type="file" id="f">
191
+ <button onclick="go()">Generate</button>
192
+ <div id="out"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
  <script>
195
+ let sid = "s" + Math.random().toString(36).slice(2);
196
+
197
+ async function go(){
198
+ let fd = new FormData();
199
+ fd.append("file", f.files[0]);
200
+ fd.append("pages", 1);
201
+ await fetch(`/upload?sid=${sid}`, {method:"POST", body:fd});
202
+ setTimeout(load, 3000);
203
+ }
204
+
205
+ async function load(){
206
+ let r = await fetch(`/output/${sid}/pages.json`);
207
+ let pages = await r.json();
208
+ pages.forEach(p=>{
209
+ let page = document.createElement("div");
210
+ page.className="comic-page";
211
+
212
+ // panel 1
213
+ let p1 = mk(p[0], true); page.appendChild(p1);
214
+ let p2 = mk(p[1]); page.appendChild(p2);
215
+
216
+ let bottom = document.createElement("div");
217
+ bottom.className="bottom";
218
+ bottom.appendChild(mk(p[2]));
219
+ bottom.appendChild(mk(p[3]));
220
+ bottom.appendChild(mk(p[4]));
221
+
222
+ page.appendChild(bottom);
223
+ out.appendChild(page);
224
+ });
225
+ }
226
+
227
+ function mk(d, title=false){
228
+ let p = document.createElement("div");
229
+ p.className="panel";
230
+ let img = document.createElement("img");
231
+ img.src = `/frames/${sid}/${d.image}`;
232
+ p.appendChild(img);
233
+
234
+ if(title){
235
+ let t = document.createElement("div");
236
+ t.className="title";
237
+ t.innerText="गट्टू चिंकी और मलतियों का रहस्य";
238
+ p.appendChild(t);
239
+ }
240
+
241
+ let b = document.createElement("div");
242
+ b.className="bubble";
243
+ b.style.left=d.bubble.x+"px";
244
+ b.style.top=d.bubble.y+"px";
245
+ b.innerText=d.bubble.text;
246
+ p.appendChild(b);
247
+ return p;
248
+ }
 
 
 
249
  </script>
250
+ </body>
251
+ </html>
252
+ """
253
 
254
+ if __name__ == "__main__":
255
+ gpu_warmup()
256
+ app.run(host="0.0.0.0", port=7860)