Last active
November 4, 2021 19:28
-
-
Save mie00/38b98dce5867a2554b8f40dd3b7cd1f1 to your computer and use it in GitHub Desktop.
tool to run ffmpeg for audio, video merging, scaling, subtitles adding
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
#!/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) |
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 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> | |
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
#!/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) |
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
#!/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 |
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
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. |
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
{ | |
"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