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!')
예제 #2
0
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!' )
예제 #3
0
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!')
예제 #4
0
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!')
예제 #5
0
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!' )
예제 #6
0
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 )
예제 #7
0
    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
예제 #9
0
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!')