def skip_frames_ffmpeg(filename, skip=0): if skip == 0: return import os of, fex = os.path.splitext(filename) pts_ratio = 1 / (skip + 1) atempo_ratio = skip + 1 outname = of + '_skip' + fex if has_audio(filename): cmd = [ 'ffmpeg', '-y', '-i', filename, '-filter_complex', f'[0:v]setpts={pts_ratio}*PTS[v];[0:a]atempo={atempo_ratio}[a]', '-map', '[v]', '-map', '[a]', '-q:v', '3', '-shortest', outname ] else: cmd = [ 'ffmpeg', '-y', '-i', filename, '-filter_complex', f'[0:v]setpts={pts_ratio}*PTS[v]', '-map', '[v]', '-q:v', '3', outname ] ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Skipping frames:')
def contrast_brightness_ffmpeg(filename, contrast=0, brightness=0): """ Applies contrast and brightness adjustments on the source video using ffmpeg. Args: filename (str): Path to the video to process. contrast (int or float, optional): Increase or decrease contrast. Values range from -100 to 100. Defaults to 0. brightness (int or float, optional): Increase or decrease brightness. Values range from -100 to 100. Defaults to 0. Outputs: `filename`_cb.<file extension> """ if contrast == 0 and brightness == 0: return import os import numpy as np of, fex = os.path.splitext(filename) # keeping values in sensible range contrast = np.clip(contrast, -100.0, 100.0) brightness = np.clip(brightness, -100.0, 100.0) # ranges are "handpicked" so that the results are close to the results of contrast_brightness_cv2 (deprecated) if contrast == 0: p_saturation, p_contrast, p_brightness = 0, 0, 0 elif contrast > 0: p_saturation = scale_num(contrast, 0, 100, 1, 1.9) p_contrast = scale_num(contrast, 0, 100, 1, 2.3) p_brightness = scale_num(contrast, 0, 100, 0, 0.04) elif contrast < 0: p_saturation = scale_num(contrast, 0, -100, 1, 0) p_contrast = scale_num(contrast, 0, -100, 1, 0) p_brightness = 0 if brightness != 0: p_brightness += brightness / 100 outname = of + '_cb' + fex cmd = [ 'ffmpeg', '-y', '-i', filename, '-vf', f'eq=saturation={p_saturation}:contrast={p_contrast}:brightness={p_brightness}', '-q:v', '3', "-c:a", "copy", outname ] ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Adjusting contrast and brightness:')
def contrast_brightness_ffmpeg(filename, contrast=0, brightness=0): if contrast == 0 and brightness == 0: return import os import numpy as np of, fex = os.path.splitext(filename) # keeping values in sensible range contrast = np.clip(contrast, -100.0, 100.0) brightness = np.clip(brightness, -100.0, 100.0) # ranges are "handpicked" so that the results are close to the results of mg_contrast_brightness if contrast == 0: p_saturation, p_contrast, p_brightness = 0, 0, 0 elif contrast > 0: p_saturation = scale_num(contrast, 0, 100, 1, 1.9) p_contrast = scale_num(contrast, 0, 100, 1, 2.3) p_brightness = scale_num(contrast, 0, 100, 0, 0.04) elif contrast < 0: p_saturation = scale_num(contrast, 0, -100, 1, 0) p_contrast = scale_num(contrast, 0, -100, 1, 0) p_brightness = 0 if brightness != 0: p_brightness += brightness / 100 outname = of + '_cb' + fex cmd = [ 'ffmpeg', '-y', '-i', filename, '-vf', f'eq=saturation={p_saturation}:contrast={p_contrast}:brightness={p_brightness}', '-q:v', '3', "-c:a", "copy", outname ] ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Adjusting contrast and brightness:')
def skip_frames_ffmpeg(filename, skip=0): """ Time-shrinks the video by skipping (discarding) every n frames determined by `skip`. To discard half of the frames (ie. double the speed of the video) use `skip=1`. Args: filename (str): Path to the video to process. skip (int, optional): Discard `skip` frames before keeping one. Defaults to 0. Outputs: `filename`_skip.<file extension> """ if skip == 0: return import os of, fex = os.path.splitext(filename) pts_ratio = 1 / (skip + 1) atempo_ratio = skip + 1 outname = of + '_skip' + fex if has_audio(filename): cmd = [ 'ffmpeg', '-y', '-i', filename, '-filter_complex', f'[0:v]setpts={pts_ratio}*PTS[v];[0:a]atempo={atempo_ratio}[a]', '-map', '[v]', '-map', '[a]', '-q:v', '3', '-shortest', outname ] else: cmd = [ 'ffmpeg', '-y', '-i', filename, '-filter_complex', f'[0:v]setpts={pts_ratio}*PTS[v]', '-map', '[v]', '-q:v', '3', outname ] ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Skipping frames:')
def videograms_ffmpeg(self): """ Usees FFMPEG as backend. Averages videoframes by axes, and creates two images of the horizontal-axis and vertical-axis stacks. In these stacks, a single row or column corresponds to a frame from the source video, and the index of the row or column corresponds to the index of the source frame. Outputs ------- - `filename`_vgx.png A horizontal videogram of the source video. - `filename`_vgy.png A vertical videogram of the source video. Returns ------- - list(MgImage, MgImage) A tuple with the string paths to the horizontal and vertical videograms respectively. """ width, height = get_widthheight(self.filename) framecount = get_framecount(self.filename) length = get_length(self.filename) outname = self.of + '_vgy.png' cmd = ['ffmpeg', '-y', '-i', self.filename, '-frames', '1', '-vf', f'scale=1:{height}:sws_flags=area,normalize,tile={framecount}x1', outname] ffmpeg_cmd(cmd, length, pb_prefix="Rendering horizontal videogram:") outname = self.of + '_vgx.png' cmd = ['ffmpeg', '-y', '-i', self.filename, '-frames', '1', '-vf', f'scale={width}:1:sws_flags=area,normalize,tile=1x{framecount}', outname] ffmpeg_cmd(cmd, length, pb_prefix="Rendering vertical videogram:") return MgList([MgImage(self.of+'_vgx.png'), MgImage(self.of+'_vgy.png')])
def history_ffmpeg(self, filename='', history_length=10, weights=1, normalize=False, norm_strength=1, norm_smooth=0): """ This function creates a video where each frame is the average of the n previous frames, where n is determined by `history_length`. The history frames are summed up and normalized, and added to the current frame to show the history. Based on ffmpeg. Parameters ---------- - filename : str, optional Path to the input video file. If not specified the video file pointed to by the MgObject is used. - history_length : int, optional Default is 10. Number of frames to be saved in the history tail. - weights: int, float, str, list, optional Default is 1. Defines the weight or weights applied to the frames in the history tail. If given as list the first element in the list will correspond to the weight of the newest frame in the tail. If given as a str - like "3 1.2 1" - it will be automatically converted to a list - like [3, 1.2, 1]. - normalize: bool, optional Default is `False` (no normalization). If `True`, the history video will be normalized. This can be useful when processing motion (frame difference) videos. - norm_strength: int, float, optional Default is 1. Defines the strength of the normalization where 1 represents full strength. - norm_smooth: int, optional Default is 0 (no smoothing). Defines the number of previous frames to use for temporal smoothing. The input range of each channel is smoothed using a rolling average over the current frame and the `norm_smooth` previous frames. Outputs ------- - `filename`_history.avi Returns ------- - MgObject A new MgObject pointing to the output '_history' video file. """ if filename == '': filename = self.filename of, fex = os.path.splitext(filename) if type(weights) in [int, float]: weights_map = np.ones(history_length) weights_map[-1] = weights str_weights = ' '.join([str(weight) for weight in weights_map]) elif type(weights) == list: typecheck_list = [type(item) in [int, float] for item in weights] if False in typecheck_list: raise ParameterError( 'Found wrong type(s) in the list of weights. Use ints and floats.' ) elif len(weights) > history_length: raise ParameterError( 'history_length must be greater than or equal to the number of weights specified in weights.' ) else: weights_map = np.ones(history_length - len(weights)) weights.reverse() weights_map = list(weights_map) weights_map += weights str_weights = ' '.join([str(weight) for weight in weights_map]) elif type(weights) == str: weights_as_list = weights.split() typecheck_list = [ type(item) in [int, float] for item in weights_as_list ] if False in typecheck_list: raise ParameterError( 'Found wrong type(s) in the list of weights. Use ints and floats.' ) elif len(weights) > history_length: raise ParameterError( 'history_length must be greater than or equal to the number of weights specified in weights.' ) else: weights_map = np.ones(history_length - len(weights_as_list)) weights_as_list.reverse() weights_map += weights_as_list str_weights = ' '.join([str(weight) for weight in weights_map]) else: raise ParameterError( 'Wrong type used for weights. Use int, float, str, or list.') if type(normalize) != bool: raise ParameterError('Wrong type used for normalize. Use only bool.') if normalize: if type(norm_strength) not in [int, float]: raise ParameterError( 'Wrong type used for norm_strength. Use int or float.') if type(norm_smooth) != int: raise ParameterError( 'Wrong type used for norm_smooth. Use only int.') outname = of + '_history' + fex if normalize: if norm_smooth != 0: cmd = [ 'ffmpeg', '-y', '-i', filename, '-filter_complex', f'tmix=frames={history_length}:weights={str_weights},normalize=independence=0:strength={norm_strength}:smoothing={norm_smooth}', '-q:v', '3', '-c:a', 'copy', outname ] else: cmd = [ 'ffmpeg', '-y', '-i', filename, '-filter_complex', f'tmix=frames={history_length}:weights={str_weights},normalize=independence=0:strength={norm_strength}', '-q:v', '3', '-c:a', 'copy', outname ] else: cmd = [ 'ffmpeg', '-y', '-i', filename, '-vf', f'tmix=frames={history_length}:weights={str_weights}', '-q:v', '3', '-c:a', 'copy', outname ] # success = ffmpeg_cmd(cmd, get_length(filename), # pb_prefix='Rendering history video:') ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Rendering history video:') # if success: # destination_video = self.of + '_history' + self.fex # return musicalgestures.MgObject(destination_video, color=self.color, returned_by_process=True) destination_video = self.of + '_history' + self.fex return musicalgestures.MgObject(destination_video, color=self.color, returned_by_process=True)
def videograms_ffmpeg(self): """ Renders horizontal and vertical videograms of the source video using ffmpeg. Averages videoframes by axes, and creates two images of the horizontal-axis and vertical-axis stacks. In these stacks, a single row or column corresponds to a frame from the source video, and the index of the row or column corresponds to the index of the source frame. Outputs: `self.filename`_vgx.png `self.filename`_vgy.png Returns: MgList(MgImage, MgImage): An MgList with the MgImage objects referring to the horizontal and vertical videograms respectively. """ width, height = get_widthheight(self.filename) framecount = get_framecount(self.filename) def calc_skipfactor(width, height, framecount): """ Helper function to calculate the necessary frame-skipping to avoid integer overflow. This makes sure that we can succesfully create videograms even on many-hours-long videos as well. Args: width (int): The width of the video. height (int): The height of the video. framecount (int): The number of frames in the video. Returns: list(int, int): The necessary dilation factors to apply on the video for the horizontal and vertical videograms, respectively. """ intmax = 2147483647 skipfactor_x = int( math.ceil(framecount * 8 / (intmax / (height + 128) - 1024))) skipfactor_y = int( math.ceil(framecount / (intmax / ((width * 8) + 1024) - 128))) return skipfactor_x, skipfactor_y testx, testy = calc_skipfactor(width, height, framecount) if testx > 1 or testy > 1: necessary_skipfactor = max([testx, testy]) print( f'{os.path.basename(self.filename)} is too large to process. Applying minimal skipping necessary...' ) skip_frames_ffmpeg(self.filename, skip=necessary_skipfactor - 1) shortened_file = self.of + '_skip' + self.fex framecount = get_framecount(shortened_file) length = get_length(shortened_file) outname = self.of + '_skip_vgy.png' cmd = [ 'ffmpeg', '-y', '-i', shortened_file, '-vf', f'scale=1:{height}:sws_flags=area,normalize,tile={framecount}x1', '-aspect', f'{framecount}:{height}', '-frames', '1', outname ] ffmpeg_cmd(cmd, length, stream=False, pb_prefix="Rendering horizontal videogram:") outname = self.of + '_skip_vgx.png' cmd = [ 'ffmpeg', '-y', '-i', shortened_file, '-vf', f'scale={width}:1:sws_flags=area,normalize,tile=1x{framecount}', '-aspect', f'{width}:{framecount}', '-frames', '1', outname ] ffmpeg_cmd(cmd, length, stream=False, pb_prefix="Rendering vertical videogram:") return MgList([ MgImage(self.of + '_skip_vgx.png'), MgImage(self.of + '_skip_vgy.png') ]) else: length = get_length(self.filename) outname = self.of + '_vgy.png' cmd = [ 'ffmpeg', '-y', '-i', self.filename, '-frames', '1', '-vf', f'scale=1:{height}:sws_flags=area,normalize,tile={framecount}x1', '-aspect', f'{framecount}:{height}', outname ] ffmpeg_cmd(cmd, length, stream=False, pb_prefix="Rendering horizontal videogram:") outname = self.of + '_vgx.png' cmd = [ 'ffmpeg', '-y', '-i', self.filename, '-frames', '1', '-vf', f'scale={width}:1:sws_flags=area,normalize,tile=1x{framecount}', '-aspect', f'{width}:{framecount}', outname ] ffmpeg_cmd(cmd, length, stream=False, pb_prefix="Rendering vertical videogram:") return MgList( [MgImage(self.of + '_vgx.png'), MgImage(self.of + '_vgy.png')])