Skip to content

Instantly share code, notes, and snippets.

@euske
Created October 11, 2021 02:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save euske/a67389b664ace6ba8bcf576678e623af to your computer and use it in GitHub Desktop.
Save euske/a67389b664ace6ba8bcf576678e623af to your computer and use it in GitHub Desktop.
Offline video editor
<!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);">&lt;&lt; 10s</button>
<button onclick="step(+10);">10s &gt;&gt;</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