From 9b2913653542ea3d1af1fbfeb3f4b94ac8bc3f21 Mon Sep 17 00:00:00 2001 From: Tanishq Dubey Date: Tue, 8 Mar 2022 00:25:54 -0500 Subject: [PATCH] Add files via upload --- autoditor.py | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 autoditor.py diff --git a/autoditor.py b/autoditor.py new file mode 100644 index 0000000..2cb4991 --- /dev/null +++ b/autoditor.py @@ -0,0 +1,210 @@ +import moviepy.editor as mp +import numpy as np +from matplotlib import pyplot as plt +import pandas as pd +import scipy.io.wavfile as wav +from typing import Tuple +from itertools import zip_longest +import argparse +import os +import tempfile + +class Moment: + + def __init__(self, start, stop): + self.start = start + self.stop = stop + self.duration = 0 + if stop is not None: + self.duration = stop - start + + def __str__(self): + return f"Start {self.start} \t\t Stop {self.stop} \t\t Duration {self.duration}" + + def __repr__(self): + return f"Start {self.start} \t\t Stop {self.stop} \t\t Duration {self.duration}" + + +def process_audio(source_audio_path: str) -> Tuple[np.ndarray, int, int]: + rate, data_raw = wav.read(source_audio_path) + data_raw = data_raw.astype(np.int32) + mono = (data_raw[:,0] + data_raw[:,1])/2 + duration = len(mono) / rate + return mono, duration, rate + + +def convert_video_to_audio(source_video_path: str, destination_audio_location = None) -> str: + tdir = tempfile.gettempdir() + dest_location = f"{tdir}/{source_video_path}.wav" + print(f"checking to see if {dest_location} exists") + if destination_audio_location is not None: + dest_location = destination_audio_location + if os.path.isfile(dest_location): + print(f"{dest_location} exists, using cached") + return dest_location + vid = mp.VideoFileClip(source_video_path) + vid.audio.write_audiofile(dest_location) + vid.close() + return dest_location + + +def get_subclips(source_video_path: str, moments): + vid = mp.VideoFileClip(source_video_path) + clips = [] + + for m in moments: + if m.duration > 30: + clips.append(vid.subclip(m.start, m.stop)) + return clips + + +def sub_resample(data: np.ndarray, factor: int): + return data[::factor].copy() + + +def moving_average(x, w): + return np.convolve(x, np.ones(w), 'valid') / w + + +def find_highlights(data, threshold, rate, factor): + chunks = [] + for i in range(len(data) - 1): + if data[i] < threshold < data[i + 1]: + chunks.append(i * factor / rate) + return chunks + + +def find_moving_average_highlights(short_ma, long_ma, bitrate, resample_factor): + in_a_clip = False + timestamps = [] + for t in range(1, len(long_ma)): + if not in_a_clip and (short_ma[t - 1] < long_ma[t - 1]) and (short_ma[t] > long_ma[t]): + in_a_clip = True + timestamps.append(t * resample_factor / bitrate) + elif in_a_clip and (short_ma[t - 1] > long_ma[t - 1]) and (short_ma[t] < long_ma[t]): + in_a_clip = False + timestamps.append(t * resample_factor / bitrate) + + ret_list = [] + raw_moments = list(blockwise(timestamps)) + for rm in raw_moments: + ret_list.append(Moment(rm[0], rm[1])) + + return ret_list + + +def blockwise(t, size=2, fillvalue=None): + it = iter(t) + return zip_longest(*[it]*size, fillvalue=fillvalue) + + +def plot_audio(data): + plt.plot(list(range(len(data))), data) + plt.show() + + + +def main(vidfilepath, outfile, res_factor, lw, sw, dry_run, minduration, maxduration): + try: + videofile = vidfilepath + + audiofile = convert_video_to_audio(videofile) + data, duration, bitrate = process_audio(audiofile) + RESAMPLE_FACTOR = res_factor + subsampled_data = sub_resample(data, RESAMPLE_FACTOR) + squared_subsample = np.square(subsampled_data) + LONG_WINDOW = lw + SHORT_WINDOW = sw + + assert(LONG_WINDOW > SHORT_WINDOW) + + long_ma = moving_average(squared_subsample, LONG_WINDOW) + short_ma = moving_average(squared_subsample, SHORT_WINDOW) + moments = find_moving_average_highlights(short_ma, long_ma, bitrate, RESAMPLE_FACTOR) + total_time = 0 + for m in moments: + if m.duration > minduration and m.duration < maxduration: + print(f"Start {round(m.start/60, 2)} \t\t Stop {round(m.stop/60, 2)} \t\t Duration {round(m.duration, 2)}") + total_time = total_time + m.duration + + print(total_time/60) + + if not dry_run: + clips = get_subclips(videofile, moments) + clips + to_render = mp.concatenate_videoclips(clips) + to_render.write_videofile(outfile) + os.remove(audiofile) + except: + os.remove(audiofile) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog="autoditor", + description="autoditor is an automatic video editor." + ) + parser.add_argument( + "-v", + "--video", + required=True, + metavar="Video file path", + dest="vpath" + ) + parser.add_argument( + "-f", + "--factor", + default=16000, + metavar="Subsampling factor", + dest="factor", + type=int + ) + parser.add_argument( + "-l", + "--longwindow", + default=128, + metavar="Long moving average time", + dest="lwindow", + type=int + ) + parser.add_argument( + "-s", + "--shortwindow", + default=64, + metavar="Short moving average time", + dest="swindow", + type=int + ) + parser.add_argument( + "-d", + "--dryrun", + dest="drun", + action="store_true" + ) + parser.add_argument( + "-o", + "--output", + required=True, + metavar="Output file location", + dest="opath" + ) + parser.add_argument( + "-i", + "--minduration", + default=30, + metavar="Minimum clip duration", + dest="mindur", + type=int + ) + parser.add_argument( + "-m", + "--maxduration", + default=100, + metavar="Maximum clip duration", + dest="maxdur", + type=int + ) + + args = parser.parse_args() + # def main(vidfilepath, outfile, res_factor, lw, sw, dry_run): + main(args.vpath, args.opath, args.factor, args.lwindow, args.swindow, args.drun, args.mindur, args.maxdur) \ No newline at end of file