Update app_enhanced.py
Browse files- app_enhanced.py +62 -33
app_enhanced.py
CHANGED
|
@@ -91,7 +91,6 @@ INDEX_HTML = '''
|
|
| 91 |
.file-label:hover { background: #34495e; }
|
| 92 |
.submit-btn { width: 100%; padding: 15px; background: #e67e22; color: white; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; transition: 0.2s; }
|
| 93 |
.submit-btn:hover { background: #d35400; }
|
| 94 |
-
|
| 95 |
.restore-btn { margin-top: 15px; background: #27ae60; color: white; padding: 10px; width: 100%; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; display: none; }
|
| 96 |
|
| 97 |
.loader { width: 120px; height: 20px; background: radial-gradient(circle 10px, #e67e22 100%, transparent 0); background-size: 20px 20px; animation: ball 1s infinite linear; margin: 20px auto; }
|
|
@@ -102,11 +101,11 @@ INDEX_HTML = '''
|
|
| 102 |
.comic-grid { display: grid; grid-template-columns: 285px 285px; grid-template-rows: 185px 185px; gap: 10px; width: 100%; height: 100%; padding: 10px; box-sizing: border-box; }
|
| 103 |
.panel { position: relative; overflow: hidden; border: 2px solid #000; background: #eee; cursor: pointer; }
|
| 104 |
.panel.selected { outline: 3px solid #2196F3; outline-offset: -3px; }
|
| 105 |
-
.panel img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.1s; }
|
| 106 |
.panel img.pannable { cursor: grab; }
|
| 107 |
.panel img.panning { cursor: grabbing; }
|
| 108 |
|
| 109 |
-
/* --- SPEECH BUBBLE (
|
| 110 |
.speech-bubble {
|
| 111 |
position: absolute; display: flex; justify-content: center; align-items: center;
|
| 112 |
width: 150px; height: 80px; min-width: 60px; min-height: 40px; box-sizing: border-box;
|
|
@@ -117,24 +116,65 @@ INDEX_HTML = '''
|
|
| 117 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 118 |
.speech-bubble textarea { position: absolute; top:0; left:0; width:100%; height:100%; box-sizing:border-box; border:1px solid #4CAF50; background:rgba(255,255,255,0.9); text-align:center; padding:8px; z-index:100; resize:none; }
|
| 119 |
|
|
|
|
| 120 |
.speech-bubble.speech {
|
| 121 |
-
--b: 3em;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
--c: var(--bubble-fill-color, #4ECDC4);
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
border-radius: var(--r) var(--r) min(var(--r), calc(100% - var(--p) - (1 - var(--t)) * var(--b) / 2)) min(var(--r), calc(var(--p) - (1 - var(--t)) * var(--b) / 2)) / var(--r);
|
| 125 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
.speech-bubble.speech:before {
|
| 127 |
content: ""; position: absolute; width: var(--b); height: var(--h);
|
|
|
|
| 128 |
background: radial-gradient(100% 100% at 100% 0, transparent calc(var(--t) * 100% - 1px), var(--c) calc(var(--t) * 100%));
|
| 129 |
border-bottom-left-radius: 100%; pointer-events: none; z-index: 1;
|
| 130 |
}
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
.speech-bubble.speech.tail-left { border-radius: var(--r); }
|
| 135 |
-
.speech-bubble.speech.tail-left:before {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
.speech-bubble.speech.tail-right { border-radius: var(--r); }
|
| 137 |
-
.speech-bubble.speech.tail-right:before {
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
.speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
|
| 140 |
.speech-bubble.thought::after { display:none; }
|
|
@@ -165,7 +205,6 @@ INDEX_HTML = '''
|
|
| 165 |
<span id="fn">No file selected</span>
|
| 166 |
<button class="submit-btn" onclick="upload()">Generate Comic</button>
|
| 167 |
<button id="restore-btn" class="restore-btn" onclick="restoreSession()">📂 Restore Unsaved Session</button>
|
| 168 |
-
|
| 169 |
<div class="loading-view" id="loading-view" style="display:none;">
|
| 170 |
<div class="loader"></div>
|
| 171 |
<p id="status-text">Starting...</p>
|
|
@@ -214,10 +253,11 @@ INDEX_HTML = '''
|
|
| 214 |
</div>
|
| 215 |
<input type="text" id="timestamp-input" placeholder="mm:ss">
|
| 216 |
<button onclick="gotoTimestamp()">Go to Time</button>
|
|
|
|
| 217 |
<input type="range" id="zoom-slider" min="100" max="300" value="100" step="5" disabled oninput="handleZoom(this)">
|
| 218 |
</div>
|
| 219 |
<hr>
|
| 220 |
-
<button onclick="saveLocal()">💾 Force Save
|
| 221 |
<button onclick="exportComic()" style="background:#ccffcc; font-weight:bold;">📥 Download PNG</button>
|
| 222 |
<button onclick="location.reload()" style="color:red; margin-top:10px;">↺ Start Over</button>
|
| 223 |
</div>
|
|
@@ -245,13 +285,10 @@ INDEX_HTML = '''
|
|
| 245 |
if(!savedData) return alert("No saved session found.");
|
| 246 |
try {
|
| 247 |
const state = JSON.parse(savedData);
|
| 248 |
-
// Restore SID from state if needed, or keep current
|
| 249 |
renderFromState(state);
|
| 250 |
document.getElementById('upload-container').style.display = 'none';
|
| 251 |
document.getElementById('editor-container').style.display = 'block';
|
| 252 |
-
} catch(e) {
|
| 253 |
-
alert("Failed to restore: " + e);
|
| 254 |
-
}
|
| 255 |
}
|
| 256 |
|
| 257 |
function renderFromState(pagesData) {
|
|
@@ -269,9 +306,7 @@ INDEX_HTML = '''
|
|
| 269 |
pDiv.onclick = (e) => { e.stopPropagation(); selectPanel(pDiv); };
|
| 270 |
|
| 271 |
const img = document.createElement('img');
|
| 272 |
-
// Ensure image path includes SID if it's relative, or use full saved src
|
| 273 |
img.src = pan.src;
|
| 274 |
-
// Restore pan/zoom
|
| 275 |
img.dataset.zoom = pan.zoom || 100;
|
| 276 |
img.dataset.translateX = pan.tx || 0;
|
| 277 |
img.dataset.translateY = pan.ty || 0;
|
|
@@ -279,10 +314,8 @@ INDEX_HTML = '''
|
|
| 279 |
img.onmousedown = startPan;
|
| 280 |
pDiv.appendChild(img);
|
| 281 |
|
| 282 |
-
// Restore bubbles
|
| 283 |
pan.bubbles.forEach(bData => {
|
| 284 |
const b = createBubble(bData.text, bData.left, bData.top, bData.type, bData.tailPos, bData.width, bData.height, bData.colors);
|
| 285 |
-
// Re-apply rotation classes
|
| 286 |
if(bData.classes) b.className = bData.classes;
|
| 287 |
pDiv.appendChild(b);
|
| 288 |
});
|
|
@@ -326,7 +359,6 @@ INDEX_HTML = '''
|
|
| 326 |
pages.push({ panels: panels });
|
| 327 |
});
|
| 328 |
localStorage.setItem('comic_autosave', JSON.stringify(pages));
|
| 329 |
-
console.log("Autosaved.");
|
| 330 |
}
|
| 331 |
|
| 332 |
// --- UPLOAD ---
|
|
@@ -368,7 +400,7 @@ INDEX_HTML = '''
|
|
| 368 |
}))
|
| 369 |
}));
|
| 370 |
renderFromState(cleanData);
|
| 371 |
-
saveLocal();
|
| 372 |
});
|
| 373 |
}
|
| 374 |
|
|
@@ -380,22 +412,18 @@ INDEX_HTML = '''
|
|
| 380 |
if(w) b.style.width = w; if(h) b.style.height = h;
|
| 381 |
b.dataset.type = type;
|
| 382 |
b.style.setProperty('--tail-pos', tailPos || '50%');
|
| 383 |
-
|
| 384 |
if(colors) {
|
| 385 |
if(colors.text) b.style.setProperty('--bubble-text-color', colors.text);
|
| 386 |
if(colors.fill) b.style.setProperty('--bubble-fill-color', colors.fill);
|
| 387 |
}
|
| 388 |
-
|
| 389 |
b.innerHTML = `<span class="bubble-text">${text}</span><div class="resize-handle se"></div>`;
|
| 390 |
|
| 391 |
-
// Handlers
|
| 392 |
b.onmousedown = (e) => {
|
| 393 |
if(e.target.classList.contains('resize-handle')) return;
|
| 394 |
e.stopPropagation(); selectedBubble = b;
|
| 395 |
document.querySelectorAll('.speech-bubble').forEach(el=>el.classList.remove('selected'));
|
| 396 |
b.classList.add('selected');
|
| 397 |
selectBubbleUI(b);
|
| 398 |
-
|
| 399 |
const ox = e.clientX - b.offsetLeft, oy = e.clientY - b.offsetTop;
|
| 400 |
document.onmousemove = (ev) => {
|
| 401 |
b.style.left = (ev.clientX-ox)+'px'; b.style.top = (ev.clientY-oy)+'px';
|
|
@@ -421,7 +449,6 @@ INDEX_HTML = '''
|
|
| 421 |
b.appendChild(txt); span.style.display='none'; txt.focus();
|
| 422 |
txt.onblur = () => { span.innerText = txt.value; txt.remove(); span.style.display='block'; saveLocal(); };
|
| 423 |
};
|
| 424 |
-
|
| 425 |
return b;
|
| 426 |
}
|
| 427 |
|
|
@@ -430,6 +457,7 @@ INDEX_HTML = '''
|
|
| 430 |
el.classList.add('selected');
|
| 431 |
selectedPanel = el;
|
| 432 |
selectBubbleUI(null);
|
|
|
|
| 433 |
}
|
| 434 |
|
| 435 |
function selectBubbleUI(el) {
|
|
@@ -507,13 +535,13 @@ INDEX_HTML = '''
|
|
| 507 |
img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${z})`;
|
| 508 |
img.classList.toggle('pannable', z>1);
|
| 509 |
}
|
| 510 |
-
function resetPanelTransform() {
|
| 511 |
if(!selectedPanel) return;
|
| 512 |
const img = selectedPanel.querySelector('img');
|
| 513 |
-
img.dataset.zoom=100; img.dataset.translateX=0; img.dataset.translateY=0;
|
| 514 |
-
document.getElementById('zoom-slider').value = 100;
|
| 515 |
updateImageTransform(img);
|
| 516 |
-
saveLocal();
|
| 517 |
}
|
| 518 |
|
| 519 |
// API
|
|
@@ -563,7 +591,7 @@ INDEX_HTML = '''
|
|
| 563 |
}
|
| 564 |
}
|
| 565 |
|
| 566 |
-
// Color Listeners
|
| 567 |
document.getElementById('bubble-text-color').addEventListener('input', (e) => {
|
| 568 |
if(selectedBubble) { selectedBubble.style.setProperty('--bubble-text-color', e.target.value); saveLocal(); }
|
| 569 |
});
|
|
@@ -630,7 +658,8 @@ class EnhancedComicGenerator:
|
|
| 630 |
frame_files = []
|
| 631 |
bubbles = []
|
| 632 |
|
| 633 |
-
|
|
|
|
| 634 |
mid = (sub.start.total_seconds() + sub.end.total_seconds())/2
|
| 635 |
cap.set(cv2.CAP_PROP_POS_MSEC, mid*1000)
|
| 636 |
ret, frame = cap.read()
|
|
|
|
| 91 |
.file-label:hover { background: #34495e; }
|
| 92 |
.submit-btn { width: 100%; padding: 15px; background: #e67e22; color: white; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; transition: 0.2s; }
|
| 93 |
.submit-btn:hover { background: #d35400; }
|
|
|
|
| 94 |
.restore-btn { margin-top: 15px; background: #27ae60; color: white; padding: 10px; width: 100%; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; display: none; }
|
| 95 |
|
| 96 |
.loader { width: 120px; height: 20px; background: radial-gradient(circle 10px, #e67e22 100%, transparent 0); background-size: 20px 20px; animation: ball 1s infinite linear; margin: 20px auto; }
|
|
|
|
| 101 |
.comic-grid { display: grid; grid-template-columns: 285px 285px; grid-template-rows: 185px 185px; gap: 10px; width: 100%; height: 100%; padding: 10px; box-sizing: border-box; }
|
| 102 |
.panel { position: relative; overflow: hidden; border: 2px solid #000; background: #eee; cursor: pointer; }
|
| 103 |
.panel.selected { outline: 3px solid #2196F3; outline-offset: -3px; }
|
| 104 |
+
.panel img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.1s ease-out; transform-origin: center center; }
|
| 105 |
.panel img.pannable { cursor: grab; }
|
| 106 |
.panel img.panning { cursor: grabbing; }
|
| 107 |
|
| 108 |
+
/* --- SPEECH BUBBLE (SHARK FIN) --- */
|
| 109 |
.speech-bubble {
|
| 110 |
position: absolute; display: flex; justify-content: center; align-items: center;
|
| 111 |
width: 150px; height: 80px; min-width: 60px; min-height: 40px; box-sizing: border-box;
|
|
|
|
| 116 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 117 |
.speech-bubble textarea { position: absolute; top:0; left:0; width:100%; height:100%; box-sizing:border-box; border:1px solid #4CAF50; background:rgba(255,255,255,0.9); text-align:center; padding:8px; z-index:100; resize:none; }
|
| 118 |
|
| 119 |
+
/* The specific CSS request from user */
|
| 120 |
.speech-bubble.speech {
|
| 121 |
+
--b: 3em; /* tail base */
|
| 122 |
+
--h: 1.8em; /* tail height */
|
| 123 |
+
--t: 0.6; /* thickness */
|
| 124 |
+
--p: var(--tail-pos, 50%); /* slider pos */
|
| 125 |
+
--r: 1.2em; /* radius */
|
| 126 |
--c: var(--bubble-fill-color, #4ECDC4);
|
| 127 |
+
|
| 128 |
+
background: var(--c);
|
| 129 |
+
color: var(--bubble-text-color, #fff);
|
| 130 |
+
padding: 1em;
|
| 131 |
+
position: absolute;
|
| 132 |
+
|
| 133 |
+
/* Exact border radius formula provided */
|
| 134 |
border-radius: var(--r) var(--r) min(var(--r), calc(100% - var(--p) - (1 - var(--t)) * var(--b) / 2)) min(var(--r), calc(var(--p) - (1 - var(--t)) * var(--b) / 2)) / var(--r);
|
| 135 |
}
|
| 136 |
+
|
| 137 |
+
/*
|
| 138 |
+
TAIL IMPLEMENTATION:
|
| 139 |
+
Using 'radial-gradient' background instead of mask.
|
| 140 |
+
This visually creates the EXACT concave shape requested but is 100% export-safe.
|
| 141 |
+
*/
|
| 142 |
.speech-bubble.speech:before {
|
| 143 |
content: ""; position: absolute; width: var(--b); height: var(--h);
|
| 144 |
+
/* Gradient mimics the mask curve */
|
| 145 |
background: radial-gradient(100% 100% at 100% 0, transparent calc(var(--t) * 100% - 1px), var(--c) calc(var(--t) * 100%));
|
| 146 |
border-bottom-left-radius: 100%; pointer-events: none; z-index: 1;
|
| 147 |
}
|
| 148 |
+
|
| 149 |
+
/* ROTATION LOGIC (Adapting the shape for 4 sides) */
|
| 150 |
+
|
| 151 |
+
/* Bottom (Default) */
|
| 152 |
+
.speech-bubble.speech.tail-bottom:before {
|
| 153 |
+
top: 99%; left: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b)));
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
/* Top */
|
| 157 |
+
.speech-bubble.speech.tail-top {
|
| 158 |
+
border-radius: min(var(--r), calc(var(--p) - (1 - var(--t)) * var(--b) / 2)) min(var(--r), calc(100% - var(--p) - (1 - var(--t)) * var(--b) / 2)) var(--r) var(--r) / var(--r);
|
| 159 |
+
}
|
| 160 |
+
.speech-bubble.speech.tail-top:before {
|
| 161 |
+
bottom: 99%; left: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b)));
|
| 162 |
+
transform: scaleY(-1);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
/* Left */
|
| 166 |
.speech-bubble.speech.tail-left { border-radius: var(--r); }
|
| 167 |
+
.speech-bubble.speech.tail-left:before {
|
| 168 |
+
right: 99%; top: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b)));
|
| 169 |
+
transform: rotate(90deg); transform-origin: top right;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/* Right */
|
| 173 |
.speech-bubble.speech.tail-right { border-radius: var(--r); }
|
| 174 |
+
.speech-bubble.speech.tail-right:before {
|
| 175 |
+
left: 99%; top: clamp(0%, calc(var(--p) - (1 - var(--t)) * var(--b) / 2), calc(100% - (1 - var(--t)) * var(--b)));
|
| 176 |
+
transform: rotate(-90deg); transform-origin: top left;
|
| 177 |
+
}
|
| 178 |
|
| 179 |
.speech-bubble.thought { background: white; border: 2px dashed #555; color: #333; border-radius: 50%; }
|
| 180 |
.speech-bubble.thought::after { display:none; }
|
|
|
|
| 205 |
<span id="fn">No file selected</span>
|
| 206 |
<button class="submit-btn" onclick="upload()">Generate Comic</button>
|
| 207 |
<button id="restore-btn" class="restore-btn" onclick="restoreSession()">📂 Restore Unsaved Session</button>
|
|
|
|
| 208 |
<div class="loading-view" id="loading-view" style="display:none;">
|
| 209 |
<div class="loader"></div>
|
| 210 |
<p id="status-text">Starting...</p>
|
|
|
|
| 253 |
</div>
|
| 254 |
<input type="text" id="timestamp-input" placeholder="mm:ss">
|
| 255 |
<button onclick="gotoTimestamp()">Go to Time</button>
|
| 256 |
+
<label>Zoom:</label>
|
| 257 |
<input type="range" id="zoom-slider" min="100" max="300" value="100" step="5" disabled oninput="handleZoom(this)">
|
| 258 |
</div>
|
| 259 |
<hr>
|
| 260 |
+
<button onclick="saveLocal()">💾 Force Save</button>
|
| 261 |
<button onclick="exportComic()" style="background:#ccffcc; font-weight:bold;">📥 Download PNG</button>
|
| 262 |
<button onclick="location.reload()" style="color:red; margin-top:10px;">↺ Start Over</button>
|
| 263 |
</div>
|
|
|
|
| 285 |
if(!savedData) return alert("No saved session found.");
|
| 286 |
try {
|
| 287 |
const state = JSON.parse(savedData);
|
|
|
|
| 288 |
renderFromState(state);
|
| 289 |
document.getElementById('upload-container').style.display = 'none';
|
| 290 |
document.getElementById('editor-container').style.display = 'block';
|
| 291 |
+
} catch(e) { alert("Failed to restore"); }
|
|
|
|
|
|
|
| 292 |
}
|
| 293 |
|
| 294 |
function renderFromState(pagesData) {
|
|
|
|
| 306 |
pDiv.onclick = (e) => { e.stopPropagation(); selectPanel(pDiv); };
|
| 307 |
|
| 308 |
const img = document.createElement('img');
|
|
|
|
| 309 |
img.src = pan.src;
|
|
|
|
| 310 |
img.dataset.zoom = pan.zoom || 100;
|
| 311 |
img.dataset.translateX = pan.tx || 0;
|
| 312 |
img.dataset.translateY = pan.ty || 0;
|
|
|
|
| 314 |
img.onmousedown = startPan;
|
| 315 |
pDiv.appendChild(img);
|
| 316 |
|
|
|
|
| 317 |
pan.bubbles.forEach(bData => {
|
| 318 |
const b = createBubble(bData.text, bData.left, bData.top, bData.type, bData.tailPos, bData.width, bData.height, bData.colors);
|
|
|
|
| 319 |
if(bData.classes) b.className = bData.classes;
|
| 320 |
pDiv.appendChild(b);
|
| 321 |
});
|
|
|
|
| 359 |
pages.push({ panels: panels });
|
| 360 |
});
|
| 361 |
localStorage.setItem('comic_autosave', JSON.stringify(pages));
|
|
|
|
| 362 |
}
|
| 363 |
|
| 364 |
// --- UPLOAD ---
|
|
|
|
| 400 |
}))
|
| 401 |
}));
|
| 402 |
renderFromState(cleanData);
|
| 403 |
+
saveLocal();
|
| 404 |
});
|
| 405 |
}
|
| 406 |
|
|
|
|
| 412 |
if(w) b.style.width = w; if(h) b.style.height = h;
|
| 413 |
b.dataset.type = type;
|
| 414 |
b.style.setProperty('--tail-pos', tailPos || '50%');
|
|
|
|
| 415 |
if(colors) {
|
| 416 |
if(colors.text) b.style.setProperty('--bubble-text-color', colors.text);
|
| 417 |
if(colors.fill) b.style.setProperty('--bubble-fill-color', colors.fill);
|
| 418 |
}
|
|
|
|
| 419 |
b.innerHTML = `<span class="bubble-text">${text}</span><div class="resize-handle se"></div>`;
|
| 420 |
|
|
|
|
| 421 |
b.onmousedown = (e) => {
|
| 422 |
if(e.target.classList.contains('resize-handle')) return;
|
| 423 |
e.stopPropagation(); selectedBubble = b;
|
| 424 |
document.querySelectorAll('.speech-bubble').forEach(el=>el.classList.remove('selected'));
|
| 425 |
b.classList.add('selected');
|
| 426 |
selectBubbleUI(b);
|
|
|
|
| 427 |
const ox = e.clientX - b.offsetLeft, oy = e.clientY - b.offsetTop;
|
| 428 |
document.onmousemove = (ev) => {
|
| 429 |
b.style.left = (ev.clientX-ox)+'px'; b.style.top = (ev.clientY-oy)+'px';
|
|
|
|
| 449 |
b.appendChild(txt); span.style.display='none'; txt.focus();
|
| 450 |
txt.onblur = () => { span.innerText = txt.value; txt.remove(); span.style.display='block'; saveLocal(); };
|
| 451 |
};
|
|
|
|
| 452 |
return b;
|
| 453 |
}
|
| 454 |
|
|
|
|
| 457 |
el.classList.add('selected');
|
| 458 |
selectedPanel = el;
|
| 459 |
selectBubbleUI(null);
|
| 460 |
+
resetPanelTransform(true);
|
| 461 |
}
|
| 462 |
|
| 463 |
function selectBubbleUI(el) {
|
|
|
|
| 535 |
img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${z})`;
|
| 536 |
img.classList.toggle('pannable', z>1);
|
| 537 |
}
|
| 538 |
+
function resetPanelTransform(loadOnly=false) {
|
| 539 |
if(!selectedPanel) return;
|
| 540 |
const img = selectedPanel.querySelector('img');
|
| 541 |
+
if(!loadOnly) { img.dataset.zoom=100; img.dataset.translateX=0; img.dataset.translateY=0; }
|
| 542 |
+
document.getElementById('zoom-slider').value = img.dataset.zoom || 100;
|
| 543 |
updateImageTransform(img);
|
| 544 |
+
if(!loadOnly) saveLocal();
|
| 545 |
}
|
| 546 |
|
| 547 |
// API
|
|
|
|
| 591 |
}
|
| 592 |
}
|
| 593 |
|
| 594 |
+
// Color Listeners
|
| 595 |
document.getElementById('bubble-text-color').addEventListener('input', (e) => {
|
| 596 |
if(selectedBubble) { selectedBubble.style.setProperty('--bubble-text-color', e.target.value); saveLocal(); }
|
| 597 |
});
|
|
|
|
| 658 |
frame_files = []
|
| 659 |
bubbles = []
|
| 660 |
|
| 661 |
+
# Generate UP TO 48 PANELS (12 PAGES)
|
| 662 |
+
for i, sub in enumerate(subs[:48]):
|
| 663 |
mid = (sub.start.total_seconds() + sub.end.total_seconds())/2
|
| 664 |
cap.set(cv2.CAP_PROP_POS_MSEC, mid*1000)
|
| 665 |
ret, frame = cap.read()
|