def process_vid(video, args): """ If video is not valid we will print error and pass out_directory must exist! """ out_directory = get_output_directory(args.outdir, video, args) try: clip = VideoFileClip(video) except Exception as e: print(f"Unable to open clip{video}: {e}") return save_file = get_save_file(out_directory, video, args) if args.time_start is not None: clip = clip.subclip(t_start=args.time_start, t_end=args.time_end) if exists(save_file): scenes = FramesMatches.load(save_file) else: scenes = FramesMatches.from_clip(clip.resize(width=120), dist_thr=args.dist_thr, max_d=args.max_d) try: scenes.save(save_file) except Exception as e: print(f"Unable to save matches: {e}") selected_scenes = scenes.select_scenes(match_thr=args.match_thr, min_time_span=args.min_time_span, nomatch_thr=args.nomatch_thr, time_distance=args.time_distance) selected_scenes.write_gifs(clip.resize(width=450), out_directory) optimize_dir(out_directory)
def test_FramesMatches_best(n, percent, expected_result): assert (FramesMatches([ FramesMatch(1, 2, 0, 0), FramesMatch(2, 3, 0, 0), FramesMatch(4, 5, 0, 0), FramesMatch(5, 6, 0, 0), ]).best(n=n, percent=percent) == expected_result)
def saveVideoSummaryAsGIF(destinationDirectory, file_path, image_width): # Open a video file (any format should work) clip = VideoFileClip(file_path) matches = FramesMatches.from_clip(clip, 1, 1) # loose matching # find the best matching pair of frames > 1.5s away # best = matches.filter(lambda x: x.time_span > 0.5).best() # Write the sequence to a GIF (with speed=30% of the original) final = clip.subclip(matches[0].t1, matches[0].t2).speedx(0.3) final.write_gif(destinationDirectory, fps=2)
def test_FramesMatches_save_load(util): input_matching_frames = [ FramesMatch(1, 2, 0, 0), FramesMatch(1, 2, 0.8, 0), FramesMatch(1, 2, 0.8, 0.8), ] expected_frames_matches_file_content = """1.000 2.000 0.000 0.000 1.000 2.000 0.800 0.000 1.000 2.000 0.800 0.800 """ outputfile = os.path.join(util.TMP_DIR, "moviepy_FramesMatches_save_load.txt") # save FramesMatches(input_matching_frames).save(outputfile) with open(outputfile, "r") as f: assert f.read() == expected_frames_matches_file_content # load for i, frames_match in enumerate(FramesMatches.load(outputfile)): assert frames_match == input_matching_frames[i]
def test_FramesMatches_filter(): input_matching_frames = [ FramesMatch(1, 2, 0, 0), FramesMatch(1, 2, 0.8, 0.8), FramesMatch(1, 2, 0.8, 0), ] expected_matching_frames = [FramesMatch(1, 2, 0, 0)] matching_frames_filter = lambda x: not x.min_distance and not x.max_distance matching_frames = FramesMatches(input_matching_frames).filter( matching_frames_filter) assert len(matching_frames) == len(expected_matching_frames) for i, frames_match in enumerate(matching_frames): assert frames_match == expected_matching_frames[i]
def process_vid(video, out_directory): """ If video is not valid we will print error and pass out_directory must exist! """ try: clip = mp.VideoFileClip(video) scenes = FramesMatches.from_clip(clip.resize(width=120), 10, 3) except Exception: print("oops, Looks like {} isn't a vid".format(video)) return # Cinderalla # match_thr=2, min_time_span=0.5, nomatch_thr=4, time_distance=0.5 # Ex # match_thr=1, min_time_span=1.5, nomatch_thr=2, time_distance=0.5) selected_scenes = scenes.select_scenes(2, 1, 4, 0.5) selected_scenes.write_gifs(clip.resize(width=450), out_directory)
def test_FramesMatches_select_scenes( filename, subclip, match_threshold, min_time_span, nomatch_threshold, expected_result, ): video_clip = VideoFileClip(filename) if subclip is not None: video_clip = video_clip.subclip(subclip[0], subclip[1]) clip = concatenate_videoclips([video_clip.fx(time_mirror), video_clip]) result = FramesMatches.from_clip(clip, 10, 3, logger=None).select_scenes( match_threshold, min_time_span, nomatch_threshold=nomatch_threshold, ) assert len(result) == len(expected_result) assert result == expected_result
def test_FramesMatches_from_clip( bitmap, expected_matches, distance_threshold, max_duration, ): clip = BitmapClip(bitmap, fps=1) matching_frames = FramesMatches.from_clip( clip, distance_threshold, max_duration, logger=None, ) assert matching_frames assert isinstance(matching_frames, FramesMatches) assert isinstance(matching_frames[0], FramesMatch) for i, match in enumerate(matching_frames): for j, n in enumerate(match): assert round(n, 4) == expected_matches[i][j]
def test_FramesMatches_write_gifs(util): video_clip = VideoFileClip("media/chaplin.mp4").subclip(0, 0.2) clip = concatenate_videoclips([video_clip.fx(time_mirror), video_clip]) # add matching frame starting at start < clip.start which should be ignored matching_frames = FramesMatches.from_clip(clip, 10, 3, logger=None) matching_frames.insert(0, FramesMatch(-1, -0.5, 0, 0)) matching_frames = matching_frames.select_scenes( 1, 0.01, nomatch_threshold=0, ) gifs_dir = os.path.join(util.TMP_DIR, "moviepy_FramesMatches_write_gifs") if os.path.isdir(gifs_dir): shutil.rmtree(gifs_dir) os.mkdir(gifs_dir) assert os.path.isdir(gifs_dir) matching_frames.write_gifs(clip, gifs_dir, logger=None) gifs_filenames = os.listdir(gifs_dir) assert len(gifs_filenames) == 7 for filename in gifs_filenames: filepath = os.path.join(gifs_dir, filename) assert os.path.isfile(filepath) with open(filepath, "rb") as f: assert len(f.readline()) end, start = filename.split(".")[0].split("_") end, start = (int(end), int(start)) assert isinstance(end, int) assert isinstance(end, int) shutil.rmtree(gifs_dir)
def loopDetection(videoId): print "////////////////" print "Looking for loops..." outputDir = os.path.join(_STATIC_BASE, videoId) selected_scenes_file = os.path.join(outputDir, "loops.txt") #ultimately what we want to fill if not os.path.exists(selected_scenes_file): videoFile = getVideoPath(videoId) clip = VideoFileClip(videoFile, audio=False) clip_small = clip.resize(width=150) # Downsize the clip to a width of 150px to speed up things matches = FramesMatches.from_clip(clip_small, 5, 3) # Find all the pairs of matching frames an return their corresponding start and end times. # matchesFile = os.path.join(outputDir, "matches.txt") # (Optional) Save the matches for later use. # matches.save(matchesFile) # matches = FramesMatches.load(matchesFile) # Filter the scenes: keep only segments with duration >1.5 seconds, # where the first and last frame have a per-pixel distance < 1, # with at least one frame at a distance 2 of the first frame, # and with >0.5 seconds between the starts of the selected segments. selected_scenes = matches.select_scenes(match_thr=10, min_time_span=1.5, nomatch_thr=.5, time_distance=1) print " ______ loops... ______ " #if any selected_scenes.save(selected_scenes_file) #save selected scenes scenes = [] ss = open(selected_scenes_file, "r") for line in ss: start, end, c, d = line.split("\t") scene = { 'start': start, 'end': end } scenes.append(scene) return scenes
def from_clip(clip, dist_thr, max_d, fps=None, dark_mean=100, dark_std=10): """ Finds all the frames tht look alike in a clip, for instance to make a looping gif. This teturns a FramesMatches object of the all pairs of frames with (t2-t1 < max_d) and whose distance is under dist_thr. This is well optimized routine and quite fast. Examples --------- We find all matching frames in a given video and turn the best match with a duration of 1.5s or more into a GIF: >>> from moviepy.editor import VideoFileClip >>> from moviepy.video.tools.cuts import find_matching_frames >>> clip = VideoFileClip("foo.mp4").resize(width=200) >>> matches = find_matching_frames(clip, 10, 3) # will take time >>> best = matches.filter(lambda m: m.time_span > 1.5).best() >>> clip.subclip(best.t1, best.t2).write_gif("foo.gif") Parameters ----------- clip A MoviePy video clip, possibly transformed/resized dist_thr Distance above which a match is rejected max_d Maximal duration (in seconds) between two matching frames fps Frames per second (default will be clip.fps) """ N_pixels = clip.w * clip.h * 3 dot_product = lambda F1, F2: (F1*F2).sum()/N_pixels F = {} # will store the frames and their mutual distances def distance(t1, t2): uv = dot_product(F[t1]['frame'], F[t2]['frame']) u, v = F[t1]['|F|sq'], F[t2]['|F|sq'] return np.sqrt(u+v - 2*uv) matching_frames = [] # the final result. for (t,frame) in clip.iter_frames(with_times=True, progress_bar=True): if frame.mean() <= dark_mean and frame.mean(2).std() <= dark_std: continue flat_frame = 1.0*frame.flatten() F_norm_sq = dot_product(flat_frame, flat_frame) F_norm = np.sqrt(F_norm_sq) for t2 in list(F.keys()): # forget old frames, add 't' to the others frames # check for early rejections based on differing norms if (t-t2) > max_d: F.pop(t2) else: F[t2][t] = {'min':abs(F[t2]['|F|'] - F_norm), 'max':F[t2]['|F|'] + F_norm} F[t2][t]['rejected']= (F[t2][t]['min'] > dist_thr) t_F = sorted(F.keys()) F[t] = {'frame': flat_frame, '|F|sq': F_norm_sq, '|F|': F_norm} for i,t2 in enumerate(t_F): # Compare F(t) to all the previous frames if F[t2][t]['rejected']: continue dist = distance(t, t2) F[t2][t]['min'] = F[t2][t]['max'] = dist F[t2][t]['rejected'] = (dist >= dist_thr) for t3 in t_F[i+1:]: # For all the next times t3, use d(F(t), F(t2)) to # update the bounds on d(F(t), F(t3)). See if you can # conclude on wether F(t) and F(t3) match. t3t, t2t3 = F[t3][t], F[t2][t3] t3t['max'] = min(t3t['max'], dist+ t2t3['max']) t3t['min'] = max(t3t['min'], dist - t2t3['max'], t2t3['min'] - dist) if t3t['min'] > dist_thr: t3t['rejected'] = True # Store all the good matches (t2,t) matching_frames += [(t1, t, F[t1][t]['min'], F[t1][t]['max']) for t1 in F if (t1!=t) and not F[t1][t]['rejected']] return FramesMatches([FramesMatch(*e) for e in matching_frames])
with open(outputfile, "r") as f: assert f.read() == expected_frames_matches_file_content # load for i, frames_match in enumerate(FramesMatches.load(outputfile)): assert frames_match == input_matching_frames[i] @pytest.mark.parametrize( ("n", "percent", "expected_result"), ( pytest.param(1, None, FramesMatch(1, 2, 0, 0), id="n=1"), pytest.param( 2, None, FramesMatches([FramesMatch(1, 2, 0, 0), FramesMatch(2, 3, 0, 0)]), id="n=2", ), pytest.param( 1, 50, FramesMatches([FramesMatch(1, 2, 0, 0), FramesMatch(2, 3, 0, 0)]), id="percent=50", ), ), ) def test_FramesMatches_best(n, percent, expected_result): assert (FramesMatches([ FramesMatch(1, 2, 0, 0), FramesMatch(2, 3, 0, 0),
from moviepy.video.tools.cuts import FramesMatches from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip import datetime import argparse from subprocess import call import img2stl # clean up # eg. python bin/process-video.py media/fish.mp4 parser = argparse.ArgumentParser( description='Extract seamless loops from a video') parser.add_argument('file', metavar='N', type=str, help='vido file') args = parser.parse_args() clip = mpy.VideoFileClip(args.file).resize(width=200) matches = FramesMatches.from_clip(clip, 40, 3) # loose matching # find the best matching pair of frames > 1.5s away best = matches.filter(lambda x: x.time_span > 1.5).best() # create clip of loop start_time = datetime.timedelta(0, best.t1) end_time = datetime.timedelta(0, best.t2) duration = end_time - end_time call( "ffmpeg -i {} -vcodec copy -acodec copy -ss {} -to {} tmp/clip.mp4".format( args.file, start_time, end_time), shell=True) ffmpeg_extract_subclip(args.file, best.t1, best.t2, targetname="tmp/clip.mp4")