executive-cut / app.py
Fortidune's picture
Update app.py
258ebd9 verified
import streamlit as st
import math
import sys
from moviepy.editor import AudioClip, VideoFileClip, concatenate_videoclips
import numpy as np
@st.cache_data
def get_stats(_audio_clip, window_size = 0.60):
audio_clip = _audio_clip
num_windows = min(650, math.floor(audio_clip.end/window_size))
max_volumes = []
for i in range(num_windows):
s = audio_clip.subclip(i * window_size, (i + 1) * window_size)
v = s.max_volume()
max_volumes.append(v)
arr = np.array(max_volumes)
return {"mean": np.mean(arr), "std": np.std(arr),"max": np.max(arr), "median": np.median(arr), "clips":len(max_volumes)}
@st.cache_data
def find_speaking(_audio_clip, window_size=0.60, volume_threshold=0.01, ease_in=0.20):
audio_clip = _audio_clip
# First, iterate over audio to find all silent windows.
num_windows = min(650, math.floor(audio_clip.end/window_size))
window_is_silent = []
silent_windows = []
#can optimize and multi thread
for i in range(num_windows):
s = audio_clip.subclip(i * window_size, (i + 1) * window_size)
v = s.max_volume()
window_is_silent.append(v < volume_threshold)
if v < volume_threshold:
silent_windows.append([i * window_size, (i + 1) * window_size])
# Find speaking intervals.
speaking_start = 0
speaking_end = 0
speaking_intervals = []
for i in range(1, len(window_is_silent)):
e1 = window_is_silent[i - 1]
e2 = window_is_silent[i]
# silence -> speaking
if e1 and not e2:
speaking_start = i * window_size
# speaking -> silence, now have a speaking interval
if not e1 and e2:
speaking_end = i * window_size
new_speaking_interval = [speaking_start - ease_in, speaking_end + ease_in]
# With tiny windows, this can sometimes overlap the previous window, so merge.
need_to_merge = len(speaking_intervals) > 0 and speaking_intervals[-1][1] > new_speaking_interval[0]
if need_to_merge:
merged_interval = [speaking_intervals[-1][0], new_speaking_interval[1]]
speaking_intervals[-1] = merged_interval
else:
speaking_intervals.append(new_speaking_interval)
return speaking_intervals, silent_windows
def read_video(name):
return VideoFileClip(name)
def main():
st.title('Video Editor')
st.subheader('Cuts silences given a threshold')
uploaded_file = st.file_uploader("Please upload a video file (mp4 or mov)", type=["mp4", "mov"])
if uploaded_file is not None:
with open(uploaded_file.name, "wb") as f:
f.write(uploaded_file.read())
st.subheader('Original Video')
st.write(uploaded_file.name)
st.video(uploaded_file)
vid = read_video(uploaded_file.name)
stats = get_stats(vid.audio)
with st.status('Settings'):
col1, col2, col3, col4, col5 = st.columns(5)
with col1:
st.write("Std noise")
st.write(stats["std"])
with col2:
st.write("Max noise")
st.write(stats["max"])
with col3:
st.write("Mean noise")
st.write(stats["mean"])
with col4:
st.write("Median noise")
st.write(stats["median"])
with col5:
st.write("Number of clips")
st.write(stats["clips"])
threshold = st.slider("Control the sound threshold (%) here. The lower the threshold the more you keep.", min_value=0, max_value=100, step=5 , value=40)
if st.button('Cut'):
with st.spinner('Looking for the sound of silence...'):
intervals_to_keep, silent_windows = find_speaking(vid.audio, volume_threshold=threshold*(stats["mean"]+stats["std"])/100)
st.write("Found the silence. Cutting...")
keep_clips = [vid.subclip(start, end) for [start, end] in intervals_to_keep]
silent_clips = [vid.subclip(start,end) for [start,end] in silent_windows]
if len(keep_clips) == 0:
st.write("Nothing to keep with current settings. Try increasing threshold")
elif len(keep_clips) != 0:
st.write(f'We cut {len(silent_clips)} sections of the video')
st.write(f'We kept {stats["clips"] - len(silent_clips)} sections of the video')
edited_video = concatenate_videoclips(keep_clips)
st.write('Saving edited video...')
edited_video.write_videofile('edited.mp4',
fps=60,
preset='ultrafast',
codec='libx264',
temp_audiofile='temp-audio.m4a',
remove_temp=True,
audio_codec="aac",
threads=4
)
st.subheader('Edited Video')
st.video('edited.mp4')
with open('edited.mp4', 'rb') as f:
video_bytes = f.read()
if st.download_button(label='Download file',
data=video_bytes,
file_name='edited.mp4',
mime='video/mp4'):
if os.path.exists('edited.mp4'):
os.remove('edited.mp4')
st.info('Deleted edited.mp4 from filesystem.')
if os.path.exists(uploaded_file.name):
os.remove(uploaded_file.name)
st.info('Deleted edited.mp4 from filesystem.')
for silent_clip in silent_clips:
name =f'removed_{counter}.mp4'
if os.path.exists(name):
os.remove(name)
counter = 1
counter = 1
#TODO
#for silent_clip in silent_clips:
# name =f'removed_{counter}.mp4'
# silent_clip.write_videofile(name,
# fps=60,
# preset='ultrafast',
# codec='libx264',
# temp_audiofile=f'temp-{counter}.m4a',
# remove_temp=True,
# audio_codec="aac",
# threads=4
# )
# st.video(name)
# counter = counter + 1
vid.close()
if __name__ == '__main__':
main()