Spaces:
Sleeping
Sleeping
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) |