tester343 commited on
Commit
69db5d6
·
verified ·
1 Parent(s): a3c4dc0

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +61 -44
app_enhanced.py CHANGED
@@ -1,4 +1,4 @@
1
- import spaces
2
  import os
3
  import threading
4
  import json
@@ -6,14 +6,24 @@ import traceback
6
  import shutil
7
  import cv2
8
  import numpy as np
 
9
  from flask import Flask, jsonify, request, send_from_directory, send_file
10
 
11
  # ======================================================
12
- # 🔧 CONFIGURATION & SETUP
 
 
 
 
 
 
 
 
 
13
  # ======================================================
14
  app = Flask(__name__)
15
 
16
- # Check for persistent storage (Hugging Face Spaces specific)
17
  if os.path.exists('/data'):
18
  BASE_STORAGE_PATH = '/data'
19
  else:
@@ -23,20 +33,21 @@ BASE_USER_DIR = os.path.join(BASE_STORAGE_PATH, "userdata")
23
  os.makedirs(BASE_USER_DIR, exist_ok=True)
24
 
25
  # ======================================================
26
- # 🧠 GPU / BACKEND LOGIC
27
  # ======================================================
28
 
29
  def create_placeholder_image(text, filename, output_dir):
30
  """Creates a backup image if video fails to read."""
31
- img = np.zeros((600, 600, 3), dtype=np.uint8)
32
- img[:] = (40, 40, 40) # Dark gray
 
33
 
34
- # Text
35
  font = cv2.FONT_HERSHEY_SIMPLEX
36
- cv2.putText(img, text, (50, 300), font, 1.5, (200, 200, 200), 2, cv2.LINE_AA)
37
 
38
- # Border
39
- cv2.rectangle(img, (0,0), (600,600), (100,100,100), 10)
40
 
41
  path = os.path.join(output_dir, filename)
42
  cv2.imwrite(path, img)
@@ -45,9 +56,9 @@ def create_placeholder_image(text, filename, output_dir):
45
  @spaces.GPU(duration=120)
46
  def generate_comic_gpu(video_path, frames_dir, target_pages):
47
  """
48
- Extracts frames for a 4-panel layout (2x2 grid).
49
- Target: 4 frames per page.
50
  """
 
51
 
52
  # 1. Setup
53
  if os.path.exists(frames_dir): shutil.rmtree(frames_dir)
@@ -67,7 +78,7 @@ def generate_comic_gpu(video_path, frames_dir, target_pages):
67
 
68
  # 2. Calculate frames needed (4 per page)
69
  panels_per_page = 4
70
- total_panels_needed = target_pages * panels_per_page
71
 
72
  frame_files_ordered = []
73
 
@@ -86,6 +97,7 @@ def generate_comic_gpu(video_path, frames_dir, target_pages):
86
  min_dim = min(h, w)
87
  start_x = (w - min_dim) // 2
88
  start_y = (h - min_dim) // 2
 
89
  frame = frame[start_y:start_y+min_dim, start_x:start_x+min_dim]
90
 
91
  # Resize for consistency
@@ -106,7 +118,7 @@ def generate_comic_gpu(video_path, frames_dir, target_pages):
106
 
107
  # 4. Build Page Data
108
  pages_data = []
109
- for i in range(target_pages):
110
  start = i * panels_per_page
111
  end = start + panels_per_page
112
  p_frames = frame_files_ordered[start:end]
@@ -121,7 +133,7 @@ def generate_comic_gpu(video_path, frames_dir, target_pages):
121
 
122
  pg_bubbles = []
123
  if i == 0:
124
- pg_bubbles.append({'dialog': "Drag the CENTER DOT\nto adjust grid!", 'x': 50, 'y': 50})
125
 
126
  pages_data.append({
127
  'panels': pg_panels,
@@ -147,7 +159,7 @@ class ComicGenHost:
147
  def run(self, pages):
148
  try:
149
  self.write_status("Generating...", 30)
150
- data = generate_comic_gpu(self.video_path, self.frames_dir, int(pages))
151
 
152
  with open(os.path.join(self.output_dir, 'data.json'), 'w') as f:
153
  json.dump(data, f)
@@ -170,24 +182,24 @@ INDEX_HTML = '''
170
  <head>
171
  <meta charset="UTF-8">
172
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
173
- <title>4-Panel Comic Generator</title>
174
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
175
  <link href="https://fonts.googleapis.com/css2?family=Bangers&display=swap" rel="stylesheet">
176
  <style>
177
  body { background: #121212; color: #eee; font-family: sans-serif; margin: 0; text-align: center; }
178
 
179
- /* UPLOAD */
180
  #upload-view { padding: 50px; }
181
  .box { background: #1e1e1e; display: inline-block; padding: 40px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); }
182
- button { background: #f1c40f; border: none; padding: 10px 20px; font-weight: bold; cursor: pointer; border-radius: 4px; font-size: 16px; margin-top: 10px; }
183
- button:hover { background: #f39c12; }
184
  input { padding: 10px; margin: 5px; border-radius: 4px; border: 1px solid #555; background: #333; color: white; }
185
 
186
- /* EDITOR */
187
  #editor-view { display: none; padding: 20px; }
188
- .comic-container { display: flex; flex-wrap: wrap; justify-content: center; gap: 40px; margin-top: 20px; }
189
 
190
- /* PAGE */
191
  .comic-page {
192
  width: 600px; height: 800px;
193
  background: white;
@@ -195,46 +207,47 @@ INDEX_HTML = '''
195
  position: relative;
196
  box-shadow: 0 0 20px rgba(0,0,0,0.5);
197
  user-select: none;
198
- overflow: hidden; /* Clean edges */
199
  }
200
 
201
  /* 2x2 GRID LOGIC */
202
  .comic-grid {
203
  width: 100%; height: 100%;
204
  position: relative;
205
- background: #000; /* Gutter color */
206
  --x: 50%;
207
  --y: 50%;
 
208
  }
209
 
210
  .panel {
211
  position: absolute;
212
  overflow: hidden;
213
  background: #333;
214
- border: 2px solid black; /* Inner borders */
215
  box-sizing: border-box;
 
216
  }
217
 
218
  .panel img {
219
  width: 100%; height: 100%;
220
  object-fit: cover;
221
- pointer-events: auto; /* Allow image dragging if added later */
222
  }
223
 
224
- /* POSITIONS BASED ON CSS VARIABLES */
225
  /* Top Left */
226
- .panel:nth-child(1) { left: 0; top: 0; width: var(--x); height: var(--y); }
227
  /* Top Right */
228
- .panel:nth-child(2) { left: var(--x); top: 0; width: calc(100% - var(--x)); height: var(--y); }
229
  /* Bottom Left */
230
- .panel:nth-child(3) { left: 0; top: var(--y); width: var(--x); height: calc(100% - var(--y)); }
231
  /* Bottom Right */
232
- .panel:nth-child(4) { left: var(--x); top: var(--y); width: calc(100% - var(--x)); height: calc(100% - var(--y)); }
233
 
234
- /* CENTRAL HANDLE */
235
  .grid-handle {
236
  position: absolute;
237
- width: 30px; height: 30px;
238
  background: #e74c3c;
239
  border: 3px solid white;
240
  border-radius: 50%;
@@ -245,7 +258,7 @@ INDEX_HTML = '''
245
  box-shadow: 0 4px 10px rgba(0,0,0,0.5);
246
  pointer-events: auto;
247
  }
248
- .grid-handle:hover { transform: translate(-50%, -50%) scale(1.1); }
249
 
250
  /* BUBBLES */
251
  .bubble {
@@ -272,6 +285,7 @@ INDEX_HTML = '''
272
  background: #333; padding: 10px 20px; border-radius: 50px;
273
  display: flex; gap: 15px; z-index: 1000; box-shadow: 0 5px 20px rgba(0,0,0,0.6);
274
  }
 
275
  </style>
276
  </head>
277
  <body>
@@ -283,25 +297,25 @@ INDEX_HTML = '''
283
  <input type="file" id="fileIn" accept="video/*"><br>
284
  <label>Pages:</label> <input type="number" id="pgCount" value="1" min="1" max="5" style="width:50px;">
285
  <br>
286
- <button onclick="startUpload()">Generate</button>
287
  <p id="status" style="color: #bbb; margin-top:10px;"></p>
288
  </div>
289
  </div>
290
 
291
  <div id="editor-view">
292
- <h2>Adjust the Crosshair to resize panels!</h2>
293
  <div class="comic-container" id="container"></div>
294
 
295
  <div class="toolbar">
296
  <button onclick="addBubble()">💬 Add Text</button>
297
  <button onclick="downloadAll()">💾 Download</button>
298
- <button style="background:#555" onclick="location.reload()">↺ Reset</button>
299
  </div>
300
  </div>
301
 
302
  <script>
303
  let sid = 'S' + Date.now();
304
- let dragItem = null; // 'handle' or 'bubble'
305
  let activeEl = null;
306
 
307
  async function startUpload() {
@@ -312,7 +326,7 @@ INDEX_HTML = '''
312
  fd.append('file', f);
313
  fd.append('pages', document.getElementById('pgCount').value);
314
 
315
- document.getElementById('status').innerText = "Uploading & Processing (GPU)...";
316
 
317
  let r = await fetch(`/upload?sid=${sid}`, {method:'POST', body:fd});
318
  if(r.ok) monitorStatus();
@@ -346,7 +360,6 @@ INDEX_HTML = '''
346
 
347
  let grid = document.createElement('div');
348
  grid.className = 'comic-grid';
349
- // Default split
350
  grid.style.setProperty('--x', pg.splitX || '50%');
351
  grid.style.setProperty('--y', pg.splitY || '50%');
352
 
@@ -392,7 +405,7 @@ INDEX_HTML = '''
392
  };
393
 
394
  if(!parent) parent = document.querySelector('.comic-grid');
395
- parent.appendChild(b);
396
  }
397
 
398
  window.addBubble = () => createBubble("New Text");
@@ -428,7 +441,6 @@ INDEX_HTML = '''
428
  window.downloadAll = async () => {
429
  let pgs = document.querySelectorAll('.comic-page');
430
  for(let i=0; i<pgs.length; i++) {
431
- // Hide handles for screenshot
432
  let handles = pgs[i].querySelectorAll('.grid-handle');
433
  handles.forEach(h => h.style.display = 'none');
434
 
@@ -485,5 +497,10 @@ def output(filename):
485
  return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'output'), filename)
486
 
487
  if __name__ == '__main__':
488
- # Standard HF Spaces port
 
 
 
 
 
489
  app.run(host='0.0.0.0', port=7860)
 
1
+ import spaces # <--- CRITICAL: MUST BE THE FIRST LINE
2
  import os
3
  import threading
4
  import json
 
6
  import shutil
7
  import cv2
8
  import numpy as np
9
+ import torch
10
  from flask import Flask, jsonify, request, send_from_directory, send_file
11
 
12
  # ======================================================
13
+ # 🚀 ZEROGPU WARMUP (FIXES RUNTIME ERROR)
14
+ # ======================================================
15
+ @spaces.GPU
16
+ def gpu_warmup():
17
+ """Dummy function to trigger ZeroGPU detection at startup."""
18
+ print(f"✅ GPU Warmup: CUDA Available = {torch.cuda.is_available()}")
19
+ return True
20
+
21
+ # ======================================================
22
+ # 🔧 CONFIGURATION
23
  # ======================================================
24
  app = Flask(__name__)
25
 
26
+ # Persistent storage check
27
  if os.path.exists('/data'):
28
  BASE_STORAGE_PATH = '/data'
29
  else:
 
33
  os.makedirs(BASE_USER_DIR, exist_ok=True)
34
 
35
  # ======================================================
36
+ # 🧠 BACKEND LOGIC
37
  # ======================================================
38
 
39
  def create_placeholder_image(text, filename, output_dir):
40
  """Creates a backup image if video fails to read."""
41
+ # Create dark grey image
42
+ img = np.zeros((800, 800, 3), dtype=np.uint8)
43
+ img[:] = (40, 40, 40)
44
 
45
+ # Add text
46
  font = cv2.FONT_HERSHEY_SIMPLEX
47
+ cv2.putText(img, text, (50, 400), font, 1.5, (200, 200, 200), 3, cv2.LINE_AA)
48
 
49
+ # Add border
50
+ cv2.rectangle(img, (0,0), (800,800), (100,100,100), 20)
51
 
52
  path = os.path.join(output_dir, filename)
53
  cv2.imwrite(path, img)
 
56
  @spaces.GPU(duration=120)
57
  def generate_comic_gpu(video_path, frames_dir, target_pages):
58
  """
59
+ Extracts 4 frames per page (2x2 Grid).
 
60
  """
61
+ print(f"🚀 Starting GPU generation for {video_path}")
62
 
63
  # 1. Setup
64
  if os.path.exists(frames_dir): shutil.rmtree(frames_dir)
 
78
 
79
  # 2. Calculate frames needed (4 per page)
80
  panels_per_page = 4
81
+ total_panels_needed = int(target_pages) * panels_per_page
82
 
83
  frame_files_ordered = []
84
 
 
97
  min_dim = min(h, w)
98
  start_x = (w - min_dim) // 2
99
  start_y = (h - min_dim) // 2
100
+ # Crop center square
101
  frame = frame[start_y:start_y+min_dim, start_x:start_x+min_dim]
102
 
103
  # Resize for consistency
 
118
 
119
  # 4. Build Page Data
120
  pages_data = []
121
+ for i in range(int(target_pages)):
122
  start = i * panels_per_page
123
  end = start + panels_per_page
124
  p_frames = frame_files_ordered[start:end]
 
133
 
134
  pg_bubbles = []
135
  if i == 0:
136
+ pg_bubbles.append({'dialog': "Drag the RED DOT\nto resize panels!", 'x': '50%', 'y': '50%'})
137
 
138
  pages_data.append({
139
  'panels': pg_panels,
 
159
  def run(self, pages):
160
  try:
161
  self.write_status("Generating...", 30)
162
+ data = generate_comic_gpu(self.video_path, self.frames_dir, pages)
163
 
164
  with open(os.path.join(self.output_dir, 'data.json'), 'w') as f:
165
  json.dump(data, f)
 
182
  <head>
183
  <meta charset="UTF-8">
184
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
185
+ <title>4-Panel Adjustable Comic</title>
186
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
187
  <link href="https://fonts.googleapis.com/css2?family=Bangers&display=swap" rel="stylesheet">
188
  <style>
189
  body { background: #121212; color: #eee; font-family: sans-serif; margin: 0; text-align: center; }
190
 
191
+ /* UPLOAD SCREEN */
192
  #upload-view { padding: 50px; }
193
  .box { background: #1e1e1e; display: inline-block; padding: 40px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); }
194
+ button { background: #e74c3c; border: none; padding: 10px 20px; font-weight: bold; cursor: pointer; border-radius: 4px; font-size: 16px; margin-top: 10px; color: white; }
195
+ button:hover { background: #c0392b; }
196
  input { padding: 10px; margin: 5px; border-radius: 4px; border: 1px solid #555; background: #333; color: white; }
197
 
198
+ /* EDITOR SCREEN */
199
  #editor-view { display: none; padding: 20px; }
200
+ .comic-container { display: flex; flex-wrap: wrap; justify-content: center; gap: 40px; margin-top: 20px; padding-bottom: 80px; }
201
 
202
+ /* COMIC PAGE STYLE */
203
  .comic-page {
204
  width: 600px; height: 800px;
205
  background: white;
 
207
  position: relative;
208
  box-shadow: 0 0 20px rgba(0,0,0,0.5);
209
  user-select: none;
210
+ overflow: hidden;
211
  }
212
 
213
  /* 2x2 GRID LOGIC */
214
  .comic-grid {
215
  width: 100%; height: 100%;
216
  position: relative;
217
+ background: #000; /* The "Gap" color */
218
  --x: 50%;
219
  --y: 50%;
220
+ --gap: 4px; /* Thickness of divider */
221
  }
222
 
223
  .panel {
224
  position: absolute;
225
  overflow: hidden;
226
  background: #333;
 
227
  box-sizing: border-box;
228
+ border: 2px solid #000;
229
  }
230
 
231
  .panel img {
232
  width: 100%; height: 100%;
233
  object-fit: cover;
234
+ pointer-events: auto;
235
  }
236
 
237
+ /* DYNAMIC PANEL SIZING */
238
  /* Top Left */
239
+ .panel:nth-child(1) { left: 0; top: 0; width: calc(var(--x) - var(--gap)/2); height: calc(var(--y) - var(--gap)/2); }
240
  /* Top Right */
241
+ .panel:nth-child(2) { left: calc(var(--x) + var(--gap)/2); top: 0; width: calc(100% - var(--x) - var(--gap)/2); height: calc(var(--y) - var(--gap)/2); }
242
  /* Bottom Left */
243
+ .panel:nth-child(3) { left: 0; top: calc(var(--y) + var(--gap)/2); width: calc(var(--x) - var(--gap)/2); height: calc(100% - var(--y) - var(--gap)/2); }
244
  /* Bottom Right */
245
+ .panel:nth-child(4) { left: calc(var(--x) + var(--gap)/2); top: calc(var(--y) + var(--gap)/2); width: calc(100% - var(--x) - var(--gap)/2); height: calc(100% - var(--y) - var(--gap)/2); }
246
 
247
+ /* CENTRAL HANDLE (RED DOT) */
248
  .grid-handle {
249
  position: absolute;
250
+ width: 24px; height: 24px;
251
  background: #e74c3c;
252
  border: 3px solid white;
253
  border-radius: 50%;
 
258
  box-shadow: 0 4px 10px rgba(0,0,0,0.5);
259
  pointer-events: auto;
260
  }
261
+ .grid-handle:hover { transform: translate(-50%, -50%) scale(1.2); }
262
 
263
  /* BUBBLES */
264
  .bubble {
 
285
  background: #333; padding: 10px 20px; border-radius: 50px;
286
  display: flex; gap: 15px; z-index: 1000; box-shadow: 0 5px 20px rgba(0,0,0,0.6);
287
  }
288
+ .toolbar button { margin-top: 0; font-size: 14px; }
289
  </style>
290
  </head>
291
  <body>
 
297
  <input type="file" id="fileIn" accept="video/*"><br>
298
  <label>Pages:</label> <input type="number" id="pgCount" value="1" min="1" max="5" style="width:50px;">
299
  <br>
300
+ <button onclick="startUpload()">🚀 Generate</button>
301
  <p id="status" style="color: #bbb; margin-top:10px;"></p>
302
  </div>
303
  </div>
304
 
305
  <div id="editor-view">
306
+ <h2>Drag the <span style="color:#e74c3c">Red Dot</span> to resize panels!</h2>
307
  <div class="comic-container" id="container"></div>
308
 
309
  <div class="toolbar">
310
  <button onclick="addBubble()">💬 Add Text</button>
311
  <button onclick="downloadAll()">💾 Download</button>
312
+ <button style="background:#555" onclick="location.reload()">↺ New</button>
313
  </div>
314
  </div>
315
 
316
  <script>
317
  let sid = 'S' + Date.now();
318
+ let dragItem = null;
319
  let activeEl = null;
320
 
321
  async function startUpload() {
 
326
  fd.append('file', f);
327
  fd.append('pages', document.getElementById('pgCount').value);
328
 
329
+ document.getElementById('status').innerText = "Uploading & Processing...";
330
 
331
  let r = await fetch(`/upload?sid=${sid}`, {method:'POST', body:fd});
332
  if(r.ok) monitorStatus();
 
360
 
361
  let grid = document.createElement('div');
362
  grid.className = 'comic-grid';
 
363
  grid.style.setProperty('--x', pg.splitX || '50%');
364
  grid.style.setProperty('--y', pg.splitY || '50%');
365
 
 
405
  };
406
 
407
  if(!parent) parent = document.querySelector('.comic-grid');
408
+ if(parent) parent.appendChild(b);
409
  }
410
 
411
  window.addBubble = () => createBubble("New Text");
 
441
  window.downloadAll = async () => {
442
  let pgs = document.querySelectorAll('.comic-page');
443
  for(let i=0; i<pgs.length; i++) {
 
444
  let handles = pgs[i].querySelectorAll('.grid-handle');
445
  handles.forEach(h => h.style.display = 'none');
446
 
 
497
  return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'output'), filename)
498
 
499
  if __name__ == '__main__':
500
+ # 🚀 EXPLICIT WARMUP CALL TO REGISTER FUNCTION
501
+ try:
502
+ gpu_warmup()
503
+ except Exception as e:
504
+ print(f"⚠️ Warmup ignored (Normal if not on GPU yet): {e}")
505
+
506
  app.run(host='0.0.0.0', port=7860)