forked from DarkTrick/python-video-silence-cutter
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsilence_cutter.py
158 lines (124 loc) · 5.93 KB
/
silence_cutter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import subprocess
import tempfile
import os
import argparse
def findSilences(filename, noise_tolerance, duration):
"""
Returns a list of detected silence start and end times:
even elements (0,2,4, ...) denote silence start time
uneven elements (1,3,5, ...) denote silence end time
"""
command = ["ffmpeg",
"-i", filename,
"-af", "silencedetect=n=" + str(noise_tolerance) +
":d=" + str(duration),
"-f", "null", "-"]
output = str(subprocess.run(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE))
lines = output.replace("\\r", "").split("\\n")
time_list = []
for line in lines:
if ("silencedetect" in line):
words = line.split(" ")
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]))
return time_list
def getVideoDuration(filename: str) -> float:
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, margin):
ret = ""
for i in range(int(len(videoSectionTimings) / 2)):
start = max(videoSectionTimings[2 * i] - margin, videoSectionTimings[0])
end = min(videoSectionTimings[2 * i + 1] + margin, videoSectionTimings[-1])
ret += "between(t," + str(start) + "," + str(end) + ")+"
# cut away last "+"
ret = ret[:-1]
return ret
def getFileContent_videoFilter(videoSectionTimings, margin):
ret = "select='"
ret += ffmpeg_filter_getSegmentFilter(videoSectionTimings, margin)
ret += "', setpts=N/FRAME_RATE/TB"
return ret
def getFileContent_audioFilter(videoSectionTimings, margin):
ret = "aselect='"
ret += ffmpeg_filter_getSegmentFilter(videoSectionTimings, margin)
ret += "', asetpts=N/SR/TB"
return ret
def writeFile(filename, content):
with open(filename, "w") as file:
file.write(str(content))
def ffmpeg_run(file, videoFilter, audioFilter, outfile):
"""Run ffmpeg with the given filters on the given file,
outputting to the given outfile"""
# Create temporary files to store filters in
videoFilter_file = os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
audioFilter_file = os.path.join(tempfile.gettempdir(), os.urandom(24).hex())
open(videoFilter_file, "x").close()
open(audioFilter_file, "x").close()
# Write filters to the temp files
writeFile(videoFilter_file, videoFilter)
writeFile(audioFilter_file, audioFilter)
# Run ffmpeg with the created filter files
command = ["ffmpeg", "-i", file,
"-filter_script:v", videoFilter_file,
"-filter_script:a", audioFilter_file,
outfile]
subprocess.run(command)
# Remove the temp files
os.remove(videoFilter_file)
os.remove(audioFilter_file)
def cut_silences(infile, outfile, noise_tolerance, silence_min_duration, margin):
print("Detecting silences, this may take a while depending on the length of the video...")
silences = findSilences(infile, noise_tolerance, silence_min_duration)
duration = getVideoDuration(infile)
videoSegments = getSectionsOfNewVideo(silences, duration)
videoFilter = getFileContent_videoFilter(videoSegments, margin)
audioFilter = getFileContent_audioFilter(videoSegments, margin)
print("Creating new video...")
ffmpeg_run(infile, videoFilter, audioFilter, outfile)
def main():
parser = argparse.ArgumentParser(
description='Automatically detect and cut silences from videos.')
parser.add_argument("input_file", help="The file that should have the silences cut from it")
parser.add_argument("-o", "--output_file", help="Where the resulting video should be stored")
parser.add_argument("-n", "--noise_tolerance", default="0.03",
help=("The threshold for determining wether audio is considered silence. "
"Can be specified in dB (in case 'dB' is appended to the specified value) "
"or amplitude ratio. Default is 0.03"))
parser.add_argument("-d", "--min_duration", default="0.1",
help=("The minimum duration (in seconds) for a silence to be detected as such. "
"Default is 0.1"))
parser.add_argument("-m", "--margin", type=float, default=0.0,
help=("The margin (in seconds) that should be kept before and after a non-silent part. "
"Should not be larger than half of the minimum duration. "
"Default is 0.0"))
args = parser.parse_args()
# Check if input file exists
if (not os.path.isfile(args.input_file)):
print("error: The input file could not be found:\n" + args.input_file)
return
# Check if the margin isn't greater than half of the minimum duration
if (args.margin > (float(args.min_duration) / 2)):
print("error: The margin is greater than half of the minimum duration\n")
return
# Set default output filename if it wasn't specified
outfile = args.output_file
if not outfile:
tmp = os.path.splitext(args.input_file)
outfile = tmp[0] + "_cut" + tmp[1]
# Cut out the silences and store the result
cut_silences(args.input_file, outfile, args.noise_tolerance, args.min_duration, args.margin)
if __name__ == "__main__":
main()