Created
October 11, 2021 02:27
-
-
Save euske/a67389b664ace6ba8bcf576678e623af to your computer and use it in GitHub Desktop.
Offline video editor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |
<title>Video Trimmer</title> | |
<style> | |
h1 { border-bottom: solid black 2px; } | |
.selector { border: solid black 1px; margin: 4px; padding: 4px; } | |
.player { border: solid black 1px; margin: 4px; padding: 4px; } | |
.controls { border: solid black 1px; margin: 4px; padding: 4px; } | |
.segments { border: solid black 1px; margin: 4px; padding: 4px; } | |
.commandline { border: solid black 1px; margin: 4px; padding: 4px; } | |
#video { position: relative; margin: 0; padding: 0; } | |
#time { position: absolute; top: 0; left: 0; margin: 0; padding: 4px; | |
font-family: monospace; font-size: 200%; | |
color: white; background: rgba(0,0,0, 0.7); | |
} | |
</style> | |
<script> | |
function convTime(v) { | |
let s = Math.floor(v*10).toString(); | |
if (s < 10) { | |
return "0."+s; | |
} else { | |
return s.substring(0, s.length-1)+"."+s.substring(s.length-1); | |
} | |
} | |
function setup() { | |
let video = document.getElementById("video"); | |
if (video === null) return; | |
let time = document.getElementById("time"); | |
if (time === null) return; | |
function update() { | |
time.innerHTML = convTime(video.currentTime); | |
} | |
update_buttons(); | |
window.setInterval(update, 33); | |
} | |
function get_path() { | |
let file = document.getElementById("file"); | |
if (file === null) return null; | |
let path = file.value; | |
if (!path) return null; | |
let i = path.lastIndexOf('/'); | |
if (i < 0) { i = path.lastIndexOf('\\'); } | |
if (i < 0) return null; | |
return path.substring(i+1); | |
} | |
function set_video() { | |
let video = document.getElementById("video"); | |
if (video === null) return; | |
video.src = get_path(); | |
video.play(); | |
} | |
function step(dt) { | |
let video = document.getElementById("video"); | |
if (video === null) return; | |
video.currentTime += dt; | |
} | |
function rec_start() { | |
let video = document.getElementById("video"); | |
if (video === null) return; | |
let f_start = document.getElementById("f_start"); | |
if (f_start === null) return; | |
let startTime = convTime(video.currentTime); | |
f_start.innerHTML = startTime; | |
update_buttons(); | |
} | |
function rec_end() { | |
let video = document.getElementById("video"); | |
if (video === null) return; | |
let f_start = document.getElementById("f_start"); | |
if (f_start === null) return; | |
let segs = document.getElementById("segs"); | |
if (segs === null) return; | |
let endTime = convTime(video.currentTime); | |
let startTime = f_start.innerHTML; | |
if (!startTime) return; | |
if (segs.value) segs.value += " "; | |
segs.value += startTime+"-"+endTime; | |
f_start.innerHTML = ""; | |
update_buttons(); | |
gen_cmd(); | |
} | |
function update_buttons() { | |
let b_start = document.getElementById("b_start"); | |
if (b_start === null) return; | |
let b_end = document.getElementById("b_end"); | |
if (b_end === null) return; | |
let f_start = document.getElementById("f_start"); | |
if (f_start === null) return; | |
if (f_start.innerHTML) { | |
b_start.hidden = true; | |
b_end.hidden = false; | |
} else { | |
b_start.hidden = false; | |
b_end.hidden = true; | |
} | |
} | |
function gen_cmd() { | |
let video = document.getElementById("video"); | |
if (video === null) return; | |
let segs = document.getElementById("segs"); | |
if (segs === null) return; | |
let cmdline = document.getElementById("cmdline"); | |
if (cmdline === null) return; | |
let a = segs.value.split(/[ ,]/); | |
let s = 'ffmpeg -i '+get_path()+' -filter_complex "'; | |
let n = 0; | |
for (let c of a) { | |
if (c) { | |
s += '[0:v]trim='+c.replace('-',':')+',setpts=PTS-STARTPTS[v'+n+'];'; | |
s += '[0:a]atrim='+c.replace('-',':')+',asetpts=PTS-STARTPTS[a'+n+'];'; | |
n++; | |
} | |
} | |
for (let i = 0; i < n; i++) { | |
s += '[v'+i+'][a'+i+']'; | |
} | |
s += 'concat=n='+n+':v=1:a=1[out]"'; | |
s += ' -map "[out]" -b:v 1m output.mp4'; | |
cmdline.innerHTML = s; | |
} | |
</script> | |
<body onload="setup();"> | |
<h1>Video Trimmer</h1> | |
<p> | |
<strong>使い方:</strong> | |
<ol> | |
<li> 編集したい動画ファイルを「動画入力:」欄に入れます。 | |
(<strong>注意:</strong> 動画ファイルは同一フォルダ上に存在している必要があります。) | |
<li> 切り取りを開始したい部分に移動して「ここから」ボタンを押します。 | |
<li> 切り取りを終了したい部分に移動して「ここまで」ボタンを押します。 | |
<li> ステップ 2. 〜 3. を繰り返すと、「選択範囲」欄に範囲が追加されていきます。(手で編集することもできます。) | |
<li> 「コマンドライン出力」欄に表示されたコマンドをシェル上で実行します。(要ffmpeg) | |
</ol> | |
<div class=selector> | |
動画入力: <input id="file" type="file" oninput="set_video()"> | |
</div> | |
<div class=player style="position: relative;"> | |
<video id="video" controls height="480"> | |
</video> | |
<div id="time">0</div> | |
</div> | |
<div class=controls> | |
<button onclick="step(-10);"><< 10s</button> | |
<button onclick="step(+10);">10s >></button> | |
<button id="b_start" onclick="rec_start();">ここから</button> | |
<button id="b_end" onclick="rec_end();">ここまで</button> | |
<span id="f_start"></span> | |
</div> | |
<div class=segments> | |
選択範囲:<br> | |
<textarea id="segs" cols="80" oninput="gen_cmd();"></textarea> | |
</div> | |
<div class=commandline> | |
コマンドライン出力:<br> | |
<textarea id="cmdline" cols="80" rows="5" disabled="true"></textarea> | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment