Skip to content

Instantly share code, notes, and snippets.

@mie00
Last active November 4, 2021 19:28
Show Gist options
  • Save mie00/38b98dce5867a2554b8f40dd3b7cd1f1 to your computer and use it in GitHub Desktop.
Save mie00/38b98dce5867a2554b8f40dd3b7cd1f1 to your computer and use it in GitHub Desktop.
tool to run ffmpeg for audio, video merging, scaling, subtitles adding
#!/usr/bin/env python3
# usage: eval `./runner.py VID_20200321_142302.mp4 Recording_9.m4a 0 00:01:40.76 45.2 p3oc.mp4 s3.srt`
import sys
import os
import tempfile
if len(sys.argv) not in [7, 8]:
print("runner has to run with `runner <video> <audio> <start> <end> <audio_trim> <output> [<subtitle file>]`")
exit(1)
subtitles = None
if len(sys.argv) == 8:
subtitles = sys.argv.pop()
[runner, video, audio, start, end, audio_trim, output] = sys.argv
def parse_time(t):
if isinstance(t, (bytes, bytearray)):
t = str(t, "utf-8")
t = t.replace(',', '.')
xs = t.split(":")
if len(xs) not in [1, 2, 3]:
raise Exception("cannot parse %r time"%(t))
tot = 0
for i in xs:
tot = tot * 60 + float(i)
return tot
def repr_time(t):
s = t % 60
t = int(t / 60)
m = t % 60
t = int(t / 60)
h = t
return "%02.0f:%02.0f:%02.3f"%(h, m, s)
fstart = parse_time(start)
fend = parse_time(end)
faudio_trim = parse_time(audio_trim)
start_video = fstart
duration_video = fend - fstart
start_audio = fstart + faudio_trim
subtitles_arg = ""
if subtitles is not None:
fd, name = tempfile.mkstemp(suffix=".srt")
with open(subtitles, 'rb') as f:
ind = 0
for line in f:
if ind == 1:
ender = b''
if line.endswith(b'\r\n'):
ender = b'\r\n'
elif line.endswith(b'\n'):
ender = b'\n'
[s, sep, e] = line.strip().split(b' ')
line = b"%s %s %s%s"%(bytes(repr_time(parse_time(s.replace(b',', b'.')) - fstart).replace('.', ','), 'utf-8'), sep, bytes(repr_time(parse_time(e.replace(b',', b'.')) - fstart).replace('.', ','), 'utf-8'), ender)
ind += 1
if line.strip() == b'':
ind = 0
os.write(fd, line)
subtitles_arg = "-vf subtitles=%r"%(name)
try:
command = "ffmpeg -ss %0.3f -accurate_seek -t %0.3f -i %r -ss %0.3f -accurate_seek -t %0.3f -i %r -c:a aac -strict experimental -map 0:v:0 -map 1:a:0 -vf scale=1280x720 -sws_flags bilinear %s -af aresample=44100 %r"%(start_video, duration_video, video, start_audio, duration_video, audio, subtitles_arg, output)
print(command)
finally:
if subtitles is not None:
os.close(fd)
# os.remove(name)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
</head>
<body>
<video id="vid" width="1024" height="720" controls autobuffer preload>
Your browser does not support the video tag.
</video>
<br />
<br />
<br />
<div id="audio">
Drag audio here
</div>
<br />
Start: <input id="start" /><button id="start-btn">S</button>
<br />
End: <input id="end" /><button id="end-btn">S</button>
<br />
Lag: <input id="lag" /><button id="lag-btn">Calculate</button>
<br />
<div id="subtitles">
</div>
<button id="add-subtitle-btn">Add Subtitle</button>
<br />
<button id="create">ffmpeg_runner</button>
<input type=textarea" id="ffmpeg" />
<script>
var rate = 30
var vidfile;
var audfile;
var vid = document.getElementById("vid");
vid.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
for (const f of e.dataTransfer.files) {
vidfile = f.path;
vid.src = f.path;
}
});
vid.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
});
var aud = document.getElementById("audio");
aud.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
for (const f of e.dataTransfer.files) {
audfile = f.path;
aud.innerHTML = f.path;
}
});
aud.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
});
function next_frame() {
vid.currentTime += 1/rate;
}
function previous_frame() {
vid.currentTime -= 1/rate;
}
function next() {
vid.currentTime += 5;
}
function previous() {
vid.currentTime -= 5;
}
function start() {
vid.currentTime = 0;
}
function pause() {
vid.pause();
}
function play() {
vid.play();
}
function toggle() {
vid.paused?vid.play():vid.pause();
}
var ti;
function interval (fn) {
if (ti !== undefined) {
window.cancelAnimationFrame(ti)
}
fn();
ti = window.requestAnimationFrame(fn, 100);
}
document.addEventListener("keydown", event => {
console.log(event.keyCode);
if (event.target && event.target.tagName === "INPUT") {
return;
}
if (event.keyCode === 32) {
toggle();
event.preventDefault();
} else if(event.keyCode === 37) {
interval(previous);
event.preventDefault();
} else if(event.keyCode === 39) {
interval(next);
event.preventDefault();
} else if(event.keyCode === 13) {
start();
event.preventDefault();
} else if(event.keyCode === 74) {
interval(previous_frame);
event.preventDefault();
} else if(event.keyCode === 75) {
interval(next_frame);
event.preventDefault();
}
});
document.addEventListener("keyup", event => {
window.cancelAnimationFrame(ti);
ti = undefined;
});
function setfn(id) {
return function(event){
document.getElementById(id).value = vid.currentTime;
}
}
const ipcRenderer = require('electron').ipcRenderer;
function calculate_lag() {
if(vidfile === undefined) {
alert("video is not here");
return;
}
if(audfile === undefined) {
alert("audio is not here");
return;
}
document.getElementById("lag").value = ipcRenderer.sendSync('synchronous-message', [vidfile, audfile]).trim();
}
document.getElementById("start-btn").addEventListener("click", setfn("start"));
document.getElementById("end-btn").addEventListener("click", setfn("end"));
document.getElementById("lag-btn").addEventListener("click", calculate_lag);
document.getElementById("add-subtitle-btn").addEventListener("click", (event) => {
var ss = document.getElementById("subtitles");
var c = document.createElement('p');
c.innerHTML = `
<input placeholder="start" /><button>S</button>
<input placeholder="end" /><button>S</button>
<input placeholder="text" style="width: 500px" />
<button>X</button>
`;
ss.appendChild(c);
c.children[0].value = vid.currentTime;
c.children[1].addEventListener("click", () => c.children[0].value = vid.currentTime);
c.children[3].addEventListener("click", () => c.children[2].value = vid.currentTime);
c.children[5].addEventListener("click", () => c.remove());
});
function create() {
if(vidfile === undefined) {
alert("video is not here");
return;
}
if(audfile === undefined) {
alert("audio is not here");
return;
}
var start = Number(document.getElementById("start").value);
var end = document.getElementById("end").value;
var lag = document.getElementById("lag").value;
var subtitles = document.getElementById("subtitles").children;
if(start === '') {
alert("start is not here");
return;
}
if(end === '') {
alert("end is not here");
return;
}
if(lag === '') {
alert("lag is not here");
return;
}
var subs = [];
for (var c of subtitles) {
var st = c.children[0].value;
var en = c.children[2].value;
var tx = c.children[4].value;
if(st === '') {
alert("start for subtitle is not here");
return;
}
if(en === '') {
alert("end for subtitle is not here");
return;
}
if(tx === '') {
alert("text for subtitle is not here");
return;
}
subs.append({
"start": Number(st),
"end": Number(en),
"text": tx,
})
}
document.getElementById("ffmpeg").value = ipcRenderer.sendSync('ffmpeg', {vidfile, audfile, start, end, lag, subs}).trim();
}
document.getElementById("create").addEventListener("click", create);
</script>
</body>
</html>
#!/usr/bin/env python
with open('lag-audio.raw', 'rb') as f:
x = f.read()
with open('lag-video.raw', 'rb') as f:
y = f.read()
import numpy as np
a = np.frombuffer(x, dtype=np.int16)
v = np.frombuffer(y, dtype=np.int16)
m = None
ii = -1
from tqdm import tqdm
import math
START = len(v) / 2
RATE = 44100
T = 2 * RATE
JUMP = RATE/100
tt = tqdm(range(0, len(a) - T, JUMP))
for i in tt:
r = np.corrcoef(a[i:i+T], v[START:START + T])[0,1]
if m is None or m < r or math.isnan(m):
m = r
ii = i
tt.set_description("setting m to " + str(m) + " ii to " + str(ii))
tt = tqdm(range(ii - JUMP, ii + JUMP + 1))
for i in tt:
r = np.corrcoef(a[i:i+T], v[START:START + T])[0,1]
if m is None or m < r or math.isnan(m):
m = r
ii = i
tt.set_description("setting m to " + str(m) + " ii to " + str(ii))
na = a[ii:]
nv = v[START:]
with open('lag-newaudio.raw', 'wb') as f:
f.write(na.tobytes())
with open('lag-newvideo.raw', 'wb') as f:
f.write(nv.tobytes())
print(1.0 * (ii - START) / RATE)
#!/bin/bash
if [ "$#" != "2" ]; then
echo "usage lag <video> <audio>"
exit 1
fi
ffmpeg -y -ss 10 -accurate_seek -t 02:00 -i $2 -f s16le -acodec pcm_s16le -ar 44100 lag-audio.raw
ffmpeg -y -ss 10 -accurate_seek -t 02:00 -i $1 -f s16le -acodec pcm_s16le -ar 44100 -map_channel 0.1.0 lag-video.raw
I=`python2 lag.py`
ffmpeg -y -f s16le -acodec pcm_s16le -ar 44100 -i lag-newaudio.raw lag-audio.wav
ffmpeg -y -f s16le -acodec pcm_s16le -ar 44100 -i lag-newvideo.raw lag-video.wav
ffmpeg -y -i lag-audio.wav -i lag-video.wav -filter_complex "[0:a][1:a]amerge=inputs=2[a]" -map "[a]" lag-total.wav
vlc lag-total.wav
echo "=================================================="
echo $I
const { app, BrowserWindow, dialog, ipcMain } = require('electron')
ipcMain.on('synchronous-message', function(event, arg) {
var child = require('child_process').execFile;
var executablePath = "/home/mie/aie/New/lag.sh"
var parameters = arg;
console.log(arg)
child(executablePath, parameters, function(err, data) {
console.log(err)
event.returnValue = data.toString();
});
});
var util = require("util");
var tmp = require("tmp");
var fs = require("fs");
function format_time(t) {
return util.format("%02f:%02f:%02.3f", t/60/60, t/60, t).replace('.', ',');
}
ipcMain.on('ffmpeg', function(event, arg) {
var {vidfile, audfile, start, end, lag, subs} = arg;
var subname;
if (subs.length > 0) {
var t = "";
for (var i in subs) {
t += (i + 1) + "\r\n";
t += format_time(s.start) + ' --> ' + format_time(s.end) + "\r\n";
t += s.text;
t += '\r\n\r\n';
}
var tmpobj = tmp.fileSync({ mode: 0644, prefix: 'subs-', postfix: '.srt' });
subname = tmpobj.name;
fs.writeSync(tmpobj.fd, t);
fs.closeSync(tmpobj.fd);
}
var child = require('child_process').execFile;
var executablePath = "ffmpeg_runner"
var output = vidfile.replace(/\.mp4$/, "-processed.mp4")
var parameters = [vidfile, audfile, start, end, lag, output];
if (subname !== undefined) {
parameters.append(subname);
}
child(executablePath, parameters, function(err, data) {
console.log(err)
event.returnValue = data.toString();
});
});
function createWindow () {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
win.loadFile('index.html')
// Open the DevTools.
// win.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^8.2.0"
},
"dependencies": {
"tmp": "^0.1.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment