Skip to content

Instantly share code, notes, and snippets.

@OverlappingElvis
Last active January 30, 2023 19:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OverlappingElvis/0a83cafcdeafe061757faef0aa420ee2 to your computer and use it in GitHub Desktop.
Save OverlappingElvis/0a83cafcdeafe061757faef0aa420ee2 to your computer and use it in GitHub Desktop.
generate random video montages from internet archive identifiers
from math import floor
from sys import float_info, stdout
from internetarchive import download
from moviepy.editor import VideoFileClip, concatenate_videoclips, vfx
from moviepy.video.tools.cuts import find_video_period
from random import choice, sample
import time
from argparse import ArgumentParser
import os
import pytesseract
import pickle
import shutil
options = ArgumentParser(prog="Wingnuts Video Mixer")
options.add_argument("identifiers", nargs="+", help="Internet Archive identifier(s)")
options.add_argument("-l", "--length", default=30, type=int, help="Length of output file (seconds)")
options.add_argument("-m", "--min", default=1.5, type=float, help="Minimum length per clip (seconds)")
options.add_argument("-M", "--max", default=float_info.max, type=float, help="Maximum length per clip (seconds)")
options.add_argument("-w", "--window", default=5, type=int, help="Search window length (seconds)")
options.add_argument("-t", "--text", default=False, type=bool, help="Allow clips with text")
options.add_argument("-u", "--unbalanced", default=False, type=bool, help="Clip pool size proportional to video length")
options.add_argument("-W", "--width", default=1280, type=int, help="Output video width")
options.add_argument("-H", "--height", default=720, type=int, help="Output video height")
options.add_argument("-P", "--process_only", default=False, type=bool, help="Don't output a video, just download and process")
args = options.parse_args()
clip_settings = dict(min=args.min, max=args.max, window=args.window)
print("Starting Wingnuts Video Mixer.")
def process_clips(identifier):
clips = []
source_directory = "./source/{}".format(identifier)
clips_directory = "./clips/{}".format(identifier)
os.mkdir(clips_directory)
settings_file = open("{}/.settings".format(clips_directory), "wb")
pickle.dump(clip_settings, settings_file)
settings_file.close()
source = VideoFileClip("{}/{}".format(source_directory, os.listdir(source_directory)[0]), target_resolution=(args.height, args.width))
sample_start = 0
clip_index = 0
print("Processing {} ({})".format(source.filename, source.duration))
while sample_start + args.window < source.duration:
subclip = source.subclip(sample_start, sample_start + args.window)
video_period = find_video_period(subclip)
if video_period >= args.min and video_period <= args.max:
print("Found candidate at ({}, {}) with length {}, appending.".format(sample_start, sample_start + args.window, video_period))
subclip.write_videofile("{}/{}.mp4".format(clips_directory, clip_index), audio=False)
clips.append(subclip.subclip(0.3, video_period))
sample_start += video_period
clip_index += 1
else:
print("Skipping subclip at ({}, {})".format(sample_start, sample_start + args.window))
sample_start += args.window / 2
return clips
def download_and_process_clips(identifier):
download(identifier, formats=("MPEG2","MPEG4"), verbose=True, destdir="source")
return process_clips(identifier)
all_clips = []
for identifier in args.identifiers:
clips_directory = "./clips/{}".format(identifier)
if os.path.isdir(clips_directory):
try:
settings_file = open("{}/.settings".format(clips_directory), "rb")
saved_settings = pickle.load(settings_file)
settings_file.close()
if not saved_settings == clip_settings:
print("Clip settings mismatch, reprocessing.")
shutil.rmtree(clips_directory)
all_clips.append(download_and_process_clips(identifier))
else:
print("Clips exist and settings are unchanged.")
clips = []
for file in os.listdir(clips_directory):
clips.append(VideoFileClip("{}/{}".format(clips_directory, file), target_resolution=(args.height, args.width)))
all_clips.append(clips)
except:
print("Could not open saved settings. Reprocessing.")
shutil.rmtree(clips_directory)
all_clips.append(download_and_process_clips(identifier))
else:
print("No saved clips found.")
all_clips.append(download_and_process_clips(identifier))
if (args.process_only):
pass
else:
final_clips = []
sampled_clips = []
total_duration = 0
if not args.unbalanced:
max_length = min(map(lambda x: len(x), all_clips))
for clips in all_clips:
sampled_clips = sampled_clips + sample(clips, max_length)
all_clips = sampled_clips
else:
for clips in all_clips:
final_clips = final_clips + clips
print("Remixing clips...")
while total_duration < args.length:
clip = choice(all_clips)
if not args.text and len(pytesseract.image_to_string(clip.get_frame(0)).strip()):
print("Text detected, skipping clip.")
continue
final_clips.append(clip)
total_duration += clip.duration
print("Adding clip ({} seconds so far).".format(total_duration))
concatenate_videoclips(final_clips).write_videofile("./output/{}_{}.mp4".format(identifier, floor(time.time())), audio=False)
print("Done.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment