Update app.py
Browse files
app.py
CHANGED
|
@@ -620,31 +620,8 @@ if(!S.clips.length){{alert('ν΄λ¦½μ μΆκ°νμΈμ');return}}
|
|
| 620 |
S.cancelled=false;
|
| 621 |
document.getElementById('exportModal').style.display='flex';
|
| 622 |
document.getElementById('exportBar').style.width='0%';
|
| 623 |
-
document.getElementById('exportMsg').textContent='
|
| 624 |
-
|
| 625 |
-
document.getElementById('exportMsg').textContent='FFmpeg λ‘λ μ€ν¨: '+e.message;
|
| 626 |
-
}});
|
| 627 |
-
}}
|
| 628 |
-
|
| 629 |
-
var ffmpeg=null;
|
| 630 |
-
async function loadFFmpeg(){{
|
| 631 |
-
if(ffmpeg)return;
|
| 632 |
-
var script=document.createElement('script');
|
| 633 |
-
script.src='https://cdn.jsdelivr.net/npm/@ffmpeg/ffmpeg@0.12.6/dist/umd/ffmpeg.min.js';
|
| 634 |
-
document.head.appendChild(script);
|
| 635 |
-
await new Promise(function(resolve){{script.onload=resolve}});
|
| 636 |
-
var script2=document.createElement('script');
|
| 637 |
-
script2.src='https://cdn.jsdelivr.net/npm/@ffmpeg/util@0.12.1/dist/umd/index.min.js';
|
| 638 |
-
document.head.appendChild(script2);
|
| 639 |
-
await new Promise(function(resolve){{script2.onload=resolve}});
|
| 640 |
-
ffmpeg=new FFmpegWASM.FFmpeg();
|
| 641 |
-
ffmpeg.on('progress',function(p){{
|
| 642 |
-
if(p.progress)document.getElementById('exportMsg').textContent='λ³ν μ€... '+Math.round(p.progress*100)+'%';
|
| 643 |
-
}});
|
| 644 |
-
await ffmpeg.load({{
|
| 645 |
-
coreURL:'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.4/dist/umd/ffmpeg-core.js',
|
| 646 |
-
wasmURL:'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.4/dist/umd/ffmpeg-core.wasm'
|
| 647 |
-
}});
|
| 648 |
}}
|
| 649 |
|
| 650 |
async function doExport(){{
|
|
@@ -669,7 +646,7 @@ if(t>=dur){{
|
|
| 669 |
setTimeout(function(){{rec.stop();setTimeout(resolve,300)}},200);
|
| 670 |
return;
|
| 671 |
}}
|
| 672 |
-
document.getElementById('exportBar').style.width=(t/dur*
|
| 673 |
document.getElementById('exportMsg').textContent='λ
Ήν μ€... '+Math.round(t/dur*100)+'%';
|
| 674 |
ctx.fillStyle='#000';
|
| 675 |
ctx.fillRect(0,0,1280,720);
|
|
@@ -694,30 +671,36 @@ requestAnimationFrame(render);
|
|
| 694 |
if(S.cancelled)return;
|
| 695 |
var webmBlob=new Blob(chunks,{{type:'video/webm'}});
|
| 696 |
if(webmBlob.size<1000){{document.getElementById('exportMsg').textContent='λ
Ήν μ€ν¨';return}}
|
| 697 |
-
document.getElementById('exportBar').style.width='50%';
|
| 698 |
-
document.getElementById('exportMsg').textContent='MP4 λ³ν μ€...';
|
| 699 |
-
try{{
|
| 700 |
-
var webmData=new Uint8Array(await webmBlob.arrayBuffer());
|
| 701 |
-
await ffmpeg.writeFile('input.webm',webmData);
|
| 702 |
-
await ffmpeg.exec(['-i','input.webm','-c:v','libx264','-preset','fast','-crf','23','-c:a','aac','-movflags','+faststart','output.mp4']);
|
| 703 |
-
var mp4Data=await ffmpeg.readFile('output.mp4');
|
| 704 |
-
var mp4Blob=new Blob([mp4Data],{{type:'video/mp4'}});
|
| 705 |
document.getElementById('exportBar').style.width='100%';
|
| 706 |
-
document.getElementById('exportMsg').textContent='μλ£! ('+Math.round(
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
}}
|
| 722 |
|
| 723 |
function cancelExport(){{S.cancelled=true;document.getElementById('exportModal').style.display='none'}}
|
|
@@ -861,6 +844,53 @@ def export_mp4(export_json):
|
|
| 861 |
print(f"[Export] Error: {e}")
|
| 862 |
return None
|
| 863 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 864 |
with gr.Blocks() as demo:
|
| 865 |
gr.Markdown("## π¬ Video Editor")
|
| 866 |
|
|
@@ -868,17 +898,17 @@ with gr.Blocks() as demo:
|
|
| 868 |
e = gr.HTML(value=make_iframe([]))
|
| 869 |
|
| 870 |
gr.Markdown("---")
|
| 871 |
-
gr.Markdown("### π₯
|
| 872 |
-
gr.Markdown("μλν°
|
| 873 |
|
| 874 |
with gr.Row():
|
| 875 |
-
|
| 876 |
-
|
| 877 |
|
| 878 |
mp4_output = gr.File(label="π₯ MP4 λ€μ΄λ‘λ")
|
| 879 |
|
| 880 |
f.change(fn=lambda x: make_iframe(process_file(x)), inputs=[f], outputs=[e])
|
| 881 |
-
|
| 882 |
|
| 883 |
if __name__ == "__main__":
|
| 884 |
demo.launch()
|
|
|
|
| 620 |
S.cancelled=false;
|
| 621 |
document.getElementById('exportModal').style.display='flex';
|
| 622 |
document.getElementById('exportBar').style.width='0%';
|
| 623 |
+
document.getElementById('exportMsg').textContent='λ
Ήν μ€λΉ μ€...';
|
| 624 |
+
doExport();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
}}
|
| 626 |
|
| 627 |
async function doExport(){{
|
|
|
|
| 646 |
setTimeout(function(){{rec.stop();setTimeout(resolve,300)}},200);
|
| 647 |
return;
|
| 648 |
}}
|
| 649 |
+
document.getElementById('exportBar').style.width=(t/dur*100)+'%';
|
| 650 |
document.getElementById('exportMsg').textContent='λ
Ήν μ€... '+Math.round(t/dur*100)+'%';
|
| 651 |
ctx.fillStyle='#000';
|
| 652 |
ctx.fillRect(0,0,1280,720);
|
|
|
|
| 671 |
if(S.cancelled)return;
|
| 672 |
var webmBlob=new Blob(chunks,{{type:'video/webm'}});
|
| 673 |
if(webmBlob.size<1000){{document.getElementById('exportMsg').textContent='λ
Ήν μ€ν¨';return}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
document.getElementById('exportBar').style.width='100%';
|
| 675 |
+
document.getElementById('exportMsg').textContent='μλ£! ('+Math.round(webmBlob.size/1024)+'KB) - μλμμ λ€μ΄λ‘λνμΈμ';
|
| 676 |
+
// WebM λ€μ΄λ‘λ λ§ν¬ μμ±
|
| 677 |
+
var downloadDiv=document.createElement('div');
|
| 678 |
+
downloadDiv.style.marginTop='10px';
|
| 679 |
+
var webmLink=document.createElement('a');
|
| 680 |
+
webmLink.href=URL.createObjectURL(webmBlob);
|
| 681 |
+
webmLink.download='video_'+Date.now()+'.webm';
|
| 682 |
+
webmLink.className='btn btn-success';
|
| 683 |
+
webmLink.textContent='π₯ WebM λ€μ΄λ‘λ';
|
| 684 |
+
webmLink.style.marginRight='5px';
|
| 685 |
+
downloadDiv.appendChild(webmLink);
|
| 686 |
+
// base64 λ³΅μ¬ λ²νΌ
|
| 687 |
+
var copyBtn=document.createElement('button');
|
| 688 |
+
copyBtn.className='btn btn-secondary';
|
| 689 |
+
copyBtn.textContent='π MP4λ³νμ© λ³΅μ¬';
|
| 690 |
+
copyBtn.onclick=async function(){{
|
| 691 |
+
var reader=new FileReader();
|
| 692 |
+
reader.onload=function(){{
|
| 693 |
+
var base64=reader.result.split(',')[1];
|
| 694 |
+
navigator.clipboard.writeText(base64).then(function(){{
|
| 695 |
+
alert('λ³΅μ¬ μλ£!\\n\\nμ Gradioμ "WebM λ°μ΄ν°" μ
λ ₯λμ λΆμ¬λ£κΈ°(Ctrl+V) ν\\n"MP4 λ³ν" λ²νΌμ ν΄λ¦νμΈμ.');
|
| 696 |
+
}}).catch(function(){{
|
| 697 |
+
prompt('μλ λ°μ΄ν°λ₯Ό 볡μ¬νμΈμ (Ctrl+A, Ctrl+C):', base64);
|
| 698 |
+
}});
|
| 699 |
+
}};
|
| 700 |
+
reader.readAsDataURL(webmBlob);
|
| 701 |
+
}};
|
| 702 |
+
downloadDiv.appendChild(copyBtn);
|
| 703 |
+
document.querySelector('.modal-box').appendChild(downloadDiv);
|
| 704 |
}}
|
| 705 |
|
| 706 |
function cancelExport(){{S.cancelled=true;document.getElementById('exportModal').style.display='none'}}
|
|
|
|
| 844 |
print(f"[Export] Error: {e}")
|
| 845 |
return None
|
| 846 |
|
| 847 |
+
def convert_webm_to_mp4(webm_base64):
|
| 848 |
+
"""WebM base64 λ°μ΄ν°λ₯Ό MP4λ‘ λ³ν"""
|
| 849 |
+
if not webm_base64 or len(webm_base64) < 100:
|
| 850 |
+
return None
|
| 851 |
+
|
| 852 |
+
try:
|
| 853 |
+
# base64 λμ½λ©
|
| 854 |
+
webm_data = base64.b64decode(webm_base64)
|
| 855 |
+
|
| 856 |
+
temp_dir = tempfile.mkdtemp()
|
| 857 |
+
webm_path = os.path.join(temp_dir, 'input.webm')
|
| 858 |
+
mp4_path = os.path.join(temp_dir, f'output_{int(time.time())}.mp4')
|
| 859 |
+
|
| 860 |
+
# WebM νμΌ μ μ₯
|
| 861 |
+
with open(webm_path, 'wb') as f:
|
| 862 |
+
f.write(webm_data)
|
| 863 |
+
|
| 864 |
+
# FFmpegλ‘ MP4 λ³ν
|
| 865 |
+
cmd = [
|
| 866 |
+
'ffmpeg', '-y',
|
| 867 |
+
'-i', webm_path,
|
| 868 |
+
'-c:v', 'libx264',
|
| 869 |
+
'-preset', 'fast',
|
| 870 |
+
'-crf', '23',
|
| 871 |
+
'-c:a', 'aac',
|
| 872 |
+
'-b:a', '128k',
|
| 873 |
+
'-movflags', '+faststart',
|
| 874 |
+
mp4_path
|
| 875 |
+
]
|
| 876 |
+
|
| 877 |
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
| 878 |
+
|
| 879 |
+
# WebM νμΌ μμ
|
| 880 |
+
try:
|
| 881 |
+
os.remove(webm_path)
|
| 882 |
+
except:
|
| 883 |
+
pass
|
| 884 |
+
|
| 885 |
+
if os.path.exists(mp4_path) and os.path.getsize(mp4_path) > 0:
|
| 886 |
+
return mp4_path
|
| 887 |
+
|
| 888 |
+
return None
|
| 889 |
+
|
| 890 |
+
except Exception as e:
|
| 891 |
+
print(f"[Convert] Error: {e}")
|
| 892 |
+
return None
|
| 893 |
+
|
| 894 |
with gr.Blocks() as demo:
|
| 895 |
gr.Markdown("## π¬ Video Editor")
|
| 896 |
|
|
|
|
| 898 |
e = gr.HTML(value=make_iframe([]))
|
| 899 |
|
| 900 |
gr.Markdown("---")
|
| 901 |
+
gr.Markdown("### π₯ MP4 λ³ν")
|
| 902 |
+
gr.Markdown("μλν°μμ 'λ΄λ³΄λ΄κΈ°' β 'MP4λ³νμ© λ³΅μ¬' ν΄λ¦ ν μλμ λΆμ¬λ£κΈ°")
|
| 903 |
|
| 904 |
with gr.Row():
|
| 905 |
+
webm_data = gr.Textbox(label="WebM λ°μ΄ν° (base64)", placeholder="μ¬κΈ°μ λΆμ¬λ£κΈ° (Ctrl+V)", lines=2, scale=4)
|
| 906 |
+
convert_btn = gr.Button("π¬ MP4 λ³ν", variant="primary", scale=1)
|
| 907 |
|
| 908 |
mp4_output = gr.File(label="π₯ MP4 λ€μ΄λ‘λ")
|
| 909 |
|
| 910 |
f.change(fn=lambda x: make_iframe(process_file(x)), inputs=[f], outputs=[e])
|
| 911 |
+
convert_btn.click(fn=convert_webm_to_mp4, inputs=[webm_data], outputs=[mp4_output])
|
| 912 |
|
| 913 |
if __name__ == "__main__":
|
| 914 |
demo.launch()
|