import subprocess import tempfile import sys import os import logging log_level = logging.ERROR log_filename = 'silence_cutter.log' logger = logging.getLogger('') logger.setLevel(log_level) log_handler = logging.FileHandler(log_filename, delay=True) logger.addHandler(log_handler) def findSilences(filename, dB = -25): """ returns a list: even elements (0,2,4, ...) denote silence start time uneven elements (1,3,5, ...) denote silence end time """ logging.debug(f"findSilences ()") logging.debug(f" - filename = {filename}") logging.debug(f" - dB = {dB}") command = ["ffmpeg","-i",filename, "-af","silencedetect=n=" + str (dB) + "dB:d=1", "-f","null","-"] output = subprocess.run (command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) s = str(output) lines = s.split("\\n") time_list = [] logging.debug(" lines: ```\n" + "\n".join(lines) + "```\n\n") for line in lines: if ("silencedetect" in line): words = line.split(" ") logging.debug(" words: " + str(words)) for i in range (len(words)): if ("silence_start" in words[i]): time_list.append (float(words[i+1])) if "silence_end" in words[i]: time_list.append (float (words[i+1])) silence_section_list = list (zip(*[iter(time_list)]*2)) #return silence_section_list return time_list def getVideoDuration(filename:str) -> float: logging.debug(f"getVideoDuration ()") logging.debug(f" - filename = {filename}") command = ["ffprobe","-i",filename,"-v","quiet", "-show_entries","format=duration","-hide_banner", "-of","default=noprint_wrappers=1:nokey=1"] output = subprocess.run (command, stdout=subprocess.PIPE) s = str(output.stdout, "UTF-8") return float (s) def getSectionsOfNewVideo (silences, duration): """Returns timings for parts, where the video should be kept""" return [0.0] + silences + [duration] def ffmpeg_filter_getSegmentFilter(videoSectionTimings): ret = "" for i in range (int (len(videoSectionTimings)/2)): start = videoSectionTimings[2*i] end = videoSectionTimings[2*i+1] ret += "between(t," + str(start) + "," + str(end) + ")+" # cut away last "+" ret = ret[:-1] return ret def getFileContent_videoFilter(videoSectionTimings): ret = "select='" ret += ffmpeg_filter_getSegmentFilter (videoSectionTimings) ret += "', setpts=N/FRAME_RATE/TB" return ret def getFileContent_audioFilter(videoSectionTimings): ret = "aselect='" ret += ffmpeg_filter_getSegmentFilter (videoSectionTimings) ret += "', asetpts=N/SR/TB" return ret def writeFile (filename, content): logging.debug(f"writeFile ()") logging.debug(f" - filename = {filename}") with open (filename, "w") as file: file.write (str(content)) def ffmpeg_run (file, videoFilter, audioFilter, outfile): logging.debug(f"ffmpeg_run ()") # prepare filter files vFile = tempfile.NamedTemporaryFile (mode="w", encoding="UTF-8", prefix="silence_video") aFile = tempfile.NamedTemporaryFile (mode="w", encoding="UTF-8", prefix="silence_audio") videoFilter_file = vFile.name #"/tmp/videoFilter" # TODO: replace with tempfile audioFilter_file = aFile.name #"/tmp/audioFilter" # TODO: replace with tempfile writeFile (videoFilter_file, videoFilter) writeFile (audioFilter_file, audioFilter) command = ["ffmpeg","-i",file, "-filter_script:v",videoFilter_file, "-filter_script:a",audioFilter_file, outfile] subprocess.run (command) vFile.close() aFile.close() def cut_silences(infile, outfile, dB = -35): logging.debug(f"cut_silences ()") logging.debug(f" - infile = {infile}") logging.debug(f" - outfile = {outfile}") logging.debug(f" - dB = {dB}") print ("detecting silences") silences = findSilences (infile,dB) duration = getVideoDuration (infile) videoSegments = getSectionsOfNewVideo (silences, duration) videoFilter = getFileContent_videoFilter (videoSegments) audioFilter = getFileContent_audioFilter (videoSegments) print ("create new video") ffmpeg_run (infile, videoFilter, audioFilter, outfile) def printHelp(): print ("Usage:") print (" silence_cutter.py [infile] [optional: outfile] [optional: dB]") print (" ") print (" [outfile]") print (" Default: [infile]_cut") print (" ") print (" [dB]") print (" Default: -30") print (" A suitable value might be around -50 to -35.") print (" The lower the more volume will be defined das 'silent'") print (" -30: Cut Mouse clicks and mouse movent; cuts are very recognizable.") print (" -35: Cut inhaling breath before speaking; cuts are quite recognizable.") print (" -40: Cuts are almost not recognizable.") print (" -50: Cuts are almost not recognizable.") print (" Cuts nothing, if there is background noise.") print (" ") print ("") print ("Dependencies:") print (" ffmpeg") print (" ffprobe") def main(): logging.debug(f"main ()") args = sys.argv[1:] if (len(args) < 1): printHelp() return if (args[0] == "--help"): printHelp() return infile = args[0] if (not os.path.isfile (infile)): print ("ERROR: The infile could not be found:\n" + infile) return # set default values for optionl arguments tmp = os.path.splitext (infile) outfile = tmp[0] + "_cut" + tmp[1] dB = -20 if (len(args) >= 2): outfile = args[1] if (len(args) >= 3): dB = args[2] cut_silences (infile, outfile, dB) if __name__ == "__main__": # main() import os for file in os.listdir(): if file.endswith(".mp4"): cut_silences(file, file.split(".mp4")[0] + "_cut.mp4", -20)