def get_image_attributes(file_or_path, close=False): """ Returns a tuple with the animated(boolean) and format(str) of an image. Firstly the file will be accessed directly at a supplied path. Otherwise set `close` to True if `file_or_path` is a file to ensure it is closed. If there is an error when accessing the file or `IMAGEBOARD_FORMATS` is not configured correctly the defaults will be returned (False, None). """ from imageboard.conf import IMAGEBOARD_FORMATS as formats from imageboard.utils import force_close_reader from imageio import get_reader from os.path import exists _animated = False _format = None f = None if hasattr(file_or_path, 'path'): if not exists(file_or_path.path): # No file at path. Should raise an error here but we will return # defaults instead. return _animated, _format file = file_or_path.path close = False elif hasattr(file_or_path, 'read'): file = file_or_path if not exists(file): # No file at path. Should raise an error here but we will return # defaults instead. return _animated, _format file.open() file.seek(0) else: raise AttributeError("No file or path given.") try: f = get_reader(file) try: _format = formats[f.format.name]['code'] _animated = formats[f.format.name]['animated'] except KeyError: # Format type or animated not in formats. See `imageboard.conf` for # information on how to configure the IMAGEBOARD_FORMATS setting # to support formats correctly. raise ValueError # Check if GIF has multiple frames. Extra support is required for other # formats with either single or multiple frames. # Request support here: https://github.com/arcynum/pifti/issues if _animated and _format == 'GIF': try: f.get_data(1) except ValueError: # GIF only has one frame, is not animated _animated = False except ValueError: # No reader or format, fail silently pass except OSError: # Out of memory for a new reader (bug #48) pass finally: # Close the file if close: file.close() # Close the reader force_close_reader(f) return _animated, _format
def to_python(self, data): """ Checks that the file-upload field data contains a valid source file (GIF, JPG, PNG, WEBM, MP4, etc) using imageio plugins. """ f = super(ThumbnailerExtField, self).to_python(data) if f is None: return None # Check filesize limit # Nginx client_max_body_size is 5MB if f.size > 5 * 1024 * 1024: raise ValidationError(self.error_messages['max_size'], code='max_size') from pathlib import PurePath # Check for file extension ext = PurePath(f.name).suffix if ext == '': raise ValidationError(self.error_messages['invalid_extension'], code='invalid_extension') # Get temporary file path or raw bytes if hasattr(data, 'temporary_file_path'): file = data.temporary_file_path() else: if hasattr(data, 'read'): file = BytesIO(data.read()) else: file = BytesIO(data['content']) from PIL import Image from imageio import get_reader try: # Firstly try and recognise BytesIO file = get_reader(file) # Send data to PIL f.image = Image.fromarray(file.get_data(0)) # Close the reader force_close_reader(file) except ValueError: # Create a local file with NamedTemporaryFile(suffix=ext) as n: for chunk in data.chunks(): n.write(chunk) try: # Try and read from temporary file file = get_reader(n.name) # Send data to PIL f.image = Image.fromarray(file.get_data(0)) except OSError: # FFmpeg cannot read video meta # May be out of memory for a new reader (bug #48) params = { 'file': f.name } raise ValidationError(self.error_messages['corrupt_file'], code='corrupt_file', params=params) except Exception: # imageio doesn't recognize it as an image. params = { 'types': self.types } raise ValidationError(self.error_messages['invalid_file'], code='invalid_file', params=params) finally: # Close Named Tempoary File n.close() # Make sure any open reader is closed force_close_reader(file) # Check if this format is enabled # See ``imageboard.settings`` for more information if file.format.name in formats.keys(): # MIME type is not supported with imageio # Thus if the content type is not detected, it will remain as None f.content_type = Image.MIME.get(f.image.format) if hasattr(f, 'seek') and callable(f.seek): f.seek(0) return f else: params = { 'types': self.types } raise ValidationError(self.error_messages['unsupported'], code='unsupported', params=params)