import moviepy.audio import random from sys import maxsize from os import listdir, mkdir, path from moviepy import editor from moviepy.video import fx ''' TODO: flash random images in short bursts at long intervals try to overlap videos more and distort them spread out audio duplication a bit, so they don't end up directly next to one another ''' videoSourceFolder = "input_video_sources" audioSourceFolder = "input_audio_sources" videoFiles = [videoSourceFolder + "/" + vid for vid in listdir(videoSourceFolder)] audioFiles = [audioSourceFolder + "/" + vid for vid in listdir(audioSourceFolder)] video_clip_times = (1.0, 2.5) #default: 0.5, 3.5. chaos: 0.3, 1.2 audio_clip_times = (0.7, 3) #default: 0.7, 13. chaos: 0.7, 3.0 audio_multiplier = 1.5 #audio amount multiplier (based off the amount of videos), e.g. 60 videos with a multiplier of 0.75 means 45 audio cues. default: 0.75. chaos: 1.5 #(min, max) random values range, inclusive continuous_flip_amount = (7, 14) #integer. default: 1, 7 repeat_video_amount = (0.01, 0.2) #float. default: 0.01, 0.2 shuffle_video_amount = (20, 50) #integer. default: 20, 50 flip_rotation_amount = (1, 4) #integer. default: 1, 4 random_speed_amount = (0.7, 3) #float. default: 0.7, 3 contrast_amount = (0.3, 2) #float. default: 0.3, 2 chosenSeed = input("Video seed (or 'any' to use system time): ") while not chosenSeed.isdecimal() and not chosenSeed in ["any", "skip", "default", "time"]: chosenSeed = input("Video seed (or 'any' to use system time): ") seed = int(chosenSeed) if chosenSeed.isdecimal() else random.randrange(maxsize) #get a chosen or random seed to use and reference later print(f'Chose seed: {seed}') rng = random.Random(seed) print(f'Found {len(videoFiles)} videos and {len(audioFiles)} sounds') # Returns a progress bar string, useful for measuring progress in command-line def ProgressBar(current_index, max_index, progress_bar_len:int = 20, include_percent:bool = True, percent_digits:int = 2, symbol_middle:str = "█", symbol_begin:str = "|", symbol_end:str = "|"): # Used for the progress bar - gets progress 0-1 range, # then multiplies it by the progress bar length percent_done = int(current_index / max_index * progress_bar_len) percentage = f" {round((current_index / max_index) * 100, percent_digits)}%" if include_percent else "" return symbol_begin + (symbol_middle * percent_done + " " * (progress_bar_len - percent_done)) + symbol_end + percentage def ContinuousFlipVideo(clip): #flip a video multiple times over its duration flip_amt = rng.randint(*continuous_flip_amount) #how many times the clip will be flipped flip_periods = [rng.uniform(0, clip.duration) for _ in range(flip_amt)] #random periods at which to shuffle flip_periods.sort() #in ascending order all_clips = [] last_period = 0 for period in flip_periods: new_clip = clip.subclip(last_period, period) all_clips.append(new_clip) last_period = period new_clip = clip.subclip(last_period, clip.duration) all_clips.append(new_clip) for i in range(len(all_clips)): clip = all_clips[i] all_clips[i] = rng.choice([fx.mirror_x.mirror_x, fx.mirror_y.mirror_y, lambda v: v])(all_clips[i]) #flip on the x, or y, or don't flip final_clip = editor.concatenate_videoclips(all_clips) return final_clip def RepeatVideo(clip): #repeat a video multiple times random_dur = rng.uniform(*repeat_video_amount) repeat_amt = int((clip.duration/random_dur)*0.5) start_offset = rng.uniform(0, clip.duration - random_dur) new_clip = clip.subclip(start_offset, start_offset+random_dur) final_clip = editor.concatenate_videoclips([new_clip]*repeat_amt) return final_clip def ShuffleVideo(clip): #take a clip, split it into multiple parts, shuffle those parts shuffle_amt = rng.randint(*shuffle_video_amount) #how many times the clip will be split and shuffled shuffle_periods = [rng.uniform(0, clip.duration) for _ in range(shuffle_amt)] #random periods at which to shuffle shuffle_periods.sort() #in ascending order all_clips = [] last_period = 0 for period in shuffle_periods: new_clip = clip.subclip(last_period, period) all_clips.append(new_clip) last_period = period new_clip = clip.subclip(last_period, clip.duration) all_clips.append(new_clip) rng.shuffle(all_clips) #shuffle around the clips to get the final result final_clip = editor.concatenate_videoclips(all_clips) return final_clip def FlipRotationVideo(clip): #makes the clip "rotate" by flipping and reversing the second part of the clip random_duration = rng.uniform(0.1, 0.3) start_offset = rng.uniform(0, clip.duration - random_duration) first_clip = clip.subclip(start_offset, start_offset+random_duration) second_clip = fx.time_mirror.time_mirror(fx.mirror_x.mirror_x(first_clip.copy())) #flip horizontal, then reverse video both_clips = [fx.speedx.speedx(first_clip, 1.5), fx.speedx.speedx(second_clip, 1.5)] return editor.concatenate_videoclips(both_clips*random.randint(*flip_rotation_amount)) videoEffects = [ lambda v: fx.speedx.speedx(v, rng.uniform(*random_speed_amount)), #speed up/slow down lambda v: fx.mirror_x.mirror_x(v), #mirror on the x axis lambda v: fx.time_mirror.time_mirror(v), #reverse the video lambda v: v.fx(fx.time_symmetrize.time_symmetrize).fx(fx.speedx.speedx, factor=rng.uniform(1.4, 2.3)), #forward + reverse with speed up lambda v: RepeatVideo(v), #repeat the video multiple times lambda v: ShuffleVideo(v), #shuffle up parts of the video for a glitch-like effect lambda v: ContinuousFlipVideo(v), #flip the video on the x and y axis multiple times lambda v: FlipRotationVideo(v), lambda v: fx.lum_contrast.lum_contrast(v, lum=0, contrast=rng.uniform(*contrast_amount)) #change contrast ] videoObjects = [] audioObjects = [] videoAmount = input("Amount of videos: ") while not videoAmount.isdecimal(): videoAmount = input("Amount of videos: ") videoAmount = int(videoAmount) shouldUseEffects = input("Apply video effects? (y/n): ") while not shouldUseEffects.lower() in ["y", "yes", "true", "n", "no", "false"]: shouldUseEffects = input("Apply video effects? (y/n): ") shouldUseEffects = True if shouldUseEffects.lower() in ["y", "yes", "true"] else False randomVideos = rng.sample(videoFiles, min(videoAmount, len(videoFiles))) #randomVideos = rng.choices(videoFiles, k=min(videoAmount, len(videoFiles))) if videoAmount > len(videoFiles): #if there is a higher chosen amount than total, re-use videos videoAmountToAdd = videoAmount - len(videoFiles) print(f'Chosen video amount is higher than available video amount - re-using {videoAmountToAdd} videos...') additionalVideos = rng.choices(videoFiles, k=videoAmountToAdd) randomVideos += additionalVideos print(f"Compiling {videoAmount} videos... ", end="\r") for index, video in enumerate(randomVideos): print(f"Compiling {videoAmount} videos... {ProgressBar(index, len(randomVideos), progress_bar_len=40)}", end="\r") newClip = editor.VideoFileClip(video).resize(height=480) #target_resolution=(512, 512) randomDuration = rng.uniform(*video_clip_times) if newClip.duration > randomDuration: startOffset = rng.uniform(0, newClip.duration - randomDuration) newClip = newClip.subclip(startOffset, startOffset+randomDuration) if rng.choice([True, True, False]) and shouldUseEffects: newClip = rng.choice(videoEffects)(newClip) #apply a random effect videoObjects.append(newClip) print(f"Compiling {videoAmount} videos... {ProgressBar(1, 1, progress_bar_len=40)}") print("Finished compiling videos.") finalVideo = editor.concatenate_videoclips(videoObjects, method="compose") # method="compose" audioAmount = int(videoAmount*audio_multiplier) randomSounds = rng.sample(audioFiles, min(audioAmount, len(audioFiles))) if audioAmount > len(audioFiles): audioAmountToAdd = audioAmount - len(audioFiles) print(f'Chosen audio amount is higher than available audio amount - re-using {audioAmountToAdd} audio sources...') additionalAudio = rng.choices(audioFiles, k=audioAmountToAdd) randomSounds += additionalAudio print(f"Compiling {audioAmount} sounds...", end="\r") copiedSoundAmount = 0 for index, audio in enumerate(randomSounds): print(f"Compiling {audioAmount} sounds... {ProgressBar(index, len(randomSounds), progress_bar_len=40)}", end="\r") newClip = editor.AudioFileClip(audio) newClip = moviepy.audio.fx.volumex.volumex(newClip, 0.8) # modify volume if newClip.duration > 5: #for long clips randomDuration = rng.uniform(*audio_clip_times) # crop audio duration if newClip.duration > randomDuration: # if the audio is longer than the cropped duration, crop the audio at a random position startOffset = rng.choice([rng.uniform(0, newClip.duration - randomDuration), 0]) #either use a random offset, or start at beginning of audio clip newClip = newClip.subclip(startOffset, startOffset+randomDuration) newClip = newClip.set_start(rng.uniform(0, finalVideo.duration-newClip.duration)) # move audio around video length audioObjects.append(newClip) else: # Place to position the audio clip - could be anywhere from the final video's start all the way to its full duration clipPosition = rng.uniform(0, finalVideo.duration-newClip.duration) newClip = newClip.set_start(clipPosition) # move audio around video length audioObjects.append(newClip) # Add duplicates of this audio clip for i in range(rng.randint(1, 5)): copiedSoundAmount += 1 # Max 0 and clip position - 2 so it doesn't go into negative clip position (if near beginning of video) minimumRange = max(0, clipPosition - 2) # Minimum between final video duration and clip position + 2 so it doesn't go over video length (if near end of video) maximumRange = min(finalVideo.duration, clipPosition + 2) - newClip.duration copiedClip = newClip.set_start(rng.uniform(minimumRange, maximumRange)) # move audio around video length audioObjects.append(copiedClip) print(f"Compiling {audioAmount} sounds... {ProgressBar(1, 1, progress_bar_len=40)}") print(f"Finished compiling audio. Added {copiedSoundAmount} duplicate sounds, total {audioAmount+copiedSoundAmount}.") # The video's filename finalVideoFilename = f'output/result_seed-{seed}_{videoAmount}{"_effects" if shouldUseEffects else ""}.mp4' # Create output directory if it doesn't exist if not path.exists("output"): mkdir("output") finalVideo.audio = editor.CompositeAudioClip([finalVideo.audio] + audioObjects) finalVideo.write_videofile(finalVideoFilename, fps=30, audio_bitrate="96k") # Close all file streams for video in videoObjects: video.close() for audio in audioObjects: audio.close()