def capture_timeline_thumbnails(self, stream_file, filename, duration, thumbnails_amount): """ Capture thumbnails for timeline. :param stream_file: video file :type stream_file: bytes :param filename: tmp video's file name :type filename: str :param duration: video's duration :type duration: int :param thumbnails_amount: total number of thumbnails to capture :type thumbnails_amount: int :return: file stream, metadata generator :return: bytes, generator """ path_video = create_temp_file(stream_file) try: # time period between two frames if thumbnails_amount == 1: frame_per_second = (duration - 0.05) else: frame_per_second = (duration - 0.05) / (thumbnails_amount - 1) # capture list frame via script capture_list_frames.sh path_script = os.path.dirname( __file__) + '/script/capture_list_frames.sh' # create output file path output_file = f"{path_video}_" # subprocess bash -> ffmpeg in the loop subprocess.run([ path_script, path_video, output_file, str(frame_per_second), str(thumbnails_amount) ]) for i in range(0, thumbnails_amount): thumbnail_path = f'{output_file}{i}.png' try: # get metadata thumbnail_metadata = self._get_meta(thumbnail_path) thumbnail_metadata['mimetype'] = 'image/png' # read binary with open(thumbnail_path, "rb") as f: content = f.read() yield content, thumbnail_metadata finally: # delete temp thumbnail file os.remove(thumbnail_path) finally: os.remove(path_video)
def get_meta(self, filestream, extension='tmp'): """ Use ffmpeg tool for getting metadata of file :param filestream: file to get meta from :type filestream: bytes :return: metadata :rtype: dict """ file_temp_path = create_temp_file(filestream) try: metadata = self._get_meta(file_temp_path) finally: os.remove(file_temp_path) return metadata
def edit_video(self, stream_file, filename, trim=None, crop=None, rotate=None, scale=None): """ Use ffmpeg tool for edit video :param stream_file: file to edit :type stream_file: bytes :param filename: filename for tmp file :type filename: str :param trim: trim editing rules :type trim: dict :param crop: crop editing rules :type crop: dict :param video_rotate: rotate degree :type video_rotate: int :param scale: width scale to :type scale: int :return: """ # file extension is required by ffmpeg path_input = create_temp_file(stream_file, suffix=f".{filename.rsplit('.', 1)[-1]}") path_output = '{}_edit.{}'.format(*path_input.rsplit('.', 1)) filter_string = '' try: # get option for trim trim_option = ( '-ss', str(trim['start']), '-t', str(trim['end'] - trim['start']), '-qscale', '0', ) if trim else tuple() # crop # https://ffmpeg.org/ffmpeg-filters.html#crop if crop: filter_string += f'crop={crop["width"]}:{crop["height"]}:{crop["x"]}:{crop["y"]}' # scale # http://ffmpeg.org/ffmpeg-filters.html#scale # https://trac.ffmpeg.org/wiki/Scaling if scale: filter_string += ',' if filter_string != '' else '' filter_string += f"scale={scale}:-2" # rotate # https://ffmpeg.org/ffmpeg-all.html#transpose # 0 = 90CounterCLockwise and Vertical Flip (default) # 1 = 90Clockwise # 2 = 90CounterClockwise # 3 = 90Clockwise and Vertical Flip if rotate: rotate_string = '' if rotate == 90: rotate_string = 'transpose=1' elif rotate == -90: rotate_string = 'transpose=2' elif rotate == 180: rotate_string = 'transpose=1,transpose=1' elif rotate == -180: rotate_string = 'transpose=2,transpose=2' elif rotate == 270: rotate_string = 'transpose=1,transpose=1,transpose=1' elif rotate == -270: rotate_string = 'transpose=2,transpose=2,transpose=2' filter_string += ',' if filter_string != '' else '' filter_string += rotate_string # get option for filter filter_option = ('-filter:v', filter_string) if filter_string else tuple() # run ffmpeg if filter_option or trim_option: # combine trim and filter to run one time self._run_ffmpeg( path_input=path_input, path_output=path_output, options=( *trim_option, *filter_option, '-threads', str(app.config.get('FFMPEG_THREADS')), '-preset', app.config.get('FFMPEG_PRESET') ) ) content = open(path_input, 'rb+').read() metadata_edit_file = self._get_meta(path_input) finally: if path_input: os.remove(path_input) return content, metadata_edit_file
def capture_thumbnail(self, stream_file, filename, duration, position, crop=None, rotate=0): """ Use ffmpeg tool to capture video frame at a position. :param stream_file: video file :type stream_file: bytes :param filename: tmp video's file name :type filename: str :param duration: video's duration :type duration: int :param position: video position to capture a frame :type position: int :param crop: crop editing rules :type crop: dict :param rotate: rotate degree :type rotate: int :return: file stream, metadata :rtype: bytes, dict """ path_video = create_temp_file(stream_file) try: # avoid the last frame, it is null if int(duration) <= int(position): position = duration - 0.1 # create output file path output_file = f"{path_video}_preview_thumbnail.png" vfilter = '' if crop: vfilter = f'-vf crop={crop["width"]}:{crop["height"]}:{crop["x"]}:{crop["y"]}' if rotate: vfilter += ',' if vfilter else '-vf ' transpose = f'transpose=1' if rotate > 0 else f'transpose=2' vfilter += ','.join([transpose] * (rotate // 90)) try: # run ffmpeg command self._run_ffmpeg( path_input=path_video, path_output=output_file, preoptions=('-y', '-accurate_seek'), options=( '-ss', str(position), '-vframes', '1', *shlex.split(vfilter), ), override=False, ) # get metadata thumbnail_metadata = self._get_meta(output_file) thumbnail_metadata['mimetype'] = 'image/png' # read binary with open(output_file, "rb") as f: content = f.read() return content, thumbnail_metadata finally: if os.path.exists(output_file): # delete temp thumbnail file os.remove(output_file) finally: os.remove(path_video)