123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- -- cutitout: automatically cut silence from videos
- -- Copyright (C) 2020 Wolf Clement
- -- This program is free software: you can redistribute it and/or modify
- -- it under the terms of the GNU Affero General Public License as published
- -- by the Free Software Foundation, either version 3 of the License, or
- -- (at your option) any later version.
- -- This program is distributed in the hope that it will be useful,
- -- but WITHOUT ANY WARRANTY; without even the implied warranty of
- -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- -- GNU Affero General Public License for more details.
- -- You should have received a copy of the GNU Affero General Public License
- -- along with this program. If not, see <https://www.gnu.org/licenses/>.
- -- Margin before and after a clip (in seconds)
- local clip_margin = 0.4
- assert(clip_margin >= 0.0)
- -- How loud should noise be to be considered a sound?
- local audio_treshold = 0.01
- assert(audio_treshold > 0.0 and audio_treshold <= 1.0)
- -- Minimum clip length (in seconds)
- -- Sounds shorter than that will be considered noise and cut.
- local min_clip_length = 0.2
- assert(min_clip_length > 0.0)
- -- Minimum silence length to skip (in seconds)
- local min_skip_length = 2.0
- assert(min_skip_length > 2 * clip_margin)
- local enabled = false
- local minutes = {}
- local skips = {}
- function fix_boundaries()
- -- Merge skips across minutes (required for next step)
- for ia, a in pairs(skips) do
- for ib, b in pairs(skips) do
- if b[1] > a[1] and (b[1] - a[2] < min_clip_length) then
- table.insert(skips, {a[1], b[2]})
- if ia > ib then
- table.remove(skips, ia)
- table.remove(skips, ib)
- else
- table.remove(skips, ib)
- table.remove(skips, ia)
- end
- return fix_boundaries()
- end
- end
- end
- -- Add margin when someone stops talking right as the minute rolls over
- for _, a in pairs(skips) do
- if a[1] > 0.0 and a[1] % 60.0 == 0.0 then
- a[1] = a[1] + clip_margin
- end
- end
- end
- function load_minute(minute)
- if minutes[minute] == "loading" then return end
- local utils = require("mp.utils")
- local scripts_dir = mp.find_config_file("scripts")
- local cutitoutpy = utils.join_path(scripts_dir, "cutitout_shared/cutitout.py")
- local video_path = mp.get_property("path")
- minutes[minute] = "loading"
- mp.command_native_async({
- name = "subprocess",
- capture_stdout = true,
- playback_only = false,
- args = {
- "python3", cutitoutpy, video_path, tostring(minute - 1),
- tostring(clip_margin), tostring(audio_treshold),
- tostring(min_clip_length), tostring(min_skip_length),
- }
- }, function(res, val, err)
- new_skips = loadstring(val.stdout)()
- local seconds_skippable = 0.0
- for _, skip in pairs(new_skips) do
- table.insert(skips, skip)
- seconds_skippable = seconds_skippable + (skip[2] - skip[1])
- end
- fix_boundaries()
- minutes[minute] = "loaded"
- print(tostring(#new_skips) .. " skips loaded for minute " .. tostring(minute - 1))
- print(tostring(math.floor(seconds_skippable)) .. " seconds skippable in minute " .. tostring(minute - 1))
- -- If majority of the minute is skippable, keep looking for skips
- -- (so silent videos portions lasting more than 2 minutes get skipped in one go)
- if seconds_skippable > 30 then
- load_minute(minute + 1)
- end
- end)
- end
- -- Whenever time updates...
- mp.observe_property("time-pos", "native", function (_, pos)
- if pos == nil then return end
- if not enabled then return end
- -- Check if we should load skips for the current minute
- local current_minute = math.floor(pos / 60.0) + 1
- if minutes[current_minute] == nil then
- load_minute(current_minute)
- end
- -- Check if we should load skips for the next minute
- local next_minute = math.ceil((pos + 1.0) / 60.0) + 1
- if minutes[current_minute] == "loaded" and minutes[next_minute] == nil then
- load_minute(next_minute)
- end
- -- Check if we should skip
- for _, t in pairs(skips) do
- -- t[1] == start time of the skip
- -- t[2] == end time of the skip
- if t[1] <= pos and t[2] > pos then
- print("Skipping to " .. t[2])
- mp.set_property("time-pos", t[2])
- return
- end
- end
- end)
- -- F12 toggles silence skipping (off by default)
- mp.add_key_binding("F12", "toggle_silence_skip", function ()
- enabled = not enabled
- if enabled then
- mp.osd_message("Silence skipping enabled.")
- else
- mp.osd_message("Silence skipping disabled.")
- end
- end)
- -- Whenever we load another file, we reset skips
- mp.register_event("file-loaded", function ()
- minutes = {}
- skips = {}
- end)
|