def plot_vmaf_graph(output_path, frame_nums, frame_scores, source_duration, lowest_values, fps, title=""): timecode = Timecode(fps, '00:00:00:00') plt.title(title) # generate major tics based on evenly divided time chosen_frames = np.linspace(0, len(frame_nums) - 1, 20) ticframes = [frame_nums[int(i)] for i in chosen_frames] ticlabels = [timecode.tc_to_string( *timecode.frames_to_tc(ticframe)) for ticframe in ticframes] plt.xticks(ticframes, ticlabels, rotation='vertical') plt.plot(frame_nums, frame_scores) ax = plt.axes() if lowest_values != None: style = dict(size=10, color='gray') # label the valleys for idx, lowval in enumerate(lowest_values): ax.text(lowval, frame_scores[lowval] - 5, str(idx + 1), **style) ax.set_xticks(ticframes, minor=True) ax.grid() plt.ylabel('vmaf score') plt.ylim(0, 100) plt.xlabel('time') plt.subplots_adjust(bottom=0.3) plt.gcf().set_size_inches(9, 5) plt.savefig(output_path) plt.clf()
def plot_multi_vmaf_timegraph(output_path, frame_nums, baseline_frame_scores, variant_list, target_bitrate, source_duration, fps, test_item='bitrate'): timecode = Timecode(fps, '00:00:00:00') title = " Effect of Bitrate Changes on VMAF over Asset Duration" plt.suptitle(title, fontsize=14, color='blue') plt.title("Blue line is VMAF/time of existing VOD transcode (4sec GOPs). Gray lines are quality/time for 2sec GOPs at various bitrates", fontsize=7, color='black') upper = max(baseline_frame_scores) lower = min(baseline_frame_scores) higher_bitrate_scores = [variant['vmaf_frame_scores'] for variant in variant_list if variant[test_item] > target_bitrate] lower_bitrate_scores = [variant['vmaf_frame_scores'] for variant in variant_list if variant[test_item] < target_bitrate] # generate lighter lineshades higher_lineshades = np.linspace(0.7, 0.9, len(higher_bitrate_scores)) for idx, frame_scores in enumerate(higher_bitrate_scores): upper = max(upper, max(frame_scores)) lower = min(lower, min(frame_scores)) plt.plot(frame_nums, frame_scores, str(higher_lineshades[idx])) lower_lineshades = np.linspace(0.9, 0.7, len(lower_bitrate_scores)) for idx, frame_scores in enumerate(lower_bitrate_scores): upper = max(upper, max(frame_scores)) lower = min(lower, min(frame_scores)) # plt.plot(frame_nums, frame_scores, str(1 - (0.1 * (1 + idx)))) plt.plot(frame_nums, frame_scores, str(lower_lineshades[idx])) plt.plot(frame_nums, baseline_frame_scores) # generate major tics based on evenly divided time chosen_frames = np.linspace(0, len(frame_nums) - 1, 20) ticframes = [frame_nums[int(i)] for i in chosen_frames] ticlabels = [timecode.tc_to_string( *timecode.frames_to_tc(ticframe)) for ticframe in ticframes] plt.xticks(ticframes, ticlabels, rotation='vertical') ax = plt.axes() # style = dict(size=10, color='gray') # # label the valleys # for idx, lowval in enumerate(lowest_values): # ax.text(lowval, frame_scores[lowval] - 5, str(idx + 1), **style) ax.set_xticks(ticframes, minor=True) ax.grid() plt.ylabel('vmaf score') plt.ylim(lower, upper) plt.xlabel('time') plt.subplots_adjust(bottom=0.3) plt.gcf().set_size_inches(15, 5) plt.savefig(output_path) plt.clf()
def ffmpeg_grab_frame_triptychs_slow(frame_nums, source_path, transcode_path, results_directory, transcode_fps, dest_size): timecode = Timecode(transcode_fps, '00:00:00:00') return_frames = [] source_frames = ffmpeg_grab_frames(local_source, frame_nums, results_directory, "source_") transcode_frames = ffmpeg_grab_frames(local_transcode, frame_nums, results_directory, "transcode_") diff_frames = ffmpeg_grab_diff_frames(frame_nums, source_path, transcode_path, results_directory, transcode_fps, dest_size) for (frame_num, source, transcode, diff) in zip(frame_nums, source_frames, transcode_frames, diff_frames): frame_time = timecode.tc_to_string(*timecode.frames_to_tc(frame_num)) return_path = frame_time.replace(":", "-") + ".jpg" return_abs_path = os.path.join(results_directory, return_path) source_full_path = os.path.join(results_directory, source) transcode_full_path = os.path.join(results_directory, transcode) diff_full_path = os.path.join(results_directory, diff) ffmpeg_htile_images(source_full_path, transcode_full_path, diff_full_path, return_abs_path) os.remove(source_full_path) os.remove(transcode_full_path) os.remove(diff_full_path) return_frames.append(return_path) return return_frames
def plot_three_vmaf_timegraph(output_path, frame_nums, baseline_frame_scores, variant_list, target_bitrate, recommended_bitrate, source_duration, fps): timecode = Timecode(fps, '00:00:00:00') upper = max(baseline_frame_scores) lower = min(baseline_frame_scores) baseline_label = "Existing " + \ str(target_bitrate) + "bps VOD Transcode (4sec GOP)" identical_label = "Identical " + \ str(target_bitrate) + "bps but with 2sec GOP" recommended_label = str( target_bitrate) + "bps transcode 2 sec GOP that should match VOD quality" # sift through all of the test variants and find the variant that matches the reference bitrate: same_bitrate_variant = [variant['vmaf_frame_scores'] for variant in variant_list if variant['bitrate'] == target_bitrate][0] recommended_bitrate_variant = [variant['vmaf_frame_scores'] for variant in variant_list if variant['bitrate'] == recommended_bitrate][0] title = " VOD Transcode (4sec GOP) VMAF vs. Matching Bitrate (2sec GOP) Over Asset Duration" plt.title(title, fontsize=14, color='blue') upper = max(upper, max(same_bitrate_variant)) lower = min(lower, min(same_bitrate_variant)) plt.plot(frame_nums, same_bitrate_variant, color='r', label=identical_label) upper = max(upper, max(recommended_bitrate_variant)) lower = min(lower, min(recommended_bitrate_variant)) plt.plot(frame_nums, recommended_bitrate_variant, color='g', label=recommended_label) upper = max(baseline_frame_scores) lower = min(baseline_frame_scores) plt.plot(frame_nums, baseline_frame_scores, color='k', label=baseline_label) # generate major tics based on evenly divided time chosen_frames = np.linspace(0, len(frame_nums) - 1, 20) ticframes = [frame_nums[int(i)] for i in chosen_frames] ticlabels = [timecode.tc_to_string( *timecode.frames_to_tc(ticframe)) for ticframe in ticframes] plt.xticks(ticframes, ticlabels, rotation='vertical') ax = plt.axes() # style = dict(size=10, color='gray') # # label the valleys # for idx, lowval in enumerate(lowest_values): # ax.text(lowval, frame_scores[lowval] - 5, str(idx + 1), **style) ax.set_xticks(ticframes, minor=True) ax.grid() red_line = mlines.Line2D([], [], color='k', label=baseline_label) yellow_line = mlines.Line2D([], [], color='r', label=identical_label) green_line = mlines.Line2D([], [], color='g', label=recommended_label) plt.legend(handles=[red_line, yellow_line, green_line], loc='lower right') plt.ylabel('vmaf score') plt.ylim(lower, upper) plt.xlabel('time') plt.subplots_adjust(bottom=0.3) plt.gcf().set_size_inches(15, 5) plt.savefig(output_path) plt.clf()
def create_quality_report(media_id, local_source, local_transcode, results_directory, subsample): report_data = {} test_data = [] report_data['title'] = media_id report_data['source_file'] = local_source report_data['transcode_file'] = local_transcode source_dimensions = ffprobe_get_frame_size(local_transcode) transcode_dimensions = ffprobe_get_frame_size(local_transcode) transcode_framerate = ffprobe_get_framerate(local_transcode) source_video_duration = ffprobe_get_video_duration(local_source) source_audio_duration = ffprobe_get_audio_duration(local_source) transcode_video_duration = ffprobe_get_video_duration(local_transcode) transcode_audio_duration = ffprobe_get_audio_duration(local_transcode) test_data.append( test_video_expected_duration_match(source_video_duration, transcode_video_duration)) test_data.append( test_audio_expected_duration_match(source_audio_duration, transcode_audio_duration)) test_data.append( test_audio_video_duration_match(transcode_audio_duration, transcode_video_duration)) timecode = Timecode(transcode_framerate, '00:00:00;00') source_loudness, source_true_peak = ffmpeg_get_loudness(local_source) test_data.append(test_source_loudness(source_loudness, source_true_peak)) transcode_loudness, transcode_true_peak = ffmpeg_get_loudness( local_transcode) test_data.append( test_transcode_loudness(transcode_loudness, transcode_true_peak)) report_data['test_data'] = test_data json_path = media_id + "_vmaf.json" scenes = [] # scenes, durations = ffmepg_scene_detect( # local_source, 0.5, source_duration) # save time if we have this file from a previous run -- for debugging if os.path.exists(os.path.join(results_directory, json_path)): score, frames = parse_vmaf_json( os.path.join(results_directory, json_path)) else: score, frames = ffmpeg_run_vmaf_full( local_source, local_transcode, os.path.join(results_directory, json_path), transcode_framerate, transcode_dimensions, subsample) test_data.append(test_program_VMAF(score)) frame_nums, frame_scores = zip(*frames) lowest_frames = find_valleys( frame_scores, (source_video_duration * float(timecode.framerate)) / subsample) image_path = "vmaf.png" plot_vmaf_graph(os.path.join(results_directory, image_path), frame_nums, frame_scores, source_video_duration, lowest_frames, timecode) low_frames = [] try: frame_triptychs = ffmpeg_grab_frame_triptychs_fast( lowest_frames, local_source, local_transcode, results_directory, transcode_framerate, transcode_dimensions) except: frame_triptychs = ffmpeg_grab_frame_triptychs_slow( lowest_frames, local_source, local_transcode, results_directory, transcode_framerate, transcode_dimensions) for idx, low_frame in enumerate(lowest_frames): low_frame_dict = {} low_frame_dict['index'] = 1 + idx low_frame_dict['frame_grab'] = frame_triptychs[idx] low_frame_dict['frame_time'] = timecode.tc_to_string( *timecode.frames_to_tc(low_frame)) low_frame_dict['frame_vmaf'] = frame_scores[low_frame] low_frames.append(low_frame_dict) report_data['vmaf_score'] = score report_data['vmaf_graph'] = image_path duration_tc = timecode.frames_to_tc( timecode.float_to_tc(source_video_duration)) report_data['duration'] = timecode.tc_to_string(*duration_tc) report_data['full_vmaf_json'] = json_path report_data['low_frames'] = low_frames # write the unformatted json version of our results write_dict_to_json(report_data, os.path.join(results_directory, media_id + "_QC.json")) # write the formatted html version of our results write_html_report( report_data, os.path.join(results_directory, media_id + "_quality.html"))