def ParseFFMPEGMimeText(lines): try: (input_line, ) = [l for l in lines if l.startswith('Input #0')] # Input #0, matroska, webm, from 'm.mkv': text = input_line[10:] mime_text = text.split(', from')[0] return mime_text except: raise HydrusExceptions.DamagedOrUnusualFileException( 'Error reading mime!')
def ParseFFMPEGVideoResolution( lines ): try: line = ParseFFMPEGVideoLine( lines ) # get the size, of the form 460x320 (w x h) match = re.search(" [0-9]*x[0-9]*(,| )", line) resolution_string = line[match.start():match.end()-1] ( width_string, height_string ) = resolution_string.split( 'x' ) width = int( width_string ) height = int( height_string ) sar_match = re.search( "[\\[\\s]SAR [0-9]*:[0-9]* ", line ) if sar_match is not None: # ' SAR 2:3 ' sar_string = line[ sar_match.start() : sar_match.end() ] # '2:3' sar_string = sar_string[5:-1] ( sar_width_string, sar_height_string ) = sar_string.split( ':' ) sar_width = int( sar_width_string ) sar_height = int( sar_height_string ) width *= sar_width width //= sar_height return ( width, height ) except: raise HydrusExceptions.DamagedOrUnusualFileException( 'Error parsing resolution!' )
def ParseFFMPEGVideoResolution(lines): try: line = ParseFFMPEGVideoLine(lines) # get the size, of the form 460x320 (w x h) match = re.search(" [0-9]*x[0-9]*(,| )", line) resolution = list( map(int, line[match.start():match.end() - 1].split('x'))) sar_match = re.search(" SAR [0-9]*:[0-9]* ", line) if sar_match is not None: # ' SAR 2:3 ' sar_string = line[sar_match.start():sar_match.end()] # '2:3' sar_string = sar_string[5:-1] (sar_w, sar_h) = sar_string.split(':') (sar_w, sar_h) = (int(sar_w), int(sar_h)) (x, y) = resolution x *= sar_w x //= sar_h resolution = (x, y) return resolution except: raise HydrusExceptions.DamagedOrUnusualFileException( 'Error parsing resolution!')
def ParseFFMPEGFPS(lines, png_ok=False): try: line = ParseFFMPEGVideoLine(lines, png_ok=png_ok) (possible_results, confident) = ParseFFMPEGFPSPossibleResults(line) if len(possible_results) == 0: fps = 1 confident = False else: fps = min(possible_results) return (fps, confident) except: raise HydrusExceptions.DamagedOrUnusualFileException( 'Error estimating framerate!')
def ParseFFMPEGFPS( first_second_lines ): try: line = ParseFFMPEGVideoLine( first_second_lines ) # get the frame rate possible_results = set() match = re.search("( [0-9]*.| )[0-9]* tbr", line) if match is not None: tbr = line[match.start():match.end()].split(' ')[1] tbr_fps_is_likely_garbage = match is None or tbr.endswith( 'k' ) or float( tbr ) > 144 if not tbr_fps_is_likely_garbage: possible_results.add( float( tbr ) ) # match = re.search("( [0-9]*.| )[0-9]* fps", line) if match is not None: fps = line[match.start():match.end()].split(' ')[1] fps_is_likely_garbage = match is None or fps.endswith( 'k' ) or float( fps ) > 144 if not fps_is_likely_garbage: possible_results.add( float( fps ) ) num_frames_in_first_second = ParseFFMPEGNumFramesManually( first_second_lines ) confident = len( possible_results ) <= 1 if len( possible_results ) == 0: fps = num_frames_in_first_second confident = False else: # in some cases, fps is 0.77 and tbr is incorrectly 20. extreme values cause bad results. let's default to slowest, but test our actual first second for most legit-looking sensible_first_second = num_frames_in_first_second > 1 fps = min( possible_results ) fps_matches_with_first_second = False for possible_fps in possible_results: if num_frames_in_first_second - 1 <= possible_fps and possible_fps <= num_frames_in_first_second + 1: fps = possible_fps fps_matches_with_first_second = True break confident = sensible_first_second and fps_matches_with_first_second if fps is None or fps == 0: fps = 1 confident = False return ( fps, confident ) except: raise HydrusExceptions.DamagedOrUnusualFileException( 'Error estimating framerate!' )
def GetFFMPEGVideoProperties( path, force_count_frames_manually = False ): first_second_lines = GetFFMPEGInfoLines( path, count_frames_manually = True, only_first_second = True ) ( has_video, video_format ) = ParseFFMPEGVideoFormat( first_second_lines ) if not has_video: raise HydrusExceptions.DamagedOrUnusualFileException( 'File did not appear to have a video stream!' ) resolution = ParseFFMPEGVideoResolution( first_second_lines ) ( file_duration_in_s, stream_duration_in_s ) = ParseFFMPEGDuration( first_second_lines ) # this will have to be fixed when I add audio, and dynamically accounted for on dual vid/audio rendering duration = stream_duration_in_s ( fps, confident_fps ) = ParseFFMPEGFPS( first_second_lines ) if duration is None and not confident_fps: # ok default to fall back on ( fps, confident_fps ) = ( 24, True ) if fps is None or fps == 0: fps = 1 if duration is None: force_count_frames_manually = True num_frames_inferrence_likely_odd = True # i.e. inferrence not possible! else: num_frames_estimate = int( duration * fps ) # if file is big or long, don't try to force a manual count when one not explicitly asked for # we don't care about a dropped frame on a 10min vid tbh num_frames_seems_ok_to_count = num_frames_estimate < 2400 file_is_ok_size = os.path.getsize( path ) < 128 * 1024 * 1024 if num_frames_seems_ok_to_count and file_is_ok_size: last_frame_has_unusual_duration = num_frames_estimate != duration * fps unusual_video_start = file_duration_in_s != stream_duration_in_s if not confident_fps or last_frame_has_unusual_duration or unusual_video_start: force_count_frames_manually = True if force_count_frames_manually: lines = GetFFMPEGInfoLines( path, count_frames_manually = True ) num_frames = ParseFFMPEGNumFramesManually( lines ) if duration is None: duration = num_frames / fps else: num_frames = int( duration * fps ) duration_in_ms = int( duration * 1000 ) return ( resolution, duration_in_ms, num_frames )
def GenerateInfo(self, status_hook=None): if self._pre_import_file_status.mime is None: if status_hook is not None: status_hook('generating filetype') mime = HydrusFileHandling.GetMime(self._temp_path) self._pre_import_file_status.mime = mime else: mime = self._pre_import_file_status.mime if HG.file_import_report_mode: HydrusData.ShowText('File import job mime: {}'.format( HC.mime_string_lookup[mime])) new_options = HG.client_controller.new_options if mime in HC.DECOMPRESSION_BOMB_IMAGES and not self._file_import_options.AllowsDecompressionBombs( ): if HG.file_import_report_mode: HydrusData.ShowText( 'File import job testing for decompression bomb') if HydrusImageHandling.IsDecompressionBomb(self._temp_path): if HG.file_import_report_mode: HydrusData.ShowText( 'File import job: it was a decompression bomb') raise HydrusExceptions.DecompressionBombException( 'Image seems to be a Decompression Bomb!') if status_hook is not None: status_hook('generating file metadata') self._file_info = HydrusFileHandling.GetFileInfo(self._temp_path, mime=mime) (size, mime, width, height, duration, num_frames, has_audio, num_words) = self._file_info if HG.file_import_report_mode: HydrusData.ShowText('File import job file info: {}'.format( self._file_info)) if mime in HC.MIMES_WITH_THUMBNAILS: if status_hook is not None: status_hook('generating thumbnail') if HG.file_import_report_mode: HydrusData.ShowText('File import job generating thumbnail') bounding_dimensions = HG.client_controller.options[ 'thumbnail_dimensions'] thumbnail_scale_type = HG.client_controller.new_options.GetInteger( 'thumbnail_scale_type') (clip_rect, target_resolution ) = HydrusImageHandling.GetThumbnailResolutionAndClipRegion( (width, height), bounding_dimensions, thumbnail_scale_type) percentage_in = HG.client_controller.new_options.GetInteger( 'video_thumbnail_percentage_in') try: self._thumbnail_bytes = HydrusFileHandling.GenerateThumbnailBytes( self._temp_path, target_resolution, mime, duration, num_frames, clip_rect=clip_rect, percentage_in=percentage_in) except Exception as e: raise HydrusExceptions.DamagedOrUnusualFileException( 'Could not render a thumbnail: {}'.format(str(e))) if mime in HC.FILES_THAT_HAVE_PERCEPTUAL_HASH: if status_hook is not None: status_hook('generating similar files metadata') if HG.file_import_report_mode: HydrusData.ShowText( 'File import job generating perceptual_hashes') self._perceptual_hashes = ClientImageHandling.GenerateShapePerceptualHashes( self._temp_path, mime) if HG.file_import_report_mode: HydrusData.ShowText( 'File import job generated {} perceptual_hashes: {}'. format(len(self._perceptual_hashes), [ perceptual_hash.hex() for perceptual_hash in self._perceptual_hashes ])) if HG.file_import_report_mode: HydrusData.ShowText('File import job generating other hashes') if status_hook is not None: status_hook('generating additional hashes') self._extra_hashes = HydrusFileHandling.GetExtraHashesFromPath( self._temp_path) has_icc_profile = False if mime in HC.FILES_THAT_CAN_HAVE_ICC_PROFILE: try: pil_image = HydrusImageHandling.RawOpenPILImage( self._temp_path) has_icc_profile = HydrusImageHandling.HasICCProfile(pil_image) except: pass self._has_icc_profile = has_icc_profile if mime in HC.FILES_THAT_CAN_HAVE_PIXEL_HASH and duration is None: try: self._pixel_hash = HydrusImageHandling.GetImagePixelHash( self._temp_path, mime) except: pass self._file_modified_timestamp = HydrusFileHandling.GetFileModifiedTimestamp( self._temp_path)
def GeneratePILImage(path): try: pil_image = PILImage.open(path) except Exception as e: raise HydrusExceptions.DamagedOrUnusualFileException( 'Could not load the image--it was likely malformed!') if pil_image.format == 'JPEG' and hasattr(pil_image, '_getexif'): try: exif_dict = pil_image._getexif() except: exif_dict = None if exif_dict is not None: EXIF_ORIENTATION = 274 if EXIF_ORIENTATION in exif_dict: orientation = exif_dict[EXIF_ORIENTATION] if orientation == 1: pass # normal elif orientation == 2: # mirrored horizontal pil_image = pil_image.transpose(PILImage.FLIP_LEFT_RIGHT) elif orientation == 3: # 180 pil_image = pil_image.transpose(PILImage.ROTATE_180) elif orientation == 4: # mirrored vertical pil_image = pil_image.transpose(PILImage.FLIP_TOP_BOTTOM) elif orientation == 5: # seems like these 90 degree rotations are wrong, but fliping them works for my posh example images, so I guess the PIL constants are odd # mirrored horizontal, then 90 CCW pil_image = pil_image.transpose( PILImage.FLIP_LEFT_RIGHT).transpose(PILImage.ROTATE_90) elif orientation == 6: # 90 CW pil_image = pil_image.transpose(PILImage.ROTATE_270) elif orientation == 7: # mirrored horizontal, then 90 CCW pil_image = pil_image.transpose( PILImage.FLIP_LEFT_RIGHT).transpose( PILImage.ROTATE_270) elif orientation == 8: # 90 CCW pil_image = pil_image.transpose(PILImage.ROTATE_90) if pil_image is None: raise Exception('The file at ' + path + ' could not be rendered!') return pil_image
def ParseFFMPEGDuration(lines): # get duration (in seconds) # Duration: 00:00:02.46, start: 0.033000, bitrate: 1069 kb/s try: # had a vid with 'Duration:' in title, ha ha, so now a regex line = [ l for l in lines if re.search(r'^\s*Duration:', l) is not None ][0] if 'Duration: N/A' in line: return (None, None) if 'start:' in line: m = re.search(r'(start: )-?[0-9]+\.[0-9]*', line) start_offset = float(line[m.start() + 7:m.end()]) else: start_offset = 0 match = re.search("[0-9]+:[0-9][0-9]:[0-9][0-9].[0-9][0-9]", line) hms = [ float(float_string) for float_string in line[match.start():match.end()].split(':') ] duration = 0 if len(hms) == 1: duration = hms[0] elif len(hms) == 2: duration = 60 * hms[0] + hms[1] elif len(hms) == 3: duration = 3600 * hms[0] + 60 * hms[1] + hms[2] if duration == 0: return (None, None) if start_offset > 0.85 * duration: # as an example, Duration: 127:57:31.25, start: 460633.291000 lmao return (None, None) # we'll keep this for now I think if start_offset > 1: start_offset = 0 file_duration = duration + start_offset stream_duration = duration return (file_duration, stream_duration) except: raise HydrusExceptions.DamagedOrUnusualFileException( 'Error reading duration!')