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 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 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_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 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])
# 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] @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", ), ),