seawolf2357 commited on
Commit
65dd0bc
ยท
verified ยท
1 Parent(s): 8687cdc

Rename app-backup-last.py to app-backup.py

Browse files
app-backup-last.py โ†’ app-backup.py RENAMED
@@ -58,7 +58,7 @@ body{{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#f5f5f7
58
  .prop-group{{margin-bottom:10px}}
59
  .prop-label{{font-size:9px;color:#666;margin-bottom:2px}}
60
  .prop-input{{width:100%;padding:4px;border:1px solid #ddd;border-radius:3px;font-size:10px}}
61
- .timeline{{height:140px;background:#fff;border-top:1px solid #ddd;display:flex;flex-direction:column}}
62
  .tl-toolbar{{height:28px;background:#fafafa;border-bottom:1px solid #eee;display:flex;align-items:center;padding:0 6px;gap:4px}}
63
  .tl-toolbar .btn{{padding:2px 6px;font-size:9px}}
64
  .tl-zoom{{display:flex;align-items:center;gap:3px;margin-left:auto;font-size:9px;color:#666}}
@@ -68,6 +68,7 @@ body{{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#f5f5f7
68
  .tl-tracks{{position:relative}}
69
  .tl-track{{height:45px;border-bottom:1px solid #eee;display:flex}}
70
  .tl-track:nth-child(2){{background:#fffbeb}}
 
71
  .track-label{{width:50px;padding:0 4px;font-size:8px;color:#666;background:#fafafa;display:flex;align-items:center;border-right:1px solid #eee}}
72
  .track-content{{flex:1;position:relative;min-width:600px}}
73
  .clip{{position:absolute;height:36px;top:4px;border-radius:4px;cursor:grab;display:flex;align-items:center;overflow:hidden}}
@@ -76,6 +77,7 @@ body{{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#f5f5f7
76
  .clip.video{{background:linear-gradient(135deg,#818cf8,#6366f1)}}
77
  .clip.image{{background:linear-gradient(135deg,#34d399,#10b981)}}
78
  .clip.audio{{background:linear-gradient(135deg,#fbbf24,#f59e0b)}}
 
79
  .clip-thumb{{width:36px;height:100%;object-fit:cover}}
80
  .clip-info{{padding:0 4px;flex:1;overflow:hidden}}
81
  .clip-name{{font-size:8px;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}
@@ -150,6 +152,7 @@ body{{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#f5f5f7
150
  </div>
151
  <div class="timeline">
152
  <div class="tl-toolbar">
 
153
  <button class="btn btn-secondary" onclick="splitClip()">โœ‚ ์ž๋ฅด๊ธฐ</button>
154
  <button class="btn btn-secondary" onclick="dupeClip()">๐Ÿ“‹ ๋ณต์ œ</button>
155
  <button class="btn btn-danger" onclick="delClip()">๐Ÿ—‘ ์‚ญ์ œ</button>
@@ -160,6 +163,7 @@ body{{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#f5f5f7
160
  <div class="tl-tracks">
161
  <div class="tl-track"><div class="track-label">๐ŸŽฌ ์˜์ƒ</div><div class="track-content" id="t0"></div></div>
162
  <div class="tl-track"><div class="track-label">๐ŸŽต ์˜ค๋””์˜ค</div><div class="track-content" id="t1"></div></div>
 
163
  </div>
164
  <div class="playhead" id="playhead" style="left:50px"></div>
165
  </div>
@@ -179,6 +183,55 @@ body{{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#f5f5f7
179
  <button class="btn btn-secondary" onclick="cancelExport()">์ทจ์†Œ</button>
180
  </div>
181
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  <div id="hiddenMedia" class="hidden-media"></div>
183
  <script>
184
  var S={{
@@ -354,7 +407,7 @@ drawFrame();
354
  }}
355
 
356
  function renderTL(){{
357
- ['t0','t1'].forEach(function(tid){{document.getElementById(tid).innerHTML=''}});
358
  S.clips.forEach(function(c){{
359
  var tr=document.getElementById('t'+c.track);
360
  var el=document.createElement('div');
@@ -368,7 +421,8 @@ el.oncontextmenu=function(e){{e.preventDefault();selClip(c.id);showCtx(e.clientX
368
  el.ondragstart=function(e){{e.dataTransfer.setData('cid',c.id);e.dataTransfer.setData('ox',e.offsetX)}};
369
  var m=S.media.find(function(x){{return x.id===c.mid}});
370
  var th=m&&m.thumb?'<img class="clip-thumb" src="'+m.thumb+'">':'';
371
- el.innerHTML=th+'<div class="clip-info"><div class="clip-name">'+c.name+'</div><div class="clip-dur">'+fmt(len)+'</div></div><div class="clip-handle clip-handle-l"></div><div class="clip-handle clip-handle-r"></div>';
 
372
  el.querySelector('.clip-handle-l').onmousedown=function(e){{e.stopPropagation();startTrim(c.id,'l',e)}};
373
  el.querySelector('.clip-handle-r').onmousedown=function(e){{e.stopPropagation();startTrim(c.id,'r',e)}};
374
  tr.appendChild(el);
@@ -392,7 +446,7 @@ ru.innerHTML=h+'</svg>';
392
  }}
393
 
394
  function setupDrop(){{
395
- ['t0','t1'].forEach(function(tid,idx){{
396
  var tr=document.getElementById(tid);
397
  tr.ondragover=function(e){{e.preventDefault();tr.classList.add('drop-zone')}};
398
  tr.ondragleave=function(){{tr.classList.remove('drop-zone')}};
@@ -412,7 +466,14 @@ save();
412
  var c=S.clips.find(function(x){{return x.id===cid}});
413
  if(c){{
414
  c.start=r(Math.max(0,t-ox/(S.pps*S.zoom)));
415
- c.track=c.type==='audio'?1:idx;
 
 
 
 
 
 
 
416
  renderTL();
417
  updateDur();
418
  drawFrame();
@@ -461,11 +522,34 @@ var box=document.getElementById('propsBox');
461
  var c=S.clips.find(function(x){{return x.id===S.sel}});
462
  if(!c){{box.innerHTML='<div class="no-sel">ํด๋ฆฝ ์„ ํƒ</div>';return}}
463
  var len=r(c.te-c.ts);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  box.innerHTML='<div class="prop-group"><div class="prop-label">์ด๋ฆ„</div><input class="prop-input" value="'+c.name+'" onchange="setProp(\\'name\\',this.value)"></div>'+
465
  '<div class="prop-group"><div class="prop-label">์‹œ์ž‘</div><input class="prop-input" type="number" step="0.1" value="'+c.start+'" onchange="setProp(\\'start\\',parseFloat(this.value))"></div>'+
466
  '<div class="prop-group"><div class="prop-label">๊ธธ์ด: '+fmt(len)+'</div></div>'+
467
  (c.type!=='image'?'<div class="prop-group"><div class="prop-label">๋ณผ๋ฅจ '+Math.round(c.vol*100)+'%</div><input class="prop-input" type="range" min="0" max="1" step="0.05" value="'+c.vol+'" oninput="setProp(\\'vol\\',parseFloat(this.value))"></div>':'');
468
  }}
 
469
 
470
  function setProp(p,v){{
471
  save();
@@ -639,6 +723,8 @@ if(el&&!el.paused)el.pause();
639
  }}
640
  }});
641
  if(!vc&&!audioClips.length&&S.clips.length>0){{
 
 
642
  S.ctx.fillStyle='#333';
643
  S.ctx.font='12px sans-serif';
644
  S.ctx.textAlign='center';
@@ -646,6 +732,13 @@ S.ctx.fillText('์žฌ์ƒ ์œ„์น˜์— ๋ฏธ๋””์–ด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค',pw/2,ph/2);
646
  }}
647
  }}
648
 
 
 
 
 
 
 
 
649
  function drawWithMode(ctx,el,sw,sh,dw,dh,mode){{
650
  if(mode==='fill'){{
651
  // ์ฑ„์šฐ๊ธฐ: ์บ”๋ฒ„์Šค๋ฅผ ๊ฝ‰ ์ฑ„์šฐ๋„๋ก ํ™•๋Œ€, ๋„˜์น˜๋Š” ๋ถ€๋ถ„ ์ž˜๋ฆผ
@@ -693,6 +786,99 @@ ctx.drawImage(el,ox,oy,nw,nh);
693
  }}
694
 
695
  function toggleMute(){{S.muted=!S.muted;document.getElementById('muteBtn').textContent=S.muted?'๐Ÿ”‡':'๐Ÿ”Š'}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696
  function setZoom(v){{S.zoom=parseFloat(v);renderTL();updateHead()}}
697
  function tlClick(e){{
698
  if(e.target.closest('.clip'))return;
@@ -827,6 +1013,12 @@ lastClipId=null;
827
  }}
828
  }}
829
 
 
 
 
 
 
 
830
  ctx.drawImage(offCanvas,0,0);
831
  requestAnimationFrame(render);
832
  }}
@@ -920,6 +1112,48 @@ var fx=(dw-fw)/2,fy=(dh-fh)/2;
920
  ctx.drawImage(frameCanvas,fx,fy,fw,fh);
921
  }}
922
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
923
  function cancelExport(){{S.cancelled=true;document.getElementById('exportModal').style.display='none'}}
924
 
925
  document.addEventListener('keydown',function(e){{
 
58
  .prop-group{{margin-bottom:10px}}
59
  .prop-label{{font-size:9px;color:#666;margin-bottom:2px}}
60
  .prop-input{{width:100%;padding:4px;border:1px solid #ddd;border-radius:3px;font-size:10px}}
61
+ .timeline{{height:180px;background:#fff;border-top:1px solid #ddd;display:flex;flex-direction:column}}
62
  .tl-toolbar{{height:28px;background:#fafafa;border-bottom:1px solid #eee;display:flex;align-items:center;padding:0 6px;gap:4px}}
63
  .tl-toolbar .btn{{padding:2px 6px;font-size:9px}}
64
  .tl-zoom{{display:flex;align-items:center;gap:3px;margin-left:auto;font-size:9px;color:#666}}
 
68
  .tl-tracks{{position:relative}}
69
  .tl-track{{height:45px;border-bottom:1px solid #eee;display:flex}}
70
  .tl-track:nth-child(2){{background:#fffbeb}}
71
+ .tl-track:nth-child(3){{background:#fdf2f8}}
72
  .track-label{{width:50px;padding:0 4px;font-size:8px;color:#666;background:#fafafa;display:flex;align-items:center;border-right:1px solid #eee}}
73
  .track-content{{flex:1;position:relative;min-width:600px}}
74
  .clip{{position:absolute;height:36px;top:4px;border-radius:4px;cursor:grab;display:flex;align-items:center;overflow:hidden}}
 
77
  .clip.video{{background:linear-gradient(135deg,#818cf8,#6366f1)}}
78
  .clip.image{{background:linear-gradient(135deg,#34d399,#10b981)}}
79
  .clip.audio{{background:linear-gradient(135deg,#fbbf24,#f59e0b)}}
80
+ .clip.text{{background:linear-gradient(135deg,#f472b6,#ec4899)}}
81
  .clip-thumb{{width:36px;height:100%;object-fit:cover}}
82
  .clip-info{{padding:0 4px;flex:1;overflow:hidden}}
83
  .clip-name{{font-size:8px;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}
 
152
  </div>
153
  <div class="timeline">
154
  <div class="tl-toolbar">
155
+ <button class="btn btn-secondary" onclick="addTextClip()">๐Ÿ“ ํ…์ŠคํŠธ</button>
156
  <button class="btn btn-secondary" onclick="splitClip()">โœ‚ ์ž๋ฅด๊ธฐ</button>
157
  <button class="btn btn-secondary" onclick="dupeClip()">๐Ÿ“‹ ๋ณต์ œ</button>
158
  <button class="btn btn-danger" onclick="delClip()">๐Ÿ—‘ ์‚ญ์ œ</button>
 
163
  <div class="tl-tracks">
164
  <div class="tl-track"><div class="track-label">๐ŸŽฌ ์˜์ƒ</div><div class="track-content" id="t0"></div></div>
165
  <div class="tl-track"><div class="track-label">๐ŸŽต ์˜ค๋””์˜ค</div><div class="track-content" id="t1"></div></div>
166
+ <div class="tl-track"><div class="track-label">๐Ÿ“ ํ…์ŠคํŠธ</div><div class="track-content" id="t2"></div></div>
167
  </div>
168
  <div class="playhead" id="playhead" style="left:50px"></div>
169
  </div>
 
183
  <button class="btn btn-secondary" onclick="cancelExport()">์ทจ์†Œ</button>
184
  </div>
185
  </div>
186
+ <div class="modal" id="textModal" style="display:none">
187
+ <div class="modal-box" style="min-width:320px">
188
+ <h3>๐Ÿ“ ํ…์ŠคํŠธ ์ถ”๊ฐ€</h3>
189
+ <div style="margin:10px 0;text-align:left">
190
+ <div style="margin-bottom:8px">
191
+ <label style="font-size:11px;color:#666">ํ…์ŠคํŠธ ๋‚ด์šฉ</label>
192
+ <input type="text" id="textInput" placeholder="ํ…์ŠคํŠธ ์ž…๋ ฅ..." style="width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;margin-top:4px">
193
+ </div>
194
+ <div style="display:flex;gap:8px;margin-bottom:8px">
195
+ <div style="flex:1">
196
+ <label style="font-size:11px;color:#666">๊ธ€์ž ํฌ๊ธฐ</label>
197
+ <select id="textSize" style="width:100%;padding:6px;border:1px solid #ddd;border-radius:4px;margin-top:4px">
198
+ <option value="24">์ž‘๊ฒŒ (24px)</option>
199
+ <option value="36">๋ณดํ†ต (36px)</option>
200
+ <option value="48" selected>ํฌ๊ฒŒ (48px)</option>
201
+ <option value="64">๋งค์šฐ ํฌ๊ฒŒ (64px)</option>
202
+ <option value="96">์ตœ๋Œ€ (96px)</option>
203
+ </select>
204
+ </div>
205
+ <div style="flex:1">
206
+ <label style="font-size:11px;color:#666">๊ธ€์ž ์ƒ‰์ƒ</label>
207
+ <input type="color" id="textColor" value="#ffffff" style="width:100%;height:32px;border:1px solid #ddd;border-radius:4px;margin-top:4px;cursor:pointer">
208
+ </div>
209
+ </div>
210
+ <div style="display:flex;gap:8px;margin-bottom:8px">
211
+ <div style="flex:1">
212
+ <label style="font-size:11px;color:#666">๋ฐฐ๊ฒฝ ์Šคํƒ€์ผ</label>
213
+ <select id="textBgStyle" onchange="toggleBgColor()" style="width:100%;padding:6px;border:1px solid #ddd;border-radius:4px;margin-top:4px">
214
+ <option value="none">๋ฐฐ๊ฒฝ ์—†์Œ</option>
215
+ <option value="solid" selected>๋‹จ์ƒ‰ ๋ฐฐ๊ฒฝ</option>
216
+ <option value="rounded">๋‘ฅ๊ทผ ๋ฐฐ๊ฒฝ</option>
217
+ </select>
218
+ </div>
219
+ <div style="flex:1" id="bgColorBox">
220
+ <label style="font-size:11px;color:#666">๋ฐฐ๊ฒฝ ์ƒ‰์ƒ</label>
221
+ <input type="color" id="textBgColor" value="#000000" style="width:100%;height:32px;border:1px solid #ddd;border-radius:4px;margin-top:4px;cursor:pointer">
222
+ </div>
223
+ </div>
224
+ <div style="margin-bottom:8px">
225
+ <label style="font-size:11px;color:#666">ํ‘œ์‹œ ์‹œ๊ฐ„ (์ดˆ)</label>
226
+ <input type="number" id="textDuration" value="5" min="1" max="60" step="0.5" style="width:100%;padding:6px;border:1px solid #ddd;border-radius:4px;margin-top:4px">
227
+ </div>
228
+ </div>
229
+ <div style="display:flex;gap:8px;justify-content:center">
230
+ <button class="btn btn-secondary" onclick="closeTextModal()">์ทจ์†Œ</button>
231
+ <button class="btn btn-success" onclick="confirmAddText()">์ถ”๊ฐ€</button>
232
+ </div>
233
+ </div>
234
+ </div>
235
  <div id="hiddenMedia" class="hidden-media"></div>
236
  <script>
237
  var S={{
 
407
  }}
408
 
409
  function renderTL(){{
410
+ ['t0','t1','t2'].forEach(function(tid){{document.getElementById(tid).innerHTML=''}});
411
  S.clips.forEach(function(c){{
412
  var tr=document.getElementById('t'+c.track);
413
  var el=document.createElement('div');
 
421
  el.ondragstart=function(e){{e.dataTransfer.setData('cid',c.id);e.dataTransfer.setData('ox',e.offsetX)}};
422
  var m=S.media.find(function(x){{return x.id===c.mid}});
423
  var th=m&&m.thumb?'<img class="clip-thumb" src="'+m.thumb+'">':'';
424
+ var clipName=c.type==='text'?('๐Ÿ“ '+c.text.substring(0,8)):c.name;
425
+ el.innerHTML=th+'<div class="clip-info"><div class="clip-name">'+clipName+'</div><div class="clip-dur">'+fmt(len)+'</div></div><div class="clip-handle clip-handle-l"></div><div class="clip-handle clip-handle-r"></div>';
426
  el.querySelector('.clip-handle-l').onmousedown=function(e){{e.stopPropagation();startTrim(c.id,'l',e)}};
427
  el.querySelector('.clip-handle-r').onmousedown=function(e){{e.stopPropagation();startTrim(c.id,'r',e)}};
428
  tr.appendChild(el);
 
446
  }}
447
 
448
  function setupDrop(){{
449
+ ['t0','t1','t2'].forEach(function(tid,idx){{
450
  var tr=document.getElementById(tid);
451
  tr.ondragover=function(e){{e.preventDefault();tr.classList.add('drop-zone')}};
452
  tr.ondragleave=function(){{tr.classList.remove('drop-zone')}};
 
466
  var c=S.clips.find(function(x){{return x.id===cid}});
467
  if(c){{
468
  c.start=r(Math.max(0,t-ox/(S.pps*S.zoom)));
469
+ // ํ…์ŠคํŠธ๋Š” ํ…์ŠคํŠธ ํŠธ๋ž™์œผ๋กœ๋งŒ, ๊ทธ ์™ธ๋Š” ์˜์ƒ/์˜ค๋””์˜ค ํŠธ๋ž™์œผ๋กœ
470
+ if(c.type==='text'){{
471
+ c.track=2;
472
+ }}else if(c.type==='audio'){{
473
+ c.track=1;
474
+ }}else{{
475
+ c.track=idx===2?0:idx;
476
+ }}
477
  renderTL();
478
  updateDur();
479
  drawFrame();
 
522
  var c=S.clips.find(function(x){{return x.id===S.sel}});
523
  if(!c){{box.innerHTML='<div class="no-sel">ํด๋ฆฝ ์„ ํƒ</div>';return}}
524
  var len=r(c.te-c.ts);
525
+
526
+ if(c.type==='text'){{
527
+ // ํ…์ŠคํŠธ ํด๋ฆฝ ์†์„ฑ
528
+ box.innerHTML='<div class="prop-group"><div class="prop-label">ํ…์ŠคํŠธ</div><input class="prop-input" value="'+c.text+'" onchange="setProp(\\'text\\',this.value)"></div>'+
529
+ '<div class="prop-group"><div class="prop-label">์‹œ์ž‘</div><input class="prop-input" type="number" step="0.1" value="'+c.start+'" onchange="setProp(\\'start\\',parseFloat(this.value))"></div>'+
530
+ '<div class="prop-group"><div class="prop-label">๊ธธ์ด: '+fmt(len)+'</div></div>'+
531
+ '<div class="prop-group"><div class="prop-label">๊ธ€์ž ํฌ๊ธฐ</div><select class="prop-input" onchange="setProp(\\'fontSize\\',parseInt(this.value))">'+
532
+ '<option value="24"'+(c.fontSize===24?' selected':'')+'>์ž‘๊ฒŒ</option>'+
533
+ '<option value="36"'+(c.fontSize===36?' selected':'')+'>๋ณดํ†ต</option>'+
534
+ '<option value="48"'+(c.fontSize===48?' selected':'')+'>ํฌ๊ฒŒ</option>'+
535
+ '<option value="64"'+(c.fontSize===64?' selected':'')+'>๋งค์šฐ ํฌ๊ฒŒ</option>'+
536
+ '<option value="96"'+(c.fontSize===96?' selected':'')+'>์ตœ๋Œ€</option>'+
537
+ '</select></div>'+
538
+ '<div class="prop-group"><div class="prop-label">๊ธ€์ž ์ƒ‰์ƒ</div><input class="prop-input" type="color" value="'+c.fontColor+'" onchange="setProp(\\'fontColor\\',this.value)"></div>'+
539
+ '<div class="prop-group"><div class="prop-label">๋ฐฐ๊ฒฝ ์Šคํƒ€์ผ</div><select class="prop-input" onchange="setProp(\\'bgStyle\\',this.value)">'+
540
+ '<option value="none"'+(c.bgStyle==='none'?' selected':'')+'>์—†์Œ</option>'+
541
+ '<option value="solid"'+(c.bgStyle==='solid'?' selected':'')+'>๋‹จ์ƒ‰</option>'+
542
+ '<option value="rounded"'+(c.bgStyle==='rounded'?' selected':'')+'>๋‘ฅ๊ทผ</option>'+
543
+ '</select></div>'+
544
+ '<div class="prop-group"><div class="prop-label">๋ฐฐ๊ฒฝ ์ƒ‰์ƒ</div><input class="prop-input" type="color" value="'+c.bgColor+'" onchange="setProp(\\'bgColor\\',this.value)"></div>';
545
+ }}else{{
546
+ // ๊ธฐ์กด ๋ฏธ๋””์–ด ํด๋ฆฝ ์†์„ฑ
547
  box.innerHTML='<div class="prop-group"><div class="prop-label">์ด๋ฆ„</div><input class="prop-input" value="'+c.name+'" onchange="setProp(\\'name\\',this.value)"></div>'+
548
  '<div class="prop-group"><div class="prop-label">์‹œ์ž‘</div><input class="prop-input" type="number" step="0.1" value="'+c.start+'" onchange="setProp(\\'start\\',parseFloat(this.value))"></div>'+
549
  '<div class="prop-group"><div class="prop-label">๊ธธ์ด: '+fmt(len)+'</div></div>'+
550
  (c.type!=='image'?'<div class="prop-group"><div class="prop-label">๋ณผ๋ฅจ '+Math.round(c.vol*100)+'%</div><input class="prop-input" type="range" min="0" max="1" step="0.05" value="'+c.vol+'" oninput="setProp(\\'vol\\',parseFloat(this.value))"></div>':'');
551
  }}
552
+ }}
553
 
554
  function setProp(p,v){{
555
  save();
 
723
  }}
724
  }});
725
  if(!vc&&!audioClips.length&&S.clips.length>0){{
726
+ var hasText=getTextClipsAt(t).length>0;
727
+ if(!hasText){{
728
  S.ctx.fillStyle='#333';
729
  S.ctx.font='12px sans-serif';
730
  S.ctx.textAlign='center';
 
732
  }}
733
  }}
734
 
735
+ // ํ…์ŠคํŠธ ์˜ค๋ฒ„๋ ˆ์ด ๋ Œ๋”๋ง
736
+ var textClips=getTextClipsAt(t);
737
+ textClips.forEach(function(tc){{
738
+ drawText(S.ctx,tc,pw,ph);
739
+ }});
740
+ }}
741
+
742
  function drawWithMode(ctx,el,sw,sh,dw,dh,mode){{
743
  if(mode==='fill'){{
744
  // ์ฑ„์šฐ๊ธฐ: ์บ”๋ฒ„์Šค๋ฅผ ๊ฝ‰ ์ฑ„์šฐ๋„๋ก ํ™•๋Œ€, ๋„˜์น˜๋Š” ๋ถ€๋ถ„ ์ž˜๋ฆผ
 
786
  }}
787
 
788
  function toggleMute(){{S.muted=!S.muted;document.getElementById('muteBtn').textContent=S.muted?'๐Ÿ”‡':'๐Ÿ”Š'}}
789
+
790
+ // ํ…์ŠคํŠธ ํด๋ฆฝ ๊ด€๋ จ ํ•จ์ˆ˜๋“ค
791
+ function addTextClip(){{
792
+ document.getElementById('textModal').style.display='flex';
793
+ document.getElementById('textInput').value='';
794
+ document.getElementById('textInput').focus();
795
+ }}
796
+
797
+ function closeTextModal(){{
798
+ document.getElementById('textModal').style.display='none';
799
+ }}
800
+
801
+ function toggleBgColor(){{
802
+ var style=document.getElementById('textBgStyle').value;
803
+ document.getElementById('bgColorBox').style.opacity=style==='none'?'0.5':'1';
804
+ }}
805
+
806
+ function confirmAddText(){{
807
+ var text=document.getElementById('textInput').value.trim();
808
+ if(!text){{alert('ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”');return}}
809
+ save();
810
+ var duration=parseFloat(document.getElementById('textDuration').value)||5;
811
+ S.clips.push({{
812
+ id:id(),
813
+ type:'text',
814
+ track:2,
815
+ start:S.time,
816
+ dur:duration,
817
+ ts:0,
818
+ te:duration,
819
+ text:text,
820
+ fontSize:parseInt(document.getElementById('textSize').value),
821
+ fontColor:document.getElementById('textColor').value,
822
+ bgStyle:document.getElementById('textBgStyle').value,
823
+ bgColor:document.getElementById('textBgColor').value
824
+ }});
825
+ closeTextModal();
826
+ renderTL();
827
+ updateDur();
828
+ drawFrame();
829
+ stat('ํ…์ŠคํŠธ ์ถ”๊ฐ€: '+text);
830
+ }}
831
+
832
+ function getTextClipsAt(t){{
833
+ return S.clips.filter(function(c){{
834
+ if(c.type!=='text')return false;
835
+ var cEnd=c.start+(c.te-c.ts);
836
+ return t>=c.start&&t<cEnd;
837
+ }});
838
+ }}
839
+
840
+ function drawText(ctx,clip,dw,dh){{
841
+ var text=clip.text;
842
+ var fontSize=clip.fontSize||48;
843
+ var fontColor=clip.fontColor||'#ffffff';
844
+ var bgStyle=clip.bgStyle||'none';
845
+ var bgColor=clip.bgColor||'#000000';
846
+
847
+ ctx.font='bold '+fontSize+'px -apple-system,BlinkMacSystemFont,sans-serif';
848
+ ctx.textAlign='center';
849
+ ctx.textBaseline='middle';
850
+
851
+ var metrics=ctx.measureText(text);
852
+ var textW=metrics.width;
853
+ var textH=fontSize;
854
+ var x=dw/2;
855
+ var y=dh-fontSize-30; // ํ•˜๋‹จ์— ๋ฐฐ์น˜
856
+
857
+ if(bgStyle!=='none'){{
858
+ var padding=fontSize*0.3;
859
+ var bgX=x-textW/2-padding;
860
+ var bgY=y-textH/2-padding/2;
861
+ var bgW=textW+padding*2;
862
+ var bgH=textH+padding;
863
+
864
+ ctx.fillStyle=bgColor+'cc'; // ์•ฝ๊ฐ„ ํˆฌ๋ช…
865
+ if(bgStyle==='rounded'){{
866
+ ctx.beginPath();
867
+ ctx.roundRect(bgX,bgY,bgW,bgH,10);
868
+ ctx.fill();
869
+ }}else{{
870
+ ctx.fillRect(bgX,bgY,bgW,bgH);
871
+ }}
872
+ }}
873
+
874
+ // ํ…์ŠคํŠธ ๊ทธ๋ฆผ์ž
875
+ ctx.fillStyle='rgba(0,0,0,0.5)';
876
+ ctx.fillText(text,x+2,y+2);
877
+
878
+ // ํ…์ŠคํŠธ
879
+ ctx.fillStyle=fontColor;
880
+ ctx.fillText(text,x,y);
881
+ }}
882
  function setZoom(v){{S.zoom=parseFloat(v);renderTL();updateHead()}}
883
  function tlClick(e){{
884
  if(e.target.closest('.clip'))return;
 
1013
  }}
1014
  }}
1015
 
1016
+ // ํ…์ŠคํŠธ ์˜ค๋ฒ„๋ ˆ์ด ๋ Œ๋”๋ง
1017
+ var textClips=getTextClipsAt(t);
1018
+ textClips.forEach(function(tc){{
1019
+ drawTextExport(offCtx,tc,exportW,exportH);
1020
+ }});
1021
+
1022
  ctx.drawImage(offCanvas,0,0);
1023
  requestAnimationFrame(render);
1024
  }}
 
1112
  ctx.drawImage(frameCanvas,fx,fy,fw,fh);
1113
  }}
1114
 
1115
+ // ๋‚ด๋ณด๋‚ด๊ธฐ์šฉ ํ…์ŠคํŠธ ๋ Œ๋”๋ง
1116
+ function drawTextExport(ctx,clip,dw,dh){{
1117
+ var text=clip.text;
1118
+ var fontSize=Math.round(clip.fontSize*(dw/640))||72; // ๋‚ด๋ณด๋‚ด๊ธฐ ํ•ด์ƒ๋„์— ๋งž๊ฒŒ ์Šค์ผ€์ผ
1119
+ var fontColor=clip.fontColor||'#ffffff';
1120
+ var bgStyle=clip.bgStyle||'none';
1121
+ var bgColor=clip.bgColor||'#000000';
1122
+
1123
+ ctx.font='bold '+fontSize+'px -apple-system,BlinkMacSystemFont,sans-serif';
1124
+ ctx.textAlign='center';
1125
+ ctx.textBaseline='middle';
1126
+
1127
+ var metrics=ctx.measureText(text);
1128
+ var textW=metrics.width;
1129
+ var textH=fontSize;
1130
+ var x=dw/2;
1131
+ var y=dh-fontSize-50; // ํ•˜๋‹จ์— ๋ฐฐ์น˜
1132
+
1133
+ if(bgStyle!=='none'){{
1134
+ var padding=fontSize*0.3;
1135
+ var bgX=x-textW/2-padding;
1136
+ var bgY=y-textH/2-padding/2;
1137
+ var bgW=textW+padding*2;
1138
+ var bgH=textH+padding;
1139
+
1140
+ ctx.fillStyle=bgColor+'cc';
1141
+ if(bgStyle==='rounded'){{
1142
+ ctx.beginPath();
1143
+ ctx.roundRect(bgX,bgY,bgW,bgH,15);
1144
+ ctx.fill();
1145
+ }}else{{
1146
+ ctx.fillRect(bgX,bgY,bgW,bgH);
1147
+ }}
1148
+ }}
1149
+
1150
+ ctx.fillStyle='rgba(0,0,0,0.5)';
1151
+ ctx.fillText(text,x+3,y+3);
1152
+
1153
+ ctx.fillStyle=fontColor;
1154
+ ctx.fillText(text,x,y);
1155
+ }}
1156
+
1157
  function cancelExport(){{S.cancelled=true;document.getElementById('exportModal').style.display='none'}}
1158
 
1159
  document.addEventListener('keydown',function(e){{