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

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +68 -96
app_enhanced.py CHANGED
@@ -10,7 +10,7 @@ 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,7 +29,7 @@ def sanitize_json(obj):
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):
@@ -45,18 +45,19 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
45
  duration = total_frames / fps
46
  cap.release()
47
 
48
- # Subtitles
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
@@ -71,7 +72,8 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
71
  ret, frame = cap.read()
72
  if ret:
73
  fname = f"frame_{i:04d}.png"
74
- cv2.imwrite(os.path.join(frames_dir, fname), frame)
 
75
  frame_metadata[fname] = {'dialogue': all_subs[idx].content if all_subs else "", 'time': t}
76
  frame_files.append(fname)
77
  cap.release()
@@ -103,7 +105,7 @@ def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_p
103
  return sanitize_json(pages_data)
104
 
105
  # ======================================================
106
- # 🌐 FLASK SERVER
107
  # ======================================================
108
  app = Flask(__name__)
109
 
@@ -114,24 +116,16 @@ def index(): return INDEX_HTML
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:
133
  with open(os.path.join(o_dir, 's.json'), 'w') as f: json.dump({'progress': -1, 'msg': str(e)}, f)
134
-
135
  threading.Thread(target=task).start()
136
  return jsonify({'ok': True})
137
 
@@ -140,7 +134,7 @@ 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)
@@ -149,122 +143,104 @@ def get_frame(sid, f): return send_from_directory(os.path.join(BASE_USER_DIR, si
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);
@@ -277,14 +253,10 @@ INDEX_HTML = '''
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()
 
10
  from flask import Flask, jsonify, request, send_from_directory, send_file
11
 
12
  # ======================================================
13
+ # 🚀 ZEROGPU & STORAGE
14
  # ======================================================
15
  @spaces.GPU
16
  def gpu_warmup():
 
29
  return obj
30
 
31
  # ======================================================
32
+ # 🧠 THE ENGINE
33
  # ======================================================
34
  @spaces.GPU(duration=300)
35
  def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages):
 
45
  duration = total_frames / fps
46
  cap.release()
47
 
48
+ # Get Dialogue
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:04,000\nDialogue...\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
+ # Calculate 5 panels per page
61
  panels_per_page = 5
62
  target_pages = int(target_pages)
63
  total_needed = target_pages * panels_per_page
 
72
  ret, frame = cap.read()
73
  if ret:
74
  fname = f"frame_{i:04d}.png"
75
+ p = os.path.join(frames_dir, fname)
76
+ cv2.imwrite(p, frame)
77
  frame_metadata[fname] = {'dialogue': all_subs[idx].content if all_subs else "", 'time': t}
78
  frame_files.append(fname)
79
  cap.release()
 
105
  return sanitize_json(pages_data)
106
 
107
  # ======================================================
108
+ # 🌐 SERVER
109
  # ======================================================
110
  app = Flask(__name__)
111
 
 
116
  def uploader():
117
  sid = request.args.get('sid')
118
  u_dir = os.path.join(BASE_USER_DIR, sid)
119
+ f_dir = os.path.join(u_dir, 'frames'); o_dir = os.path.join(u_dir, 'output')
 
120
  os.makedirs(f_dir, exist_ok=True); os.makedirs(o_dir, exist_ok=True)
121
+ request.files['file'].save(os.path.join(u_dir, 'video.mp4'))
 
 
 
 
 
 
122
  def task():
123
  try:
124
+ 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))
125
  with open(os.path.join(o_dir, 'p.json'), 'w') as f: json.dump(data, f)
126
  with open(os.path.join(o_dir, 's.json'), 'w') as f: json.dump({'progress': 100}, f)
127
  except Exception as e:
128
  with open(os.path.join(o_dir, 's.json'), 'w') as f: json.dump({'progress': -1, 'msg': str(e)}, f)
 
129
  threading.Thread(target=task).start()
130
  return jsonify({'ok': True})
131
 
 
134
  sid = request.args.get('sid')
135
  p = os.path.join(BASE_USER_DIR, sid, 'output', 's.json')
136
  if os.path.exists(p): return send_file(p)
137
+ return jsonify({'progress': 1, 'msg': 'Warming GPU...'})
138
 
139
  @app.route('/frames/<sid>/<path:f>')
140
  def get_frame(sid, f): return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'frames'), f)
 
143
  def get_output(sid, f): return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'output'), f)
144
 
145
  # ======================================================
146
+ # 🖼️ UI (PARALLEL SLANT MATH)
147
  # ======================================================
148
  INDEX_HTML = '''
149
  <!DOCTYPE html><html><head><meta charset="UTF-8">
 
150
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
151
+ <link href="https://fonts.googleapis.com/css2?family=Comic+Neue:wght@700&family=Lato:wght@400;900&display=swap" rel="stylesheet">
152
  <style>
153
+ :root {
154
+ --slant: 35px; /* Angle offset */
155
+ --gutter: 2px; /* LITTLE SPACE AS REQUESTED */
156
+ }
157
+ body { background: #000; font-family: 'Lato', sans-serif; color: white; text-align: center; }
158
 
159
+ /* THE PARALLEL GEOMETRIC GRID */
160
  .comic-page {
161
+ background: white; width: 1000px; height: 750px; margin: 30px auto;
162
+ border: 12px solid black; padding: 4px; display: grid; gap: var(--gutter);
163
  grid-template-columns: repeat(6, 1fr); grid-template-rows: 1.35fr 1fr;
164
  position: relative; overflow: hidden;
165
  }
166
+
167
+ .panel { position: relative; background: #000; overflow: hidden; border: 1.5px solid black; }
168
+
169
+ /* Headroom Protection */
170
+ .panel img {
171
+ width: 112%; height: 112%; object-fit: cover;
172
+ position: absolute; top: -6%; left: -6%;
173
+ object-position: center 15%;
174
+ }
175
 
176
+ /* THE PARALLEL SLANT LOGIC ⚡ */
177
+ /* Side A moves left, Side B follows it left - Keeping gutter parallel */
178
+
179
+ /* Row 1: Left Slant Divider */
180
  .panel:nth-child(1) { grid-column: span 4; clip-path: polygon(0 0, 100% 0, calc(100% - var(--slant)) 100%, 0 100%); }
181
  .panel:nth-child(2) { grid-column: span 2; clip-path: polygon(var(--slant) 0, 100% 0, 100% 100%, 0 100%); }
182
+
183
+ /* Row 2: Right Slant Dividers */
184
  .panel:nth-child(3) { grid-column: span 2; clip-path: polygon(0 0, calc(100% - var(--slant)) 0, 100% 100%, 0 100%); }
185
  .panel:nth-child(4) { grid-column: span 2; clip-path: polygon(var(--slant) 0, calc(100% - var(--slant)) 0, 100% 100%, var(--slant) 100%); }
186
  .panel:nth-child(5) { grid-column: span 2; clip-path: polygon(var(--slant) 0, 100% 0, 100% 100%, var(--slant) 100%); }
187
 
188
  .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;}
189
  .hidden { display: none; }
190
+ .btn { padding: 12px 24px; font-size: 16px; font-weight: 900; cursor: pointer; border-radius: 8px; border: none; margin: 10px; }
191
+ #status-log { color: #00d2ff; font-weight: bold; margin-top: 10px; }
192
  </style></head>
193
  <body>
194
+ <div id="u-zone" style="margin-top:80px;">
195
+ <h1>🎬 Elite Geometric Comic Maker</h1>
196
+ <p>Parallel Slant Geometry | Consistent Hairline Gutters</p>
197
  <input type="file" id="vid" accept="video/mp4"><br><br>
198
+ <button id="gen-btn" class="btn" onclick="start()" style="background:#00d2ff; color:black;">GENERATE COMIC</button>
199
+ <div id="status-log"></div>
 
200
  </div>
201
 
202
  <div id="e-zone" class="hidden">
203
  <div id="out"></div>
204
  <div style="position:fixed; bottom:20px; right:20px;">
205
+ <button class="btn" onclick="exportPNG()" style="background:lime; color:black;">📥 DOWNLOAD PNG</button>
206
+ <button class="btn" onclick="location.reload()" style="background:#555; color:white;">🏠 RESET</button>
207
  </div>
208
  </div>
209
 
210
  <script>
211
  let sid = 's'+Math.random().toString(36).substr(2,9);
 
212
  async function start() {
213
+ const file = document.getElementById('vid').files[0];
214
+ if(!file) return alert("Select video");
215
 
 
 
 
216
  document.getElementById('gen-btn').disabled = true;
217
+ document.getElementById('gen-btn').innerText = "PROCESSING...";
218
+ document.getElementById('status-log').innerText = "Uploading to GPU...";
219
 
220
+ const fd = new FormData(); fd.append('file', file); fd.append('pages', 2);
221
+ await fetch(`/uploader?sid=${sid}`, {method: 'POST', body: fd});
222
+
223
+ const itv = setInterval(async () => {
224
+ const r = await fetch(`/status?sid=${sid}`); const d = await r.json();
225
+ document.getElementById('status-log').innerText = "Status: " + (d.msg || "Extracting Panels...");
226
+ if(d.progress >= 100) { clearInterval(itv); load(); }
227
+ if(d.progress < 0) { clearInterval(itv); alert("Error: " + d.msg); location.reload(); }
228
+ }, 3000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  }
230
 
231
+ async function load() {
232
+ const r = await fetch(`/output/${sid}/p.json`); const pages = await r.json();
 
233
  document.getElementById('u-zone').classList.add('hidden');
234
  document.getElementById('e-zone').classList.remove('hidden');
 
235
  pages.forEach(p => {
236
+ const pgDiv = document.createElement('div'); pgDiv.className='comic-page';
 
237
  p.panels.forEach((pan, i) => {
238
+ const div = document.createElement('div'); div.className='panel';
239
+ const img = document.createElement('img'); img.src=`/frames/${sid}/${pan.image}`;
 
 
240
  div.appendChild(img);
 
241
  if(p.bubbles[i] && p.bubbles[i].dialog) {
242
+ const b = document.createElement('div'); b.className='bubble'; b.innerText=p.bubbles[i].dialog;
243
+ b.style.left='50px'; b.style.top='30px';
 
 
 
244
  div.appendChild(b);
245
  }
246
  pgDiv.appendChild(div);
 
253
  const pgs = document.querySelectorAll('.comic-page');
254
  for(let pg of pgs) {
255
  const url = await htmlToImage.toPng(pg, {pixelRatio: 3});
256
+ const l = document.createElement('a'); l.download='Comic_Page.png'; l.href=url; l.click();
 
 
 
257
  }
258
  }
259
+ </script></body></html>'''
 
260
 
261
  if __name__ == '__main__':
262
  try: gpu_warmup()