tester343 commited on
Commit
6eebba7
·
verified ·
1 Parent(s): 617eba6

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +114 -56
app_enhanced.py CHANGED
@@ -1,15 +1,16 @@
1
- import spaces # MUST BE FIRST
2
  import os
3
  import time
4
  import threading
5
  import json
 
6
  import cv2
7
  import numpy as np
8
  import srt
9
  from flask import Flask, jsonify, request, send_from_directory, send_file
10
 
11
  # ======================================================
12
- # 🚀 ZEROGPU & STORAGE
13
  # ======================================================
14
  @spaces.GPU
15
  def gpu_warmup():
@@ -28,7 +29,7 @@ def sanitize_json(obj):
28
  return obj
29
 
30
  # ======================================================
31
- # 🧠 THE ENGINE
32
  # ======================================================
33
  @spaces.GPU(duration=300)
34
  def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages):
@@ -48,16 +49,17 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
48
  user_srt = os.path.join(user_dir, 'subs.srt')
49
  try:
50
  get_real_subtitles(video_path)
51
- if os.path.exists('test1.srt'): os.rename('test1.srt', user_srt)
52
  except:
53
- with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:05,000\nDialogue...\n")
54
 
55
  with open(user_srt, 'r', encoding='utf-8') as f:
56
  try: all_subs = list(srt.parse(f.read()))
57
  except: all_subs = []
58
 
59
  panels_per_page = 5
60
- total_needed = int(target_pages) * panels_per_page
 
61
  indices = np.linspace(0, len(all_subs) - 1, total_needed, dtype=int) if all_subs else range(total_needed)
62
 
63
  frame_metadata = {}
@@ -80,7 +82,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
80
 
81
  se = SimpleColorEnhancer()
82
  pages_data = []
83
- for p_idx in range(int(target_pages)):
84
  p_p, p_b = [], []
85
  start = p_idx * 5
86
  for i in range(start, start + 5):
@@ -101,7 +103,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
101
  return sanitize_json(pages_data)
102
 
103
  # ======================================================
104
- # 🌐 FLASK & HTML
105
  # ======================================================
106
  app = Flask(__name__)
107
 
@@ -112,13 +114,19 @@ def index(): return INDEX_HTML
112
  def uploader():
113
  sid = request.args.get('sid')
114
  u_dir = os.path.join(BASE_USER_DIR, sid)
115
- f_dir = os.path.join(u_dir, 'frames'); o_dir = os.path.join(u_dir, 'output')
 
116
  os.makedirs(f_dir, exist_ok=True); os.makedirs(o_dir, exist_ok=True)
117
- request.files['file'].save(os.path.join(u_dir, 'video.mp4'))
118
 
 
 
 
 
 
 
119
  def task():
120
  try:
121
- data = generate_comic_gpu(os.path.join(u_dir, 'video.mp4'), u_dir, f_dir, os.path.join(f_dir, 'm.json'), request.form.get('pages', 2))
122
  with open(os.path.join(o_dir, 'p.json'), 'w') as f: json.dump(data, f)
123
  with open(os.path.join(o_dir, 's.json'), 'w') as f: json.dump({'progress': 100}, f)
124
  except Exception as e:
@@ -129,8 +137,10 @@ def uploader():
129
 
130
  @app.route('/status')
131
  def status():
132
- p = os.path.join(BASE_USER_DIR, request.args.get('sid'), 'output', 's.json')
133
- return send_file(p) if os.path.exists(p) else jsonify({'progress': 0})
 
 
134
 
135
  @app.route('/frames/<sid>/<path:f>')
136
  def get_frame(sid, f): return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'frames'), f)
@@ -138,95 +148,143 @@ def get_frame(sid, f): return send_from_directory(os.path.join(BASE_USER_DIR, si
138
  @app.route('/output/<sid>/<path:f>')
139
  def get_output(sid, f): return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'output'), f)
140
 
 
 
 
141
  INDEX_HTML = '''
142
  <!DOCTYPE html><html><head><meta charset="UTF-8">
 
143
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
144
  <style>
145
- :root {
146
- --slant: 40px; /* MATHEMATICAL PARALLEL OFFSET */
147
- --hairline: 0.1px; /* SUB-PIXEL PRECISION GAP */
148
- }
149
- body { background: #000; font-family: sans-serif; padding: 20px; color: white; }
150
 
151
- /* THE PERFECT GEOMETRIC TEMPLATE */
152
  .comic-page {
153
- background: white; width: 1000px; height: 750px; margin: auto;
154
  border: 12px solid black; padding: 2px; display: grid; gap: var(--hairline);
155
  grid-template-columns: repeat(6, 1fr); grid-template-rows: 1.35fr 1fr;
156
  position: relative; overflow: hidden;
157
  }
158
  .panel { position: relative; background: #000; overflow: hidden; border: 1px solid black; }
 
159
 
160
- /* TECHNIQUE: OVERSCAN & HEADROOM PROTECTION */
161
- .panel img {
162
- width: 115%; height: 115%; object-fit: cover;
163
- position: absolute; top: -7.5%; left: -7.5%;
164
- object-position: center 15%; /* PROTECTS FACES FROM TOP CLIPPING */
165
- }
166
-
167
- /* ROW 1: PARALLEL SLANT LEFT \ */
168
  .panel:nth-child(1) { grid-column: span 4; clip-path: polygon(0 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%); }
169
  .panel:nth-child(2) { grid-column: span 2; clip-path: polygon(var(--slant) 0, 100% 0, 100% 100%, 0 100%); }
170
-
171
- /* ROW 2: PARALLEL SLANT RIGHT / */
172
  .panel:nth-child(3) { grid-column: span 2; clip-path: polygon(0 0, calc(100% - var(--slant)) 0, 100% 100%, 0 100%); }
173
  .panel:nth-child(4) { grid-column: span 2; clip-path: polygon(var(--slant) 0, calc(100% - var(--slant)) 0, 100% 100%, var(--slant) 100%); }
174
  .panel:nth-child(5) { grid-column: span 2; clip-path: polygon(var(--slant) 0, 100% 0, 100% 100%, var(--slant) 100%); }
175
 
176
- .bubble { position: absolute; background: white; border: 2.5px solid black; border-radius: 25px; padding: 10px; color: black; font-weight: bold; cursor: move; z-index: 99; text-align: center; min-width: 100px;}
177
- .controls { position: fixed; bottom: 20px; right: 20px; background: #222; padding: 20px; border-radius: 10px; }
178
  .hidden { display: none; }
 
 
179
  </style></head>
180
  <body>
181
- <div id="u-zone" style="text-align:center; margin-top:100px;">
182
- <h1>Elite Geometric Generator</h1>
183
- <input type="file" id="vid"><br><br>
184
- <button onclick="start()" style="padding:10px 20px; background:cyan; font-weight:bold;">GENERATE PERFECT COMIC</button>
 
 
 
185
  </div>
 
186
  <div id="e-zone" class="hidden">
187
  <div id="out"></div>
188
- <div class="controls">
189
- <button onclick="exportPNG()" style="background:lime; padding:10px; width:100%;">DOWNLOAD PNG</button>
 
190
  </div>
191
  </div>
 
192
  <script>
193
  let sid = 's'+Math.random().toString(36).substr(2,9);
 
194
  async function start() {
195
- const fd = new FormData(); fd.append('file', document.getElementById('vid').files[0]);
196
- await fetch(`/uploader?sid=${sid}`, {method: 'POST', body: fd});
197
- const itv = setInterval(async () => {
198
- const r = await fetch(`/status?sid=${sid}`); const d = await r.json();
199
- if(d.progress >= 100) { clearInterval(itv); load(); }
200
- }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
- async function load() {
203
- const r = await fetch(`/output/${sid}/p.json`); const pages = await r.json();
 
 
204
  document.getElementById('u-zone').classList.add('hidden');
205
  document.getElementById('e-zone').classList.remove('hidden');
 
206
  pages.forEach(p => {
207
- const pg = document.createElement('div'); pg.className='comic-page';
 
208
  p.panels.forEach((pan, i) => {
209
- const div = document.createElement('div'); div.className='panel';
210
- const img = document.createElement('img'); img.src=`/frames/${sid}/${pan.image}`;
 
 
211
  div.appendChild(img);
212
- if(p.bubbles[i]) {
213
- const b = document.createElement('div'); b.className='bubble'; b.innerText=p.bubbles[i].dialog;
214
- b.style.left=p.bubbles[i].x+'px'; b.style.top=p.bubbles[i].y+'px';
 
 
 
 
215
  div.appendChild(b);
216
  }
217
- pg.appendChild(div);
218
  });
219
- document.getElementById('out').appendChild(pg);
220
  });
221
  }
 
222
  async function exportPNG() {
223
  const pgs = document.querySelectorAll('.comic-page');
224
  for(let pg of pgs) {
225
  const url = await htmlToImage.toPng(pg, {pixelRatio: 3});
226
- const l = document.createElement('a'); l.download='Page.png'; l.href=url; l.click();
 
 
 
227
  }
228
  }
229
- </script></body></html>'''
 
230
 
231
  if __name__ == '__main__':
232
  try: gpu_warmup()
 
1
+ import spaces # <--- CRITICAL: MUST BE THE FIRST IMPORT
2
  import os
3
  import time
4
  import threading
5
  import json
6
+ import shutil
7
  import cv2
8
  import numpy as np
9
  import srt
10
  from flask import Flask, jsonify, request, send_from_directory, send_file
11
 
12
  # ======================================================
13
+ # 🚀 ZEROGPU & STORAGE SETUP
14
  # ======================================================
15
  @spaces.GPU
16
  def gpu_warmup():
 
29
  return obj
30
 
31
  # ======================================================
32
+ # 🧠 THE GEOMETRIC ENGINE
33
  # ======================================================
34
  @spaces.GPU(duration=300)
35
  def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages):
 
49
  user_srt = os.path.join(user_dir, 'subs.srt')
50
  try:
51
  get_real_subtitles(video_path)
52
+ if os.path.exists('test1.srt'): shutil.move('test1.srt', user_srt)
53
  except:
54
+ with open(user_srt, 'w') as f: f.write("1\n00:00:01,000 --> 00:00:05,000\n...\n")
55
 
56
  with open(user_srt, 'r', encoding='utf-8') as f:
57
  try: all_subs = list(srt.parse(f.read()))
58
  except: all_subs = []
59
 
60
  panels_per_page = 5
61
+ target_pages = int(target_pages)
62
+ total_needed = target_pages * panels_per_page
63
  indices = np.linspace(0, len(all_subs) - 1, total_needed, dtype=int) if all_subs else range(total_needed)
64
 
65
  frame_metadata = {}
 
82
 
83
  se = SimpleColorEnhancer()
84
  pages_data = []
85
+ for p_idx in range(target_pages):
86
  p_p, p_b = [], []
87
  start = p_idx * 5
88
  for i in range(start, start + 5):
 
103
  return sanitize_json(pages_data)
104
 
105
  # ======================================================
106
+ # 🌐 FLASK SERVER
107
  # ======================================================
108
  app = Flask(__name__)
109
 
 
114
  def uploader():
115
  sid = request.args.get('sid')
116
  u_dir = os.path.join(BASE_USER_DIR, sid)
117
+ f_dir = os.path.join(u_dir, 'frames')
118
+ o_dir = os.path.join(u_dir, 'output')
119
  os.makedirs(f_dir, exist_ok=True); os.makedirs(o_dir, exist_ok=True)
 
120
 
121
+ video_file = request.files['file']
122
+ video_path = os.path.join(u_dir, 'video.mp4')
123
+ video_file.save(video_path)
124
+
125
+ pages = request.form.get('pages', 2)
126
+
127
  def task():
128
  try:
129
+ data = generate_comic_gpu(video_path, u_dir, f_dir, os.path.join(f_dir, 'm.json'), pages)
130
  with open(os.path.join(o_dir, 'p.json'), 'w') as f: json.dump(data, f)
131
  with open(os.path.join(o_dir, 's.json'), 'w') as f: json.dump({'progress': 100}, f)
132
  except Exception as e:
 
137
 
138
  @app.route('/status')
139
  def status():
140
+ sid = request.args.get('sid')
141
+ p = os.path.join(BASE_USER_DIR, sid, 'output', 's.json')
142
+ if os.path.exists(p): return send_file(p)
143
+ return jsonify({'progress': 1, 'msg': 'Processing...'})
144
 
145
  @app.route('/frames/<sid>/<path:f>')
146
  def get_frame(sid, f): return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'frames'), f)
 
148
  @app.route('/output/<sid>/<path:f>')
149
  def get_output(sid, f): return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'output'), f)
150
 
151
+ # ======================================================
152
+ # 🖼️ UI HTML (FIXED GENERATE BUTTON)
153
+ # ======================================================
154
  INDEX_HTML = '''
155
  <!DOCTYPE html><html><head><meta charset="UTF-8">
156
+ <title>Pro Geometric Comic Maker</title>
157
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
158
  <style>
159
+ :root { --slant: 40px; --hairline: 0.1px; }
160
+ body { background: #000; font-family: sans-serif; color: white; text-align: center; padding: 20px; }
 
 
 
161
 
 
162
  .comic-page {
163
+ background: white; width: 1000px; height: 750px; margin: 20px auto;
164
  border: 12px solid black; padding: 2px; display: grid; gap: var(--hairline);
165
  grid-template-columns: repeat(6, 1fr); grid-template-rows: 1.35fr 1fr;
166
  position: relative; overflow: hidden;
167
  }
168
  .panel { position: relative; background: #000; overflow: hidden; border: 1px solid black; }
169
+ .panel img { width: 115%; height: 115%; object-fit: cover; position: absolute; top: -7.5%; left: -7.5%; object-position: center 15%; }
170
 
171
+ /* TILTED GEOMETRY */
 
 
 
 
 
 
 
172
  .panel:nth-child(1) { grid-column: span 4; clip-path: polygon(0 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%); }
173
  .panel:nth-child(2) { grid-column: span 2; clip-path: polygon(var(--slant) 0, 100% 0, 100% 100%, 0 100%); }
 
 
174
  .panel:nth-child(3) { grid-column: span 2; clip-path: polygon(0 0, calc(100% - var(--slant)) 0, 100% 100%, 0 100%); }
175
  .panel:nth-child(4) { grid-column: span 2; clip-path: polygon(var(--slant) 0, calc(100% - var(--slant)) 0, 100% 100%, var(--slant) 100%); }
176
  .panel:nth-child(5) { grid-column: span 2; clip-path: polygon(var(--slant) 0, 100% 0, 100% 100%, var(--slant) 100%); }
177
 
178
+ .bubble { position: absolute; background: white; border: 2.5px solid black; border-radius: 25px; padding: 10px; color: black; font-weight: bold; z-index: 99; min-width: 100px;}
 
179
  .hidden { display: none; }
180
+ .btn { padding: 15px 30px; font-size: 18px; font-weight: bold; cursor: pointer; border-radius: 8px; border: none; }
181
+ #status-msg { margin-top: 20px; font-weight: bold; color: cyan; }
182
  </style></head>
183
  <body>
184
+ <div id="u-zone">
185
+ <h1>🎬 Elite Geometric Generator</h1>
186
+ <p>Parallel Trapezoid Geometry | 0.1px Hairline Gutters</p>
187
+ <input type="file" id="vid" accept="video/mp4"><br><br>
188
+ <label>Pages: </label><input type="number" id="pg-count" value="2" style="width: 50px; padding: 5px;"><br><br>
189
+ <button id="gen-btn" class="btn" onclick="start()" style="background:cyan; color:black;">GENERATE PERFECT COMIC</button>
190
+ <div id="status-msg"></div>
191
  </div>
192
+
193
  <div id="e-zone" class="hidden">
194
  <div id="out"></div>
195
+ <div style="position:fixed; bottom:20px; right:20px;">
196
+ <button class="btn" onclick="exportPNG()" style="background:lime;">DOWNLOAD PNG</button>
197
+ <button class="btn" onclick="location.reload()" style="background:grey;">RESET</button>
198
  </div>
199
  </div>
200
+
201
  <script>
202
  let sid = 's'+Math.random().toString(36).substr(2,9);
203
+
204
  async function start() {
205
+ const fileInput = document.getElementById('vid');
206
+ const pgCount = document.getElementById('pg-count').value;
207
+
208
+ if(!fileInput.files[0]) return alert("Please select a video file first!");
209
+
210
+ // Feedback to user
211
+ document.getElementById('gen-btn').disabled = true;
212
+ document.getElementById('gen-btn').innerText = "UPLOADING...";
213
+ document.getElementById('status-msg').innerText = "Uploading video to GPU... please wait.";
214
+
215
+ const fd = new FormData();
216
+ fd.append('file', fileInput.files[0]);
217
+ fd.append('pages', pgCount);
218
+
219
+ try {
220
+ const resp = await fetch(`/uploader?sid=${sid}`, {method: 'POST', body: fd});
221
+ const result = await resp.json();
222
+
223
+ if(result.ok) {
224
+ const itv = setInterval(async () => {
225
+ const r = await fetch(`/status?sid=${sid}`);
226
+ const d = await r.json();
227
+
228
+ if(d.progress === 100) {
229
+ clearInterval(itv);
230
+ loadEditor();
231
+ } else if (d.progress === -1) {
232
+ clearInterval(itv);
233
+ alert("Error: " + d.msg);
234
+ location.reload();
235
+ } else {
236
+ document.getElementById('status-msg').innerText = "GPU Processing: " + (d.msg || "Extracting Frames...");
237
+ }
238
+ }, 3000);
239
+ }
240
+ } catch (e) {
241
+ alert("Connection Error. Check if Space is awake.");
242
+ location.reload();
243
+ }
244
  }
245
+
246
+ async function loadEditor() {
247
+ const r = await fetch(`/output/${sid}/p.json`);
248
+ const pages = await r.json();
249
  document.getElementById('u-zone').classList.add('hidden');
250
  document.getElementById('e-zone').classList.remove('hidden');
251
+
252
  pages.forEach(p => {
253
+ const pgDiv = document.createElement('div');
254
+ pgDiv.className = 'comic-page';
255
  p.panels.forEach((pan, i) => {
256
+ const div = document.createElement('div');
257
+ div.className = 'panel';
258
+ const img = document.createElement('img');
259
+ img.src = `/frames/${sid}/${pan.image}`;
260
  div.appendChild(img);
261
+
262
+ if(p.bubbles[i] && p.bubbles[i].dialog) {
263
+ const b = document.createElement('div');
264
+ b.className = 'bubble';
265
+ b.innerText = p.bubbles[i].dialog;
266
+ b.style.left = p.bubbles[i].x + 'px';
267
+ b.style.top = p.bubbles[i].y + 'px';
268
  div.appendChild(b);
269
  }
270
+ pgDiv.appendChild(div);
271
  });
272
+ document.getElementById('out').appendChild(pgDiv);
273
  });
274
  }
275
+
276
  async function exportPNG() {
277
  const pgs = document.querySelectorAll('.comic-page');
278
  for(let pg of pgs) {
279
  const url = await htmlToImage.toPng(pg, {pixelRatio: 3});
280
+ const l = document.createElement('a');
281
+ l.download = 'Comic_Page.png';
282
+ l.href = url;
283
+ l.click();
284
  }
285
  }
286
+ </script></body></html>
287
+ '''
288
 
289
  if __name__ == '__main__':
290
  try: gpu_warmup()