def select_curate_auto(self, transform_set_ids, name, opts): """ This method automatically curates a transform set. :param list(int) transform_set_ids: The transform set IDs. :param dict opts: The dict of options. :raises: CommandLineRouteHandlerError :rtype: None """ for transform_set_id in transform_set_ids: result = TransformSetModel().select(transform_set_id) if result is None: raise CommandLineRouteHandlerError( f'Transform set with ID {transform_set_id:08d} not found') plugin = Plugin.get('transform_set_select_curate', name) if plugin is None: raise CommandLineRouteHandlerError( f"'{name}' is not a valid transform set curation " f'plugin name') for transform_set_id in transform_set_ids: plugin = Plugin.get('transform_set_select_curate', name) try: plugin().transform_set_select_curate(transform_set_id, opts) except ValueError as e: raise CommandLineRouteHandlerError(str(e)) debug('success', 3)
def insert_youtube_url(self, url, desc=None): """ This method inserts a YouTube video into the video collection. :param str url: The URL of the YouTube video. :param str desc: An optional description of the video. :raises: CommandLineRouteHandlerError :rtype: None """ try: context = self.youtube_context(url) except pytube.exceptions.VideoUnavailable: raise CommandLineRouteHandlerError( f'A VideoUnavailable exception was raised and caught for ' f'{url}') streams = self.youtube_streams(context) stream = self.youtube_select(streams) try: path = self.youtube_download(context, stream[0], VideoDir.path(), self.uuid()) except urllib.error.HTTPError as e: # noqa raise CommandLineRouteHandlerError( f'An HTTPError exception was raised and caught for {url} ' f'with HTTP status code {e.code}') filename = os.path.basename(path) video_id = VideoModel().insert(url, filename, desc) debug( f'video_id={video_id}, uri={url}, filename={filename}, ' f'description={desc}', 3)
def _pad(self, transform_set_path, transform_id, frame_id, metadata, target_set_id, size): """ This method pads a transform. :param str transform_set_path: The transform set path. :param int transform_id: The transform ID. :param int frame_id: The frame ID. :param str metadata: Metadata for the transform. :param int target_set_id: The new transform set ID. :param int size: The size to which to pad. :rtype: None """ transform_path = TransformFile().path(transform_set_path, transform_id, 'jpg') img = cv2.imread(transform_path) img_height, img_width = img.shape[:2] img_padded = np.zeros((size, size, 3), dtype=np.uint8) img_padded[:img_height, :img_width, :] = img.copy() target_id = TransformModel().insert(target_set_id, frame_id, json.dumps(metadata), 0) output_path = TransformFile.path( TransformSetSubDir.path(target_set_id), target_id, 'jpg') cv2.imwrite(output_path, img_padded, [cv2.IMWRITE_JPEG_QUALITY, 100]) debug( f'Transform with ID {target_id:08d} at {output_path} extracted ' f'from transform with ID {transform_id:08d} at {transform_path}', 4)
def select_extract(self, video_ids, opts={}): """ This method extracts frames and thumbnails from a video to a frame set. :param list(int) video_ids: The video IDs. :param dict opts: The optional dict of options. :raises: CommandLineRouteHandlerError :rtype: None """ video_model = VideoModel() for video_id in video_ids: result = video_model.select(video_id) if result is None: raise CommandLineRouteHandlerError( f'Video with ID {video_id:08d} not found') plugin = Plugin.get('video_select_extract', 'default') for video_id in video_ids: frame_set_id = plugin().video_select_extract( video_id, sub_sample=int(opts.get('sub-sample', 1)), max_sample=int(opts.get('max-sample', 0))) debug(f'frame_set_id={frame_set_id}, video_id={video_id}', 3)
def select_deploy(self, video_id, name, opts): """ This method deploys a video via a plugin. :param int video_id: The video ID. :param str name: The plugin name. :param opts dict: The dict of options. :rtype: None """ video_model = VideoModel() result = video_model.select(video_id) if result is None: raise CommandLineRouteHandlerError( f'Video with ID {video_id:08d} not found') plugin = Plugin.get('video_select_deploy', name) if plugin is None: raise CommandLineRouteHandlerError( f"'{name}' is not a valid frame set extraction plugin name") plugin().video_select_deploy(video_id, opts) debug('success', 3)
def delete(self, video_ids): """ This method deletes a video from the video collection. :param list(ints) video_ids: The video IDs. :raises: CommandLineRouteHandlerError :rtype: None """ video_model = VideoModel() for video_id in video_ids: result = video_model.select(video_id) if result is None: raise CommandLineRouteHandlerError( f'Video with ID {video_id:08d} not found') for video_id in video_ids: result = video_model.select(video_id) path = VideoFile.path(result[2]) video_model.delete(video_id) os.remove(path) debug(f'Video {video_id} was successfully deleted', 3)
def insert_vimeo_url(self, url, quality='hq', desc=None): """ This method inserts a Vimeo video into the video collection. :param str url: The URL of the Vimeo video. :param str desc: An optional description of the video. :raises: CommandLineRouteHandlerError :rtype: None """ try: video = self.vimeo_context(url) except KeyError: raise CommandLineRouteHandlerError( f'A KeyError exception was raised and caught for ' f'{url}') if desc is None: desc = self.vimeo_title(video) stream = self.vimeo_stream(video, quality) filename = f'{self.uuid()}.mp4' self.vimeo_stream_download(stream, VideoDir.path(), filename) video_id = VideoModel().insert(url, filename, desc) debug( f'video_id={video_id}, uri={url}, filename={filename}, ' f'description={desc}', 3)
def _resize(self, transform_set_path, transform_id, frame_id, metadata, target_set_id, max_size): """ This method resizes a transform. :param str transform_set_path: The transform set path. :param int transform_id: The transform ID. :param int frame_id: The frame ID. :param str metadata: Metadata for the transform. :param int target_set_id: The new transform set ID. :param int max_size: The max size. :rtype: None """ transform_path = TransformFile().path(transform_set_path, transform_id, 'jpg') img = cv2.imread(transform_path) img_height, img_width = img.shape[:2] if img_height > max_size or img_width > max_size: if img_height > img_width: img = imutils.resize(img, height=max_size) else: img = imutils.resize(img, width=max_size) target_id = TransformModel().insert(target_set_id, frame_id, json.dumps(metadata), 0) output_path = TransformFile.path( TransformSetSubDir.path(target_set_id), target_id, 'jpg') cv2.imwrite(output_path, img, [cv2.IMWRITE_JPEG_QUALITY, 100]) debug(f'Transform with ID {target_id:08d} at {output_path} extracted ' f'from transform with ID {transform_id:08d} at {transform_path}', 4)
def select_export_dir(self, frame_set_ids, target_dir, opts={}): """ This method exports frame sets to a directory. :param list frame_set_ids: The list of frame set IDs to export. :param str target_dir: The directory to which to export the frame sets. :param dict opts: The dict of options. :raises: CommandLineRouteHandlerError :rtype: None """ frame_set_model = FrameSetModel() for frame_set_id in frame_set_ids: result = frame_set_model.select(frame_set_id) if result is None: raise CommandLineRouteHandlerError( f'Frame set with ID {frame_set_id:08d} not found') frame_model = FrameModel() length = int(os.environ.get('MODEL_LIST_LENGTH', '100')) count = 0 for fid in frame_set_ids: offset = 0 frame_set_path = FrameSetSubDir.path(fid) while True: results = frame_model.list(fid, length=length, offset=offset, rejected=False) if not results: break for frame_id, _, _ in results: file_path = FrameFile().path(frame_set_path, frame_id, 'jpg') if 'format' in opts: filename = opts['format'] % frame_id else: filename = os.path.basename(file_path) target_path = os.path.join(target_dir, filename) shutil.copy(file_path, target_path) count += 1 debug( f'Frame with ID {frame_id:08d} at {file_path} ' f'exported to {target_path}', 4) offset += length debug(f'{count} frames were successfully exported to {target_dir}', 3)
def pytube_on_progress_callback(stream, chunk, file_handle, bytes_remaining): """ This function is called by putube as a video is downloaded and is used to print download progress. :rtype: None """ t = f'{(1.0 - bytes_remaining / stream.filesize) * 100.0:3.2f}%'.zfill(7) debug(t, 4)
def transform_set_select_curate(self, transform_set_id, opts): """ This method automatically curates a transform set and rejects transforms that are more blurry than the 'max-blur'. :param int transform_set_id: The transform set ID. :param dict opts: The dict of options. :raises: ValueError :rtype: None """ if 'max-blur' not in opts: raise ValueError( 'The max-blur option is required but was not supplied') max_blur = float(opts['max-blur']) transform_model = TransformModel() length = 100 offset = 0 p1 = TransformSetSubDir.path(transform_set_id) while True: transforms = transform_model.list(transform_set_id, length=length, offset=offset) if not transforms: break for transform in transforms: p2 = TransformFile.path(p1, transform[0], 'jpg') debug(f'Curating transform with ID {transform[0]:08d} at {p2}', 4) image = cv2.imread(p2) image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) h, w = image.shape[:2] # recommendation to scale down image to ~500 if h > 600 or w > 600: # imutils.resize preserves aspect ratio. image = imutils.resize(image, width=500, height=500) score = cv2.Laplacian(image, cv2.CV_64F).var() if score < max_blur: transform_model.update(transform[0], rejected=1) debug(f'Transform with ID {transform[0]:08d} rejected', 4) offset += length
def usage(self): """ This method prints usage. :rtype: None """ path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'frame_set_command_line_route_handler_usage.txt') with open(path, 'r') as file_: usage = file_.read() usage = usage.strip() debug(usage, 3)
def frame_set_select_extract(self, frame_set_id, opts): """ This method extracts a frame set to a transform set. :param int frame_set_id: The frame set ID. :param dict opts: The dict of options. :rtype: int """ transform_set_id = TransformSetModel().insert('transform_set', frame_set_id) p1 = TransformSetSubDir.path(transform_set_id) os.mkdir(p1) frame_model = FrameModel() transform_model = TransformModel() length = int(os.environ.get('MODEL_LIST_LENGTH', '100')) offset = 0 p2 = FrameSetSubDir.path(frame_set_id) while True: frames = frame_model.list(frame_set_id, length=length, offset=offset, rejected=False) if not frames: break for frame in frames: transform_id = transform_model.insert(transform_set_id, frame[0], None, 0) p3 = FrameFile.path(p2, frame[0], 'jpg') p4 = TransformFile.path(p1, transform_id, 'jpg') shutil.copy(p3, p4) debug(f'Transform with ID {transform_id:08d} at {p4} ' f'extracted from frame with ID {frame[0]:08d} at ' f'{p3}', 4) offset += length return transform_set_id
def list(self, transform_set_id): """ This method lists transforms in the transform collection for a transform set. :param int transform_set_id: The transform set ID. :raises: CommandLineRouteHandlerError :rtype: None """ result = TransformSetModel().select(transform_set_id) if result is None: raise CommandLineRouteHandlerError( f'Transform set with ID {transform_set_id:08d} not found') transform_model = TransformModel() count = transform_model.count(transform_set_id) debug(f'{count} results', 3) debug( 'id | fk_transform_sets | fk_frames | metadata | rejected | ' '(width | height)', 3) debug( '-----------------------------------------------------------' '----------------', 3) if count == 0: return length = int(os.environ.get('MODEL_LIST_LENGTH', '100')) offset = 0 p1 = TransformSetSubDir.path(transform_set_id) while True: transforms = transform_model.list(transform_set_id, length=length, offset=offset) if not transforms: break for transform in transforms: p2 = TransformFile.path(p1, transform[0], 'jpg') height, width, _ = cv2.imread(p2).shape debug( f'{transform[0]} | {transform[1]} | {transform[2]} | ' f'{transform[3]} | {transform[4]} | ({width} | ' f'{height})', 3) offset += length
def transform_set_select_curate(self, transform_set_id, opts): """ This method automatically curates a transform set and rejects transforms with width or height less than 'min-size'. :param int transform_set_id: The transform set ID. :param dict opts: The dict of options. :raises: ValueError :rtype: None """ if 'min-size' not in opts: raise ValueError( 'The min-size option is required but was not supplied') min_length = int(opts['min-size']) transform_model = TransformModel() length = 100 offset = 0 p1 = TransformSetSubDir.path(transform_set_id) while True: transforms = transform_model.list(transform_set_id, length=length, offset=offset) if not transforms: break for transform in transforms: p2 = TransformFile.path(p1, transform[0], 'jpg') debug(f'Curating transform with ID {transform[0]:08d} at {p2}', 4) h, w = cv2.imread(p2).shape[:2] if h < min_length or w < min_length: transform_model.update(transform[0], rejected=1) debug(f'Transform with ID {transform[0]:08d} rejected', 4) offset += length
def implode(self): """ This method implodes the deepstar path including delete the DB and all files. :rtype: None """ q = f'Are you sure you want to implode the deepstar path ' \ f'{os.environ["DEEPSTAR_PATH"]} [y/n]? ' y = 'The deepstar path was succesfully imploded' n = 'The deepstar path was not imploded' if input(q).strip() == 'y': for path in [DBDir.path(), FileDir.path()]: shutil.rmtree(path) debug(y, 3) else: debug(n, 3)
def select_extract(self, transform_set_ids, name, opts): """ This method runs a transformation plugin over previously created transform sets. :param list(int) transform_set_id: The transform set IDs. :param str name: The name of the transform set extraction plugin to use. :param dict opts: The dict of options. :raises: CommandLineRouteHandlerError :rtype: None """ transform_set_model = TransformSetModel() for transform_set_id in transform_set_ids: result = transform_set_model.select(transform_set_id) if result is None: raise CommandLineRouteHandlerError( f'Transform set with ID {transform_set_id:08d} not found') plugin = Plugin.get('transform_set_select_extract', name) if plugin is None: raise CommandLineRouteHandlerError( f"'{name}' is not a valid transform set extraction " f'plugin name') for transform_set_id in transform_set_ids: try: new_transform_set_id = plugin() \ .transform_set_select_extract(transform_set_id, opts) except ValueError as e: raise CommandLineRouteHandlerError(str(e)) result = transform_set_model.select(new_transform_set_id) debug( f'transform_set_id={result[0]}, name={result[1]}, ' f'fk_frame_sets={result[2]}, ' f'fk_prev_transform_sets={result[3]}', 3)
def list(self): """ This method lists videos in the video collection. :rtype: None """ result = VideoModel().list() debug(f'{len(result)} results', 3) debug('id | uri | filename | description', 3) debug('---------------------------------', 3) for r in result: debug(f'{r[0]} | {r[1]} | {r[2]} | {r[3]}', 3)
def list(self): """ This method lists transform sets in the transform set collection. :rtype: None """ result = TransformSetModel().list() debug(f'{len(result)} results', 3) debug('id | name | fk_frame_sets | fk_prev_transform_sets', 3) debug('--------------------------------------------------', 3) for r in result: debug(f'{r[0]} | {r[1]} | {r[2]} | {r[3]}', 3)
def list(self): """ This method lists frame sets in the frame set collection. :rtype: None """ result = FrameSetModel().list() debug(f'{len(result)} results', 3) debug('id | fk_videos', 3) debug('-------------', 3) for r in result: debug(f'{r[0]} | {r[1]}', 3)
def delete(self, frame_set_ids): """ This method deletes a frame set from the frame set collection. :param list(int) frame_set_ids: The frame set IDs. :raises: CommandLineRouteHandlerError :rtype: None """ frame_set_model = FrameSetModel() for frame_set_id in frame_set_ids: result = frame_set_model.select(frame_set_id) if result is None: raise CommandLineRouteHandlerError( f'Frame set with ID {frame_set_id:08d} not found') for frame_set_id in frame_set_ids: frame_set_model.delete(frame_set_id) shutil.rmtree(FrameSetSubDir.path(frame_set_id)) debug(f'Frame set {frame_set_id} was successfully deleted', 3)
def list(self, frame_set_id): """ This method lists frames in the frame collection for a frame set. :param int frame_set_id: The frame set ID. :raises: CommandLineRouteHandlerError :rtype: None """ result = FrameSetModel().select(frame_set_id) if result is None: raise CommandLineRouteHandlerError( f'Frame set with ID {frame_set_id:08d} not found') frame_model = FrameModel() count = frame_model.count(frame_set_id) debug(f'{count} results', 3) debug('id | fk_frame_sets | rejected | (width | height)', 3) debug('------------------------------------------------', 3) if count == 0: return length = int(os.environ.get('MODEL_LIST_LENGTH', '100')) offset = 0 p1 = FrameSetSubDir.path(frame_set_id) while True: frames = frame_model.list(frame_set_id, length=length, offset=offset) if not frames: break for frame in frames: p2 = FrameFile.path(p1, frame[0], 'jpg') height, width, _ = cv2.imread(p2).shape debug( f'{frame[0]} | {frame[1]} | {frame[2]} | ({width} | ' f'{height})', 3) offset += length
def select_extract(self, frame_set_ids, name, opts): """ This method extracts portions of frames to transform sets. :param list(int) frame_set_ids: The frame set IDs. :param str name: The name of the frame set extraction plugin to use. :param dict opts: The dict of options. :raises: CommandLineRouteHandlerError :rtype: None """ frame_set_model = FrameSetModel() for frame_set_id in frame_set_ids: result = frame_set_model.select(frame_set_id) if result is None: raise CommandLineRouteHandlerError( f'Frame set with ID {frame_set_id:08d} not found') plugin = Plugin.get('frame_set_select_extract', name) if plugin is None: raise CommandLineRouteHandlerError( f"'{name}' is not a valid frame set extraction plugin name") transform_set_model = TransformSetModel() for frame_set_id in frame_set_ids: transform_set_id = plugin().frame_set_select_extract( frame_set_id, opts) result = transform_set_model.select(transform_set_id) debug( f'transform_set_id={result[0]}, name={result[1]}, ' f'fk_frame_sets={result[2]}, ' f'fk_prev_transform_sets={result[3]}', 3)
def select_merge_non_default(self, transform_set_ids, name, opts): """ This method merges multiple transform sets into one transform set. :param list(int) transform_set_ids: The transform set IDs. :param str name: The name of the transform set merge plugin to use. :raises: CommandLineRouteHandlerError :rtype: None """ transform_set_model = TransformSetModel() for transform_set_id in transform_set_ids: result = transform_set_model.select(transform_set_id) if result is None: raise CommandLineRouteHandlerError( f'Transform set with ID {transform_set_id:08d} not found') plugin = Plugin.get('transform_set_select_merge', name) if plugin is None: raise CommandLineRouteHandlerError( f"'{name}' is not a valid transform set merge plugin name") try: new_transform_set_id = plugin().transform_set_select_merge( transform_set_ids, opts) except ValueError as e: raise CommandLineRouteHandlerError(str(e)) result = transform_set_model.select(new_transform_set_id) debug( f'transform_set_id={result[0]}, name={result[1]}, ' f'fk_frame_sets={result[2]}, ' f'fk_prev_transform_sets={result[3]}', 3)
def image_paths(): offset = 0 p1 = TransformSetSubDir.path(transform_set_id) while True: transforms = transform_model.list(transform_set_id, length=length, offset=offset, rejected=False) if not transforms: break for transform in transforms: image_path = TransformFile.path( p1, transform[0], 'jpg') yield image_path debug( f'Transform with ID {transform[0]:08d} at ' f'{image_path} exported to {video_path}', 4) offset += length
def delete(self, transform_set_ids): """ This method deletes a transform set from the transform set collection. :param list(int) transform_set_ids: The transform set IDs. :raises: CommandLineRouteHandlerError :rtype: None """ transform_set_model = TransformSetModel() for transform_set_id in transform_set_ids: result = transform_set_model.select(transform_set_id) if result is None: raise CommandLineRouteHandlerError( f'Transform set with ID {transform_set_id:08d} not found') for transform_set_id in transform_set_ids: transform_set_model.delete(transform_set_id) shutil.rmtree(TransformSetSubDir.path(transform_set_id)) debug(f'Transform set {transform_set_id} was successfully deleted', 3)
def insert_file(self, path, opts={}): """ This method inserts a local video into the video colletion. :param str path: The path to the video. :param dict opts: The optional dict of options. :raises: CommandLineRouteHandlerError :rtype: None """ if not os.path.isfile(path): raise CommandLineRouteHandlerError(f'File {path} not found') filename = self.uuid() + os.path.splitext(path)[1] shutil.copy(path, VideoDir.path() + '/' + filename) desc = opts.get('description', None) video_id = VideoModel().insert(path, filename, desc) debug( f'video_id={video_id}, uri={path}, filename={filename}, ' f'description={desc}', 3)
def insert_images(self, images_path, opts): """ This method inserts a frame set from a directory of images. :param str images_path: The path to the directory containing the images to insert. :param dict opts: The dict of options. :raises: CommandLineRouteHandlerError :rtype: None """ if not os.path.isdir(images_path): raise CommandLineRouteHandlerError( f'The path at {images_path} is not a directory') frame_set_id = FrameSetModel().insert(None) p1 = FrameSetSubDir.path(frame_set_id) os.makedirs(p1) frame_model = FrameModel() for image_path in glob.glob(os.path.join(images_path, '*')): ext = os.path.splitext(image_path)[1].lower() if ext not in ['.jpg', '.png']: debug( f"Skipped image at {image_path} because it does not " f"have a '.jpg' or '.png' file extension", 4) continue image = cv2.imread(image_path) frame_id = frame_model.insert(frame_set_id, 0) p2 = FrameFile.path(p1, frame_id, 'jpg') cv2.imwrite(p2, image, [cv2.IMWRITE_JPEG_QUALITY, 100]) thumbnail = imutils.resize(image, width=192, height=192) p3 = FrameFile.path(p1, frame_id, 'jpg', '192x192') cv2.imwrite(p3, thumbnail, [cv2.IMWRITE_JPEG_QUALITY, 100]) debug( f'Image at {image_path} inserted with ID {frame_id:08d} at ' f'{p2} and {p3}', 4) debug(f'frame_set_id={frame_set_id}, fk_videos=None', 3)
def video_select_detect(self, video_path, opts): """ Runs the MesoNet classifier over the selected video. Returns True if the video is a deepfale, False if the video is authentic, and None if no analysis could be performed. :param str video_path: The path on local disk to the video. :rtype: bool """ vc = cv2.VideoCapture(video_path) face_limit = -1 if opts.get('face-limit'): face_limit = int(opts.get('face-limit')) threshold = 0.5 if opts.get('threshold'): threshold = float(opts.get('threshold')) if threshold > 1 or threshold < 0: threshold = 0.5 face_set = [] while face_limit == -1 or len(face_set) < face_limit: ret, frame = vc.read() if not ret: break face_set.extend(self.get_faces(frame)) if len(face_set) < 10: debug( 'Less than 10 faces were extracted from this video' + ' - detection is unable to be performed', 2) return None predictions = self.mesonet.predict( x=np.array(face_set), batch_size=10, verbose=0, ) prediction_mean = np.mean(predictions) if prediction_mean > threshold: debug(f'Video is a deepfake ({prediction_mean:.04f})', 2) return True debug(f'Video is authentic ({prediction_mean:.04f})', 2) return False
def select_merge(self, frame_set_ids, video_id=None, rejected=False): """ This method merges multiple frame sets into one frame set. :param list(int) frame_set_ids: The frame set IDs. :param int video_id: The video ID to which this frame set corresponds (if any). The default value is None. :param bool rejected: True if should include rejected frames else False if should not. The default value is False. :raises: CommandLineRouteHandlerError :rtype: None """ frame_set_model = FrameSetModel() for frame_set_id in frame_set_ids: result = frame_set_model.select(frame_set_id) if result is None: raise CommandLineRouteHandlerError( f'Frame set with ID {frame_set_id:08d} not found') frame_set_id = frame_set_model.insert(video_id) p1 = FrameSetSubDir.path(frame_set_id) os.makedirs(p1) frame_model = FrameModel() length = int(os.environ.get('MODEL_LIST_LENGTH', '100')) for frame_set_id_ in frame_set_ids: offset = 0 p2 = FrameSetSubDir.path(frame_set_id_) while True: frames = frame_model.list(frame_set_id_, length=length, offset=offset, rejected=rejected) if not frames: break for frame in frames: frame_id = frame_model.insert(frame_set_id, frame[2]) p3 = FrameFile.path(p2, frame[0], 'jpg') p4 = FrameFile.path(p2, frame[0], 'jpg', '192x192') p5 = FrameFile.path(p1, frame_id, 'jpg') p6 = FrameFile.path(p1, frame_id, 'jpg', '192x192') shutil.copy(p3, p5) shutil.copy(p4, p6) debug( f'Frame with ID {frame[0]:08d} and thumbnail at ' f'{p3} and {p4} merged as ID {frame_id:08d} at ' f'{p5} and {p6}', 4) offset += length debug(f'frame_set_id={frame_set_id}, fk_videos={video_id}', 3)