示例#1
0
 def test_auto_crop_filename_generator_raises_on_bad_quality(self):
     """ Auto crop filename generator raises exception on bad quality """
     params = dict(id=utils.generate_id('jpg'),
                   size='100x200',
                   factor='fit',
                   output_format='jpg',
                   upscale=True,
                   quality="CRAP")
     pb = PathBuilder('12345')
     with assert_raises(x.InvalidArgumentException):
         pb.get_auto_crop_filename(**params)
示例#2
0
    def test_validate_signature(self):
        """ Validating signature contained within filename  """
        auto_id = utils.generate_id('test.jpg')
        auto_params = dict(id=auto_id,
                           size='100x200',
                           factor='fill',
                           output_format='jpg',
                           upscale=True,
                           quality=80)
        manual_id = utils.generate_id('test.jpg')
        manual_params = dict(id=manual_id,
                             sample_size='200x400',
                             target_size='100x200',
                             output_format='jpg',
                             upscale=True,
                             quality=80)

        pb = PathBuilder('12345')
        auto = pb.get_auto_crop_filename(**auto_params)
        manual = pb.get_manual_crop_filename(**manual_params)
        bad = manual.split('-')
        bad[4] = 'ZZZ' + bad[4]
        bad = '-'.join(bad)
        self.assertTrue(pb.validate_signature(auto_id, auto))
        self.assertTrue(pb.validate_signature(manual_id, manual))
        self.assertFalse(pb.validate_signature(manual_id, bad))
示例#3
0
 def test_missing_autocrop_format_defaults_to_original_format(self):
     """ Autocrop defaults to original format when format not specified"""
     params = dict(id=utils.generate_id('zz.gif'),
                   size='100x200',
                   factor='fill',
                   upscale=True,
                   quality=80)
     pb = PathBuilder('12345')
     filename = pb.get_auto_crop_filename(**params)
     self.assertTrue(filename.endswith('.gif'))
示例#4
0
 def test_create_auto_crop_filename(self):
     """ Creating filename for auto crop"""
     params = dict(id=utils.generate_id('jpg'),
                   size='100x200',
                   factor='fill',
                   output_format='jpg',
                   upscale=True,
                   quality=80)
     pb = PathBuilder('12345')
     filename = pb.get_auto_crop_filename(**params)
     start = '100x200-fill-80-upscale'
     self.assertTrue(filename.startswith(start))
示例#5
0
 def test_resize_filename_parser_raises_on_bad_signature(self):
     """ Resize filename parser raises on bad signature"""
     id = utils.generate_id('test.jpg')
     auto_params = dict(id=id + 'SOME-CRAP',
                        size='100x200',
                        factor='fill',
                        output_format='jpg',
                        upscale=True,
                        quality=80)
     pb = PathBuilder('12345')
     filename = pb.get_auto_crop_filename(**auto_params)
     with assert_raises(x.InvalidArgumentException):
         pb.filename_to_resize_params(id, filename)
示例#6
0
 def test_parse_auto_resize_filename(self):
     """ Parse auto resize filename into a set of parameters """
     id = utils.generate_id('test.jpg')
     params = dict(id=id,
                   size='100x200',
                   factor='fill',
                   output_format='jpg',
                   upscale=True,
                   quality=80)
     pb = PathBuilder('12345')
     filename = pb.get_auto_crop_filename(**params)
     result = pb.filename_to_resize_params(id, filename)
     self.assertEquals(id, result['id'])
     self.assertEquals(filename, result['filename'])
     self.assertEquals(params['size'], result['target_size'])
     self.assertEquals(params['output_format'], result['output_format'])
     self.assertEquals(params['quality'], result['quality'])
     self.assertEquals(params['factor'], result['factor'])
     self.assertEquals(params['upscale'], result['upscale'])
示例#7
0
class Storage:
    def __init__(self, backend, secret_key, local_temp):
        """
        Init
        :param backend:, shiftmedia.backend.Backend instance
        :param secret_key: string, random salt
        :param local_temp: string, path to local temp directory
        """
        self.backend = backend
        self.paths = PathBuilder(secret_key)
        self._tmp_path = local_temp

    @property
    def tmp(self):
        """
        Get temp path
        Returns path to local temp and creates one if necessary
        """
        if not os.path.exists(self._tmp_path):
            os.makedirs(self._tmp_path)
        return self._tmp_path

    def put(self, src, delete_local=True, fix_orientation=False):
        """
        Put local file to storage
        Generates a uuid for the file, tells backend to accept
        it by that id and removes original on success.
        """
        if not os.path.exists(src):
            msg = 'Unable to find local file [{}]'
            raise x.LocalFileNotFound(msg.format(src))

        path = Path(src)
        extension = ''.join(path.suffixes)[1:]
        name = path.name.replace('.' + extension, '')
        extension = utils.normalize_extension(extension)
        filename = name + '.' + extension
        id = utils.generate_id(filename)

        # fix image orientation before accepting
        if fix_orientation:
            Resizer.fix_orientation_and_save(src)

        self.backend.put_variant(src, id, filename.lower())
        if delete_local:
            os.remove(src)
        return id

    def delete(self, id):
        """
        Delete
        Removes file and all its artifacts from storage by id
        """
        return self.backend.delete(id)

    def get_original_url(self, id):
        """
        Get original URL
        Combines backend base url, path to object id and original filename.
        :return: string - full object url
        """
        base = self.backend.get_url().rstrip('/')
        parts = self.backend.id_to_path(id)
        filename = parts[5]
        path = '/'.join(parts)
        return base + '/' + path + '/' + filename

    def get_auto_crop_url(self, *args, **kwargs):
        """
        Get auto crop URL
        Combines backend base url, path to object id and generated filename.
        :param args: positional args to be passed to filename generator
        :param kwargs: keyword args to be passed to filename generator
        :return: string - full object url
        """
        id = kwargs['id'] if 'id' in kwargs else args[0]
        base = self.backend.get_url().rstrip('/')
        parts = self.backend.id_to_path(id)
        path = '/'.join(parts)
        filename = self.paths.get_auto_crop_filename(*args, **kwargs)
        return base + '/' + path + '/' + filename

    def get_manual_crop_url(self, *args, **kwargs):
        """
        Get manual crop URL
        Combines backend base url, path to object id and generated filename.
        :param args: positional args to be passed to filename generator
        :param kwargs: keyword args to be passed to filename generator
        :return: string - full object url
        """
        id = kwargs['id'] if 'id' in kwargs else args[0]
        base = self.backend.get_url().rstrip('/')
        parts = self.backend.id_to_path(id)
        path = '/'.join(parts)
        filename = self.paths.get_manual_crop_filename(*args, **kwargs)
        return base + '/' + path + '/' + filename

    def create_resize(self, url):
        """
        Create resize
        Accepts storage URL of a resize, parses and validates it and then
        creates the resize to be put back to storage.
        :param url: string - url of resize to be created
        :return: string - same url on success
        """
        id, filename = self.backend.parse_url(url)
        params = self.paths.filename_to_resize_params(id, filename)
        mode = params['resize_mode']
        modes = ['auto', 'manual']
        if mode not in modes:
            err = 'Resize mode [' + mode + '] is not yet implemented.'
            raise x.NotImplementedError(err)

        local_original = self.backend.retrieve_original(id, self._tmp_path)
        local_resize = os.path.join(self._tmp_path, id, params['filename'])
        factor = Resizer.RESIZE_TO_FIT
        if params['factor'] == 'fill':
            factor = Resizer.RESIZE_TO_FILL

        resize = Resizer.auto_crop(
            src=local_original,
            dst=local_resize,
            size=params['target_size'],
            mode= factor,
            upscale=params['upscale'],
            format=params['output_format'],
            quality=params['quality']
        )

        try:
            self.backend.put_variant(resize, id, filename, force=True)
        except x.FileExists:
            pass

        os.remove(local_original)
        os.remove(resize)
        tmp_dir = os.path.join(self._tmp_path, id)
        if not os.listdir(tmp_dir):
            os.rmdir(tmp_dir)
        return url