seawolf2357 commited on
Commit
4bde71e
Β·
verified Β·
1 Parent(s): 12b1424

Update app-BACKUP2.py

Browse files
Files changed (1) hide show
  1. app-BACKUP2.py +80 -94
app-BACKUP2.py CHANGED
@@ -1,6 +1,7 @@
1
  """
2
  Simple Video Editor - Canvas 기반 λ Œλ”λ§
3
  빈 ν”„λ ˆμž„ 문제 μ™„μ „ ν•΄κ²° + μ„œλ²„ μ‚¬μ΄λ“œ MP4 내보내기
 
4
  """
5
 
6
  import gradio as gr
@@ -1007,25 +1008,28 @@ document.getElementById('exportMsg').textContent='λ…Ήν™” μ€€λΉ„ 쀑...';
1007
  doExport();
1008
  }}
1009
 
 
 
 
1010
  async function doExport(){{
1011
  var size=S.ratioSizes[S.ratio];
1012
  var exportW=size.w,exportH=size.h;
1013
 
1014
- // 메인 μΊ”λ²„μŠ€ (MediaRecorder μ—°κ²°μš©)
1015
- var canvas=document.createElement('canvas');
1016
- canvas.width=exportW;canvas.height=exportH;
1017
- var ctx=canvas.getContext('2d');
1018
 
1019
- // μ˜€ν”„μŠ€ν¬λ¦° μΊ”λ²„μŠ€ (λ Œλ”λ§μš© - 더블 버퍼링)
1020
  var offCanvas=document.createElement('canvas');
1021
  offCanvas.width=exportW;offCanvas.height=exportH;
1022
  var offCtx=offCanvas.getContext('2d');
1023
 
1024
  // 초기 검은 ν™”λ©΄
1025
- ctx.fillStyle='#000';
1026
- ctx.fillRect(0,0,exportW,exportH);
1027
 
1028
- var stream=canvas.captureStream(30);
1029
 
1030
  // κ³ ν™”μ§ˆ μ„€μ •
1031
  var opts={{mimeType:'video/webm;codecs=vp9',videoBitsPerSecond:8000000}};
@@ -1044,93 +1048,117 @@ document.getElementById('exportMsg').textContent='λ…Ήν™” 쀑... ('+S.ratio+')';
1044
  rec.start(100);
1045
 
1046
  var dur=S.dur;
1047
- var fps=30;
1048
- var frameInterval=1000/fps;
1049
- var totalFrames=Math.ceil(dur*fps);
1050
- var startTime=performance.now();
1051
  var fillMode=S.fillMode;
1052
 
1053
- // μ‹€μ‹œκ°„ 기반 λ Œλ”λ§
1054
- // λΈ”λŸ¬ λͺ¨λ“œμš© ν”„λ ˆμž„ 캑처 μΊ”λ²„μŠ€ (μž¬μ‚¬μš©)
1055
  var frameCanvas=document.createElement('canvas');
1056
- frameCanvas.width=1920;frameCanvas.height=1080;
1057
  var frameCtx=frameCanvas.getContext('2d');
 
 
 
1058
 
1059
- await new Promise(function(resolve){{
1060
- var frameCount=0;
1061
- var lastVideoEl=null;
1062
- var lastClipId=null;
 
 
 
 
 
1063
 
 
 
 
 
 
 
 
 
 
 
 
 
1064
  function render(){{
1065
  if(S.cancelled){{rec.stop();resolve();return}}
1066
 
1067
- var elapsed=performance.now()-startTime;
1068
- var t=elapsed/1000;
1069
-
1070
- if(t>=dur){{
1071
  rec.stop();
1072
- setTimeout(resolve,100);
1073
  return;
1074
  }}
1075
 
1076
- document.getElementById('exportBar').style.width=(t/dur*100)+'%';
1077
- document.getElementById('exportMsg').textContent='λ…Ήν™” 쀑... '+Math.round(t/dur*100)+'% ('+t.toFixed(1)+'/'+dur.toFixed(1)+'초)';
 
1078
 
 
1079
  offCtx.fillStyle='#000';
1080
  offCtx.fillRect(0,0,exportW,exportH);
1081
 
1082
- var vc=getClipAt(t,'visual');
1083
  if(vc){{
1084
  var el=S.els[vc.mid];
1085
  if(el){{
 
1086
  if(vc.type==='video'){{
1087
- var clipT=t-vc.start+vc.ts;
1088
- if(lastClipId!==vc.id){{
1089
- el.currentTime=clipT;
1090
- el.play().catch(function(){{}});
1091
- lastClipId=vc.id;
1092
- lastVideoEl=el;
1093
- }}
1094
- if(lastVideoEl&&lastVideoEl!==el&&!lastVideoEl.paused){{
1095
- lastVideoEl.pause();
1096
  }}
1097
- lastVideoEl=el;
1098
  }}
1099
 
1100
  try{{
1101
  var sw=el.videoWidth||el.naturalWidth||el.width||exportW;
1102
  var sh=el.videoHeight||el.naturalHeight||el.height||exportH;
1103
 
1104
- // λΈ”λŸ¬ λͺ¨λ“œ: ν”„λ ˆμž„μ„ λ¨Όμ € 캑처
1105
  if(fillMode==='blur'){{
1106
- frameCanvas.width=sw;frameCanvas.height=sh;
 
 
1107
  frameCtx.drawImage(el,0,0,sw,sh);
1108
- drawBlurFromFrame(offCtx,frameCanvas,sw,sh,exportW,exportH);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1109
  }}else{{
1110
- drawWithModeExport(offCtx,el,sw,sh,exportW,exportH,fillMode);
1111
- }}
1112
- }}catch(e){{}}
 
1113
  }}
1114
- }}else{{
1115
- if(lastVideoEl&&!lastVideoEl.paused){{
1116
- lastVideoEl.pause();
1117
- lastClipId=null;
1118
  }}
1119
  }}
1120
 
1121
- // ν…μŠ€νŠΈ μ˜€λ²„λ ˆμ΄ λ Œλ”λ§
1122
- var textClips=getTextClipsAt(t);
1123
  textClips.forEach(function(tc){{
1124
  drawTextExport(offCtx,tc,exportW,exportH);
1125
  }});
1126
 
1127
- ctx.drawImage(offCanvas,0,0);
 
 
1128
  requestAnimationFrame(render);
1129
  }}
1130
-
1131
  requestAnimationFrame(render);
1132
  }});
1133
 
 
1134
  Object.keys(S.els).forEach(function(k){{
1135
  var el=S.els[k];
1136
  if(el&&el.pause)el.pause();
@@ -1175,48 +1203,6 @@ downloadDiv.appendChild(copyBtn);
1175
  document.querySelector('.modal-box').appendChild(downloadDiv);
1176
  }}
1177
 
1178
- // λ‚΄λ³΄λ‚΄κΈ°μš© 그리기 ν•¨μˆ˜ (λΈ”λŸ¬ 효과 포함)
1179
- function drawWithModeExport(ctx,el,sw,sh,dw,dh,mode){{
1180
- if(mode==='fill'){{
1181
- var scale=Math.max(dw/sw,dh/sh);
1182
- var nw=sw*scale,nh=sh*scale;
1183
- var ox=(dw-nw)/2,oy=(dh-nh)/2;
1184
- ctx.drawImage(el,ox,oy,nw,nh);
1185
- }}else{{
1186
- var scale=Math.min(dw/sw,dh/sh);
1187
- var nw=sw*scale,nh=sh*scale;
1188
- var ox=(dw-nw)/2,oy=(dh-nh)/2;
1189
- ctx.drawImage(el,ox,oy,nw,nh);
1190
- }}
1191
- }}
1192
-
1193
- // λΈ”λŸ¬ μ „μš© - 캑처된 ν”„λ ˆμž„μœΌλ‘œ 처리
1194
- function drawBlurFromFrame(ctx,frameCanvas,sw,sh,dw,dh){{
1195
- // 1. μž‘μ€ μΊ”λ²„μŠ€λ‘œ μΆ•μ†Œ (λΈ”λŸ¬ 효과)
1196
- var blurCanvas=document.createElement('canvas');
1197
- blurCanvas.width=16;blurCanvas.height=16;
1198
- var blurCtx=blurCanvas.getContext('2d');
1199
- blurCtx.drawImage(frameCanvas,0,0,16,16);
1200
-
1201
- // 2. 배경에 ν™•λŒ€ν•΄μ„œ κ·Έλ¦Ό
1202
- ctx.imageSmoothingEnabled=true;
1203
- ctx.imageSmoothingQuality='high';
1204
- var scale=Math.max(dw/sw,dh/sh)*1.1;
1205
- var nw=sw*scale,nh=sh*scale;
1206
- var ox=(dw-nw)/2,oy=(dh-nh)/2;
1207
- ctx.drawImage(blurCanvas,ox,oy,nw,nh);
1208
-
1209
- // 3. μ–΄λ‘‘κ²Œ
1210
- ctx.fillStyle='rgba(0,0,0,0.4)';
1211
- ctx.fillRect(0,0,dw,dh);
1212
-
1213
- // 4. 원본 (캑처된 ν”„λ ˆμž„ μ‚¬μš©)
1214
- var fitScale=Math.min(dw/sw,dh/sh);
1215
- var fw=sw*fitScale,fh=sh*fitScale;
1216
- var fx=(dw-fw)/2,fy=(dh-fh)/2;
1217
- ctx.drawImage(frameCanvas,fx,fy,fw,fh);
1218
- }}
1219
-
1220
  // λ‚΄λ³΄λ‚΄κΈ°μš© ν…μŠ€νŠΈ λ Œλ”λ§
1221
  function drawTextExport(ctx,clip,dw,dh){{
1222
  var text=clip.text;
@@ -1450,7 +1436,7 @@ def convert_webm_to_mp4(webm_base64):
1450
  return None
1451
 
1452
  with gr.Blocks() as demo:
1453
- gr.Markdown("## 🎬 Video Editor")
1454
 
1455
  f = gr.File(label="πŸ“ 파일 μ—…λ‘œλ“œ", file_count="multiple", file_types=["video", "image", "audio"])
1456
  e = gr.HTML(value=make_iframe([]))
 
1
  """
2
  Simple Video Editor - Canvas 기반 λ Œλ”λ§
3
  빈 ν”„λ ˆμž„ 문제 μ™„μ „ ν•΄κ²° + μ„œλ²„ μ‚¬μ΄λ“œ MP4 내보내기
4
+ λΈ”λŸ¬ λ°°κ²½ 내보내기 μˆ˜μ •: 더블 버퍼링
5
  """
6
 
7
  import gradio as gr
 
1008
  doExport();
1009
  }}
1010
 
1011
+ // ============================================
1012
+ // 핡심 μˆ˜μ •: 더블 λ²„νΌλ§μœΌλ‘œ λΈ”λŸ¬ λ²ˆμ©μž„ λ°©μ§€
1013
+ // ============================================
1014
  async function doExport(){{
1015
  var size=S.ratioSizes[S.ratio];
1016
  var exportW=size.w,exportH=size.h;
1017
 
1018
+ // β˜… 메인 μΊ”λ²„μŠ€ (MediaRecorder μ—°κ²°) - μ™„μ„±λœ ν”„λ ˆμž„λ§Œ λ°›μŒ
1019
+ var mainCanvas=document.createElement('canvas');
1020
+ mainCanvas.width=exportW;mainCanvas.height=exportH;
1021
+ var mainCtx=mainCanvas.getContext('2d');
1022
 
1023
+ // β˜… μ˜€ν”„μŠ€ν¬λ¦° μΊ”λ²„μŠ€ (λ Œλ”λ§μš©) - μ—¬κΈ°μ„œ λͺ¨λ“  μž‘μ—… ν›„ ν•œλ²ˆμ— 볡사
1024
  var offCanvas=document.createElement('canvas');
1025
  offCanvas.width=exportW;offCanvas.height=exportH;
1026
  var offCtx=offCanvas.getContext('2d');
1027
 
1028
  // 초기 검은 ν™”λ©΄
1029
+ mainCtx.fillStyle='#000';
1030
+ mainCtx.fillRect(0,0,exportW,exportH);
1031
 
1032
+ var stream=mainCanvas.captureStream(30);
1033
 
1034
  // κ³ ν™”μ§ˆ μ„€μ •
1035
  var opts={{mimeType:'video/webm;codecs=vp9',videoBitsPerSecond:8000000}};
 
1048
  rec.start(100);
1049
 
1050
  var dur=S.dur;
 
 
 
 
1051
  var fillMode=S.fillMode;
1052
 
1053
+ // λΈ”λŸ¬μš© μΊ”λ²„μŠ€ (μž¬μ‚¬μš©)
 
1054
  var frameCanvas=document.createElement('canvas');
 
1055
  var frameCtx=frameCanvas.getContext('2d');
1056
+ var blurCanvas=document.createElement('canvas');
1057
+ blurCanvas.width=16;blurCanvas.height=16;
1058
+ var blurCtx=blurCanvas.getContext('2d');
1059
 
1060
+ // λͺ¨λ“  λΉ„λ””μ˜€λ₯Ό 처음으둜 되돌리고 μž¬μƒ
1061
+ var videoClips=S.clips.filter(function(c){{return c.type==='video'}});
1062
+ videoClips.forEach(function(vc){{
1063
+ var el=S.els[vc.mid];
1064
+ if(el){{
1065
+ el.currentTime=vc.ts;
1066
+ el.muted=true;
1067
+ }}
1068
+ }});
1069
 
1070
+ // μž μ‹œ λŒ€κΈ° ν›„ μž¬μƒ μ‹œμž‘
1071
+ await new Promise(function(r){{setTimeout(r,200)}});
1072
+
1073
+ videoClips.forEach(function(vc){{
1074
+ var el=S.els[vc.mid];
1075
+ if(el)el.play().catch(function(){{}});
1076
+ }});
1077
+
1078
+ var startTime=performance.now();
1079
+
1080
+ // μ‹€μ‹œκ°„ λ Œλ”λ§ 루프
1081
+ await new Promise(function(resolve){{
1082
  function render(){{
1083
  if(S.cancelled){{rec.stop();resolve();return}}
1084
 
1085
+ var elapsed=(performance.now()-startTime)/1000;
1086
+ if(elapsed>=dur){{
 
 
1087
  rec.stop();
1088
+ setTimeout(resolve,200);
1089
  return;
1090
  }}
1091
 
1092
+ // μ§„ν–‰λ₯  μ—…λ°μ΄νŠΈ
1093
+ document.getElementById('exportBar').style.width=(elapsed/dur*100)+'%';
1094
+ document.getElementById('exportMsg').textContent='λ…Ήν™” 쀑... '+Math.round(elapsed/dur*100)+'%';
1095
 
1096
+ // β˜… μ˜€ν”„μŠ€ν¬λ¦° μΊ”λ²„μŠ€μ— μ™„μ „νžˆ λ Œλ”λ§ β˜…
1097
  offCtx.fillStyle='#000';
1098
  offCtx.fillRect(0,0,exportW,exportH);
1099
 
1100
+ var vc=getClipAt(elapsed,'visual');
1101
  if(vc){{
1102
  var el=S.els[vc.mid];
1103
  if(el){{
1104
+ // λΉ„λ””μ˜€ μ‹œκ°„ 동기화 (차이가 크면 보정)
1105
  if(vc.type==='video'){{
1106
+ var targetT=elapsed-vc.start+vc.ts;
1107
+ if(Math.abs(el.currentTime-targetT)>0.5){{
1108
+ el.currentTime=targetT;
 
 
 
 
 
 
1109
  }}
 
1110
  }}
1111
 
1112
  try{{
1113
  var sw=el.videoWidth||el.naturalWidth||el.width||exportW;
1114
  var sh=el.videoHeight||el.naturalHeight||el.height||exportH;
1115
 
 
1116
  if(fillMode==='blur'){{
1117
+ // β˜… λΈ”λŸ¬: μ˜€ν”„μŠ€ν¬λ¦°μ—μ„œ μ™„μ „νžˆ 처리 ν›„ 메인에 볡사 β˜…
1118
+ frameCanvas.width=sw;
1119
+ frameCanvas.height=sh;
1120
  frameCtx.drawImage(el,0,0,sw,sh);
1121
+ blurCtx.drawImage(frameCanvas,0,0,16,16);
1122
+
1123
+ offCtx.imageSmoothingEnabled=true;
1124
+ offCtx.imageSmoothingQuality='high';
1125
+ var scale=Math.max(exportW/sw,exportH/sh)*1.1;
1126
+ var nw=sw*scale,nh=sh*scale;
1127
+ offCtx.drawImage(blurCanvas,(exportW-nw)/2,(exportH-nh)/2,nw,nh);
1128
+ offCtx.fillStyle='rgba(0,0,0,0.4)';
1129
+ offCtx.fillRect(0,0,exportW,exportH);
1130
+ var fitScale=Math.min(exportW/sw,exportH/sh);
1131
+ var fw=sw*fitScale,fh=sh*fitScale;
1132
+ offCtx.drawImage(frameCanvas,(exportW-fw)/2,(exportH-fh)/2,fw,fh);
1133
+ }}else if(fillMode==='fill'){{
1134
+ var scale=Math.max(exportW/sw,exportH/sh);
1135
+ var nw=sw*scale,nh=sh*scale;
1136
+ offCtx.drawImage(el,(exportW-nw)/2,(exportH-nh)/2,nw,nh);
1137
  }}else{{
1138
+ // fit
1139
+ var scale=Math.min(exportW/sw,exportH/sh);
1140
+ var nw=sw*scale,nh=sh*scale;
1141
+ offCtx.drawImage(el,(exportW-nw)/2,(exportH-nh)/2,nw,nh);
1142
  }}
1143
+ }}catch(e){{console.error('Draw error:',e)}}
 
 
 
1144
  }}
1145
  }}
1146
 
1147
+ // ν…μŠ€νŠΈ μ˜€λ²„λ ˆμ΄ λ Œλ”λ§ (μ˜€ν”„μŠ€ν¬λ¦°μ—)
1148
+ var textClips=getTextClipsAt(elapsed);
1149
  textClips.forEach(function(tc){{
1150
  drawTextExport(offCtx,tc,exportW,exportH);
1151
  }});
1152
 
1153
+ // β˜… μ˜€ν”„μŠ€ν¬λ¦° β†’ 메인 μΊ”λ²„μŠ€λ‘œ ν•œλ²ˆμ— 볡사 (atomic) β˜…
1154
+ mainCtx.drawImage(offCanvas,0,0);
1155
+
1156
  requestAnimationFrame(render);
1157
  }}
 
1158
  requestAnimationFrame(render);
1159
  }});
1160
 
1161
+ // 정리
1162
  Object.keys(S.els).forEach(function(k){{
1163
  var el=S.els[k];
1164
  if(el&&el.pause)el.pause();
 
1203
  document.querySelector('.modal-box').appendChild(downloadDiv);
1204
  }}
1205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1206
  // λ‚΄λ³΄λ‚΄κΈ°μš© ν…μŠ€νŠΈ λ Œλ”λ§
1207
  function drawTextExport(ctx,clip,dw,dh){{
1208
  var text=clip.text;
 
1436
  return None
1437
 
1438
  with gr.Blocks() as demo:
1439
+ gr.Markdown("## 🎬 Video Editor (λΈ”λŸ¬ μˆ˜μ •)")
1440
 
1441
  f = gr.File(label="πŸ“ 파일 μ—…λ‘œλ“œ", file_count="multiple", file_types=["video", "image", "audio"])
1442
  e = gr.HTML(value=make_iframe([]))