tester343 commited on
Commit
21b24ab
·
verified ·
1 Parent(s): aad9bc7

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +60 -76
app_enhanced.py CHANGED
@@ -47,14 +47,12 @@ def create_placeholder_image(text, filename, output_dir):
47
 
48
  @spaces.GPU(duration=120)
49
  def generate_comic_gpu(video_path, frames_dir, target_pages):
50
- """Extracts frames WITHOUT cropping them (Preserves content)."""
51
  if os.path.exists(frames_dir): shutil.rmtree(frames_dir)
52
  os.makedirs(frames_dir, exist_ok=True)
53
 
54
  cap = cv2.VideoCapture(video_path)
55
- if not cap.isOpened():
56
- print("❌ Video load failed.")
57
- return []
58
 
59
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
60
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
@@ -70,22 +68,9 @@ def generate_comic_gpu(video_path, frames_dir, target_pages):
70
  cap.set(cv2.CAP_PROP_POS_MSEC, t * 1000)
71
  ret, frame = cap.read()
72
  fname = f"frame_{i:04d}.png"
73
-
74
  if ret and frame is not None:
75
- # FIX: Do NOT crop to square. Just resize to reasonable resolution.
76
- # Maintains visual data.
77
- # We resize to a standard width (e.g. 800) but keep aspect ratio relative
78
- # or just force a high res fit.
79
-
80
- # Let's resize to 800xWidth to ensure it fits nicely in panels
81
- # but we won't cut pixels off.
82
- h, w = frame.shape[:2]
83
-
84
- # Simple Resize (Distortion is usually okay for comics,
85
- # but keeping aspect ratio is better. Let's just standard resize
86
- # so the frontend Object-Fit handles the rest).
87
  frame = cv2.resize(frame, (800, 1000))
88
-
89
  cv2.imwrite(os.path.join(frames_dir, fname), frame)
90
  frame_files_ordered.append(fname)
91
  else:
@@ -112,7 +97,7 @@ def generate_comic_gpu(video_path, frames_dir, target_pages):
112
  pg_panels = [{'image': f} for f in p_frames]
113
  pg_bubbles = []
114
  if i == 0:
115
- pg_bubbles.append({'dialog': "Red: Center\nBlue: Top Tilt\nGreen: Bottom Tilt", 'x': '50%', 'y': '50%'})
116
 
117
  pages_data.append({
118
  'panels': pg_panels,
@@ -156,7 +141,7 @@ INDEX_HTML = '''
156
  <head>
157
  <meta charset="UTF-8">
158
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
159
- <title>Ultimate Comic Editor</title>
160
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
161
  <link href="https://fonts.googleapis.com/css2?family=Bangers&display=swap" rel="stylesheet">
162
  <style>
@@ -184,13 +169,19 @@ INDEX_HTML = '''
184
  width: 100%; height: 100%;
185
  position: relative;
186
  background: #000;
187
- /* Coordinate System */
188
- --x: 50%; /* Center X */
189
- --y: 50%; /* Center Y */
190
- --xt: 50%; /* Top Edge X */
191
- --xb: 50%; /* Bottom Edge X */
192
 
193
- --gap: 3px; /* Gap thickness */
 
 
 
 
 
 
 
 
 
 
 
194
  }
195
 
196
  .panel {
@@ -200,50 +191,54 @@ INDEX_HTML = '''
200
 
201
  .panel img {
202
  width: 100%; height: 100%;
203
- object-fit: cover; /* Ensures image fills panel. User zooms/pans to adjust. */
204
  transform-origin: center;
205
  cursor: grab;
206
  }
207
  .panel img:active { cursor: grabbing; }
208
 
209
  /* === DYNAMIC CLIP PATHS === */
210
- /* Top Left */
 
211
  .panel:nth-child(1) {
212
  clip-path: polygon(
213
  0 0,
214
- calc(var(--xt) - var(--gap)) 0,
215
- calc(var(--x) - var(--gap)) calc(var(--y) - var(--gap)),
216
  0 calc(var(--y) - var(--gap))
217
  );
218
  z-index: 1;
219
  }
220
- /* Top Right */
 
221
  .panel:nth-child(2) {
222
  clip-path: polygon(
223
- calc(var(--xt) + var(--gap)) 0,
224
  100% 0,
225
  100% calc(var(--y) - var(--gap)),
226
- calc(var(--x) + var(--gap)) calc(var(--y) - var(--gap))
227
  );
228
  z-index: 1;
229
  }
230
- /* Bottom Left */
 
231
  .panel:nth-child(3) {
232
  clip-path: polygon(
233
  0 calc(var(--y) + var(--gap)),
234
- calc(var(--x) - var(--gap)) calc(var(--y) + var(--gap)),
235
- calc(var(--xb) - var(--gap)) 100%,
236
  0 100%
237
  );
238
  z-index: 1;
239
  }
240
- /* Bottom Right */
 
241
  .panel:nth-child(4) {
242
  clip-path: polygon(
243
- calc(var(--x) + var(--gap)) calc(var(--y) + var(--gap)),
244
  100% calc(var(--y) + var(--gap)),
245
  100% 100%,
246
- calc(var(--xb) + var(--gap)) 100%
247
  );
248
  z-index: 1;
249
  }
@@ -253,14 +248,18 @@ INDEX_HTML = '''
253
  position: absolute; width: 22px; height: 22px;
254
  border: 2px solid white; border-radius: 50%;
255
  transform: translate(-50%, -50%);
256
- z-index: 999; cursor: pointer;
257
  box-shadow: 0 2px 5px rgba(0,0,0,0.8);
258
  }
259
  .handle:hover { transform: translate(-50%, -50%) scale(1.2); }
260
 
261
- .h-center { background: #e74c3c; left: var(--x); top: var(--y); cursor: move; }
262
- .h-top { background: #3498db; left: var(--xt); top: 1%; cursor: ew-resize; }
263
- .h-bottom { background: #2ecc71; left: var(--xb); top: 99%; cursor: ew-resize; }
 
 
 
 
264
 
265
  /* BUBBLES */
266
  .bubble {
@@ -284,8 +283,8 @@ INDEX_HTML = '''
284
 
285
  <div id="upload-view">
286
  <div class="box">
287
- <h1>🎞️ Full Control Comic Maker</h1>
288
- <p>Independent Upper/Lower Tilt + Full Image View</p>
289
  <input type="file" id="fileIn" accept="video/*"><br><br>
290
  <label>Pages:</label> <input type="number" id="pgCount" value="1" min="1" max="5" style="width:50px;">
291
  <br><br>
@@ -363,15 +362,13 @@ INDEX_HTML = '''
363
  grid.appendChild(div);
364
  });
365
 
366
- // Handles
367
- // 1. Center (Red)
368
- let hc = createHandle('h-center', grid, 'center');
369
- // 2. Top (Blue)
370
- let ht = createHandle('h-top', grid, 'top');
371
- // 3. Bottom (Green)
372
- let hb = createHandle('h-bottom', grid, 'bottom');
373
-
374
- grid.append(hc, ht, hb);
375
 
376
  if(pg.bubbles) pg.bubbles.forEach(b => createBubble(b.dialog, grid));
377
  pDiv.appendChild(grid);
@@ -379,12 +376,13 @@ INDEX_HTML = '''
379
  });
380
  }
381
 
382
- function createHandle(cls, grid, type) {
383
  let h = document.createElement('div');
384
  h.className = `handle ${cls}`;
385
  h.onmousedown = (e) => {
386
  e.stopPropagation();
387
- dragType = type; activeObj = grid;
 
388
  };
389
  return h;
390
  }
@@ -421,27 +419,13 @@ INDEX_HTML = '''
421
  document.addEventListener('mousemove', (e) => {
422
  if(!dragType) return;
423
 
424
- // 1. Center (Moves Intersection)
425
- if(dragType === 'center') {
426
- let rect = activeObj.getBoundingClientRect();
427
- let x = (e.clientX - rect.left) / rect.width * 100;
428
- let y = (e.clientY - rect.top) / rect.height * 100;
429
- activeObj.style.setProperty('--x', clamp(x)+'%');
430
- activeObj.style.setProperty('--y', clamp(y)+'%');
431
- }
432
- // 2. Top Tilt (Moves Top Edge X)
433
- else if(dragType === 'top') {
434
- let rect = activeObj.getBoundingClientRect();
435
- let x = (e.clientX - rect.left) / rect.width * 100;
436
- activeObj.style.setProperty('--xt', clamp(x)+'%');
437
- }
438
- // 3. Bottom Tilt (Moves Bottom Edge X)
439
- else if(dragType === 'bottom') {
440
- let rect = activeObj.getBoundingClientRect();
441
  let x = (e.clientX - rect.left) / rect.width * 100;
442
- activeObj.style.setProperty('--xb', clamp(x)+'%');
443
  }
444
- // 4. Pan Image
445
  else if(dragType === 'pan') {
446
  let dx = e.clientX - dragStart.x;
447
  let dy = e.clientY - dragStart.y;
@@ -450,7 +434,7 @@ INDEX_HTML = '''
450
  dragStart = {x: e.clientX, y: e.clientY};
451
  updateImgTransform(activeObj);
452
  }
453
- // 5. Bubble
454
  else if(dragType === 'bubble') {
455
  let rect = activeObj.parentElement.getBoundingClientRect();
456
  activeObj.style.left = (e.clientX - rect.left) + 'px';
 
47
 
48
  @spaces.GPU(duration=120)
49
  def generate_comic_gpu(video_path, frames_dir, target_pages):
50
+ """Extracts frames WITHOUT cropping (Full Content)."""
51
  if os.path.exists(frames_dir): shutil.rmtree(frames_dir)
52
  os.makedirs(frames_dir, exist_ok=True)
53
 
54
  cap = cv2.VideoCapture(video_path)
55
+ if not cap.isOpened(): return []
 
 
56
 
57
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
58
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
 
68
  cap.set(cv2.CAP_PROP_POS_MSEC, t * 1000)
69
  ret, frame = cap.read()
70
  fname = f"frame_{i:04d}.png"
 
71
  if ret and frame is not None:
72
+ # Resize to standard width, keep content
 
 
 
 
 
 
 
 
 
 
 
73
  frame = cv2.resize(frame, (800, 1000))
 
74
  cv2.imwrite(os.path.join(frames_dir, fname), frame)
75
  frame_files_ordered.append(fname)
76
  else:
 
97
  pg_panels = [{'image': f} for f in p_frames]
98
  pg_bubbles = []
99
  if i == 0:
100
+ pg_bubbles.append({'dialog': "Blue Handles = Top Row\nGreen Handles = Bot Row", 'x': '50%', 'y': '50%'})
101
 
102
  pages_data.append({
103
  'panels': pg_panels,
 
141
  <head>
142
  <meta charset="UTF-8">
143
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
144
+ <title>Independent Tilt Comic</title>
145
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
146
  <link href="https://fonts.googleapis.com/css2?family=Bangers&display=swap" rel="stylesheet">
147
  <style>
 
169
  width: 100%; height: 100%;
170
  position: relative;
171
  background: #000;
 
 
 
 
 
172
 
173
+ /* INDEPENDENT VARIABLES */
174
+ --y: 50%; /* Horizontal Split (Fixed) */
175
+
176
+ /* Top Row Line: t1 (top) -> t2 (middle) */
177
+ --t1: 50%;
178
+ --t2: 50%;
179
+
180
+ /* Bottom Row Line: b1 (middle) -> b2 (bottom) */
181
+ --b1: 50%;
182
+ --b2: 50%;
183
+
184
+ --gap: 3px;
185
  }
186
 
187
  .panel {
 
191
 
192
  .panel img {
193
  width: 100%; height: 100%;
194
+ object-fit: cover;
195
  transform-origin: center;
196
  cursor: grab;
197
  }
198
  .panel img:active { cursor: grabbing; }
199
 
200
  /* === DYNAMIC CLIP PATHS === */
201
+
202
+ /* 1. TOP LEFT: (0,0) -> (t1,0) -> (t2, y) -> (0, y) */
203
  .panel:nth-child(1) {
204
  clip-path: polygon(
205
  0 0,
206
+ calc(var(--t1) - var(--gap)) 0,
207
+ calc(var(--t2) - var(--gap)) calc(var(--y) - var(--gap)),
208
  0 calc(var(--y) - var(--gap))
209
  );
210
  z-index: 1;
211
  }
212
+
213
+ /* 2. TOP RIGHT: (t1,0) -> (100,0) -> (100, y) -> (t2, y) */
214
  .panel:nth-child(2) {
215
  clip-path: polygon(
216
+ calc(var(--t1) + var(--gap)) 0,
217
  100% 0,
218
  100% calc(var(--y) - var(--gap)),
219
+ calc(var(--t2) + var(--gap)) calc(var(--y) - var(--gap))
220
  );
221
  z-index: 1;
222
  }
223
+
224
+ /* 3. BOTTOM LEFT: (0, y) -> (b1, y) -> (b2, 100) -> (0, 100) */
225
  .panel:nth-child(3) {
226
  clip-path: polygon(
227
  0 calc(var(--y) + var(--gap)),
228
+ calc(var(--b1) - var(--gap)) calc(var(--y) + var(--gap)),
229
+ calc(var(--b2) - var(--gap)) 100%,
230
  0 100%
231
  );
232
  z-index: 1;
233
  }
234
+
235
+ /* 4. BOTTOM RIGHT: (b1, y) -> (100, y) -> (100, 100) -> (b2, 100) */
236
  .panel:nth-child(4) {
237
  clip-path: polygon(
238
+ calc(var(--b1) + var(--gap)) calc(var(--y) + var(--gap)),
239
  100% calc(var(--y) + var(--gap)),
240
  100% 100%,
241
+ calc(var(--b2) + var(--gap)) 100%
242
  );
243
  z-index: 1;
244
  }
 
248
  position: absolute; width: 22px; height: 22px;
249
  border: 2px solid white; border-radius: 50%;
250
  transform: translate(-50%, -50%);
251
+ z-index: 999; cursor: ew-resize;
252
  box-shadow: 0 2px 5px rgba(0,0,0,0.8);
253
  }
254
  .handle:hover { transform: translate(-50%, -50%) scale(1.2); }
255
 
256
+ /* Blue for Top Row */
257
+ .h-t1 { background: #3498db; left: var(--t1); top: 0%; margin-top: 10px; }
258
+ .h-t2 { background: #3498db; left: var(--t2); top: 50%; margin-top: -12px; }
259
+
260
+ /* Green for Bottom Row */
261
+ .h-b1 { background: #2ecc71; left: var(--b1); top: 50%; margin-top: 12px; }
262
+ .h-b2 { background: #2ecc71; left: var(--b2); top: 100%; margin-top: -10px; }
263
 
264
  /* BUBBLES */
265
  .bubble {
 
283
 
284
  <div id="upload-view">
285
  <div class="box">
286
+ <h1>🎞️ Independent Row Tilt Comic</h1>
287
+ <p>Control Top & Bottom Layouts Separately!</p>
288
  <input type="file" id="fileIn" accept="video/*"><br><br>
289
  <label>Pages:</label> <input type="number" id="pgCount" value="1" min="1" max="5" style="width:50px;">
290
  <br><br>
 
362
  grid.appendChild(div);
363
  });
364
 
365
+ // Handles (Independent)
366
+ // Top Row
367
+ grid.append(createHandle('h-t1', grid, 't1'));
368
+ grid.append(createHandle('h-t2', grid, 't2'));
369
+ // Bot Row
370
+ grid.append(createHandle('h-b1', grid, 'b1'));
371
+ grid.append(createHandle('h-b2', grid, 'b2'));
 
 
372
 
373
  if(pg.bubbles) pg.bubbles.forEach(b => createBubble(b.dialog, grid));
374
  pDiv.appendChild(grid);
 
376
  });
377
  }
378
 
379
+ function createHandle(cls, grid, varName) {
380
  let h = document.createElement('div');
381
  h.className = `handle ${cls}`;
382
  h.onmousedown = (e) => {
383
  e.stopPropagation();
384
+ dragType = 'handle';
385
+ activeObj = { grid: grid, var: varName };
386
  };
387
  return h;
388
  }
 
419
  document.addEventListener('mousemove', (e) => {
420
  if(!dragType) return;
421
 
422
+ // 1. Handles (Horizontal Only)
423
+ if(dragType === 'handle') {
424
+ let rect = activeObj.grid.getBoundingClientRect();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  let x = (e.clientX - rect.left) / rect.width * 100;
426
+ activeObj.grid.style.setProperty(`--${activeObj.var}`, clamp(x)+'%');
427
  }
428
+ // 2. Pan Image
429
  else if(dragType === 'pan') {
430
  let dx = e.clientX - dragStart.x;
431
  let dy = e.clientY - dragStart.y;
 
434
  dragStart = {x: e.clientX, y: e.clientY};
435
  updateImgTransform(activeObj);
436
  }
437
+ // 3. Bubble
438
  else if(dragType === 'bubble') {
439
  let rect = activeObj.parentElement.getBoundingClientRect();
440
  activeObj.style.left = (e.clientX - rect.left) + 'px';