145 lines
5.2 KiB
Lua
145 lines
5.2 KiB
Lua
|
|
local TMP_FOLDER = '/tmp/memebot/'
|
|
|
|
local internet = require 'internet'
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
local function check_file_exists (filename)
|
|
assert(type(filename) == 'string')
|
|
local status = os.execute('test -e "'..filename..'"')
|
|
return status == 0
|
|
end
|
|
|
|
local function ensure_tmp_folder_exists ()
|
|
os.execute(('mkdir --parents "%s"'):format(TMP_FOLDER))
|
|
end
|
|
|
|
local function video_length (video_filename)
|
|
assert(type(video_filename) == 'string')
|
|
assert(check_file_exists(video_filename))
|
|
--
|
|
local f = io.popen(('ffprobe -i "%s" -show_entries format=duration -v quiet -of csv="p=0"'):format(video_filename), 'r')
|
|
local str = f:read '*all'
|
|
f:close()
|
|
local len = tonumber(str)
|
|
if type(len) ~= 'number' then
|
|
error('Could not determine length of file "'..video_filename..'". ffprobe had this to say: '..str)
|
|
end
|
|
--
|
|
return len
|
|
end
|
|
|
|
local function video_silences (video_filename, decibel, duration)
|
|
assert(type(video_filename) == 'string')
|
|
assert(check_file_exists(video_filename))
|
|
assert(type(decibel) == 'number')
|
|
assert(type(duration) == 'number')
|
|
--
|
|
local f = io.popen(('ffmpeg -loglevel panic -y -i "%s" -af silencedetect=n=%sdB:d=%s -f null - 1> /dev/null'):format(video_filename, decibel, duration, log_filename))
|
|
local silences = {}
|
|
for line in f:lines() do
|
|
local silence_end, duration = line:gsub('\027%[.-;', ''):match '^%[silencedetect @ 0x%x+%]%s*silence_end:%s*([%d.]+)%s*|%s*silence_duration:%s*([%d.]+)$'
|
|
if silence_end then
|
|
local silence_end, duration = tonumber(silence_end), tonumber(duration)
|
|
silences[#silences+1] = {
|
|
start = silence_end - duration,
|
|
stop = silence_end,
|
|
duration = duration
|
|
}
|
|
end
|
|
end
|
|
f:close()
|
|
return silences
|
|
end
|
|
|
|
local function video_silence_at_end (video_filename, ...)
|
|
assert(type(video_filename) == 'string')
|
|
assert(check_file_exists(video_filename))
|
|
--
|
|
local silences = video_silences(video_filename, ...)
|
|
local duration = video_length(video_filename)
|
|
--
|
|
for _, silence in ipairs(silences) do
|
|
if math.abs(silence.stop - duration) < 0.1 then
|
|
return silence
|
|
end
|
|
end
|
|
--
|
|
return nil
|
|
end
|
|
|
|
local function paste_audio_onto_video (video_filename, audio_filename, timestamp)
|
|
timestamp = timestamp or 0
|
|
assert(type(timestamp) == 'number')
|
|
assert(type(video_filename) == 'string')
|
|
assert(type(audio_filename) == 'string')
|
|
|
|
local silence_filename = TMP_FOLDER..'silence.mp3'
|
|
local output_filename = TMP_FOLDER..'output.webm'
|
|
|
|
-- Generate silence
|
|
os.execute(('ffmpeg -loglevel panic -y -f lavfi -i anullsrc=channel_layout=5.1:sample_rate=48000 -t %s %s'):format(timestamp, silence_filename))
|
|
|
|
-- Paste audio onto video
|
|
os.execute(('ffmpeg -loglevel panic -y -i "%s" -i "concat:%s|%s" -filter_complex "[0:a:0][1:a:0]amerge=inputs=2[a]" -map 0:v:0 -map "[a]" -c:v copy -c:a libvorbis -ac 2 -shortest %s'):format(video_filename, silence_filename, audio_filename, output_filename))
|
|
|
|
-- Remove redundant silence
|
|
os.remove(silence_filename)
|
|
|
|
return output_filename
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
local CURB_YOUR_ENTHUSIASM_THEME_FILENAME = './sound/curb_your_enthusiasm_theme.mp3'
|
|
local DEFAULT_THEME_LENGTH = 10
|
|
|
|
local function determine_timestamp_for_curb_your_video (video_filename)
|
|
assert(type(video_filename) == 'string')
|
|
--
|
|
local silence_start
|
|
local silence = video_silence_at_end(video_filename, -18, 1)
|
|
local len = video_length(video_filename)
|
|
local silence_end = len
|
|
local silence_start = silence and silence.start or (len - 5)
|
|
local silence_diff = silence_end - silence_start
|
|
|
|
silence_end = math.max(len/2, silence_end - math.min(5, silence_diff))
|
|
local silence_diff = silence_end - silence_start
|
|
silence_start = math.max(len/2, silence_start + math.min(3, silence_diff))
|
|
|
|
assert(len/2 <= silence_start and silence_start <= len)
|
|
assert(len/2 <= silence_end and silence_end <= len)
|
|
return (silence_start + silence_end) / 2
|
|
end
|
|
|
|
local function curb_your_video (video_url, timestamp)
|
|
assert(type(video_url) == 'string')
|
|
assert(type(timestamp) == 'number' or timestamp == nil)
|
|
|
|
-- Download video
|
|
ensure_tmp_folder_exists()
|
|
local video_filename = internet.download_video(video_url)
|
|
assert(check_file_exists(video_filename))
|
|
|
|
-- Figure out timestamp to start theme
|
|
if timestamp == nil then
|
|
timestamp = determine_timestamp_for_curb_your_video(video_filename)
|
|
assert(type(timestamp) == 'number')
|
|
assert(timestamp < video_length(video_filename))
|
|
elseif timestamp < 0 then
|
|
timestamp = video_length(video_filename) + timestamp
|
|
end
|
|
|
|
print('Timestamp', timestamp, video_filename)
|
|
|
|
-- Paste audio
|
|
return paste_audio_onto_video(video_filename, CURB_YOUR_ENTHUSIASM_THEME_FILENAME, timestamp)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
return { your_video = curb_your_video }
|
|
|