3 | | def get_thumbnail_url(photo_url, width): |
4 | | bits = photo_url.split('/') |
5 | | bits[-1] = re.sub(r'(?i)\.(gif|jpg)$', '_t%s.\\1' % width, bits[-1]) |
6 | | return '/'.join(bits) |
| 11 | def _get_thumbnail_path(path, width=None, height=None): |
| 12 | """ create thumbnail path from path and required width and/or height. |
| 13 | |
| 14 | thumbnail file name is constructed like this: |
| 15 | <basename>_t_[w<width>][_h<height>].<extension> |
| 16 | """ |
| 17 | |
| 18 | # one of width/height is required |
| 19 | assert (width is not None) or (height is not None) |
| 20 | |
| 21 | basedir = os.path.dirname(path) + '/' |
| 22 | base, ext = os.path.splitext(os.path.basename(path)) |
| 23 | |
| 24 | # make thumbnail filename |
| 25 | th_name = base + '_t' |
| 26 | if (width is not None) and (height is not None): |
| 27 | th_name += '_w%d_h%d' % (width, height) |
| 28 | elif width is not None: |
| 29 | th_name += '_w%d' % width |
| 30 | elif height is not None: |
| 31 | th_name += '_h%d' % height |
| 32 | th_name += ext |
| 33 | |
| 34 | return urlparse.urljoin(basedir, th_name) |
| 35 | # |
| 36 | |
| 37 | def get_path_from_url(url, root=MEDIA_ROOT, url_root=MEDIA_URL): |
| 38 | """ make filesystem path from url """ |
| 39 | |
| 40 | if url.startswith(url_root): |
| 41 | url = url[len(url_root):] # strip media root url |
| 42 | |
| 43 | return os.path.normpath(os.path.join(root, url)) |
| 44 | # |
| 45 | |
| 46 | def get_url_from_path(path, root=MEDIA_ROOT, url_root=MEDIA_URL): |
| 47 | """ make url from filesystem path """ |
| 48 | |
| 49 | if path.startswith(root): |
| 50 | path = path[len(root):] # strip media root |
| 51 | |
| 52 | return urlparse.urljoin(root, path.replace('\\', '/')) |
| 53 | # |
| 54 | |
| 55 | def has_thumbnail(photo_url, width=None, height=None, root=MEDIA_ROOT, url_root=MEDIA_URL): |
| 56 | # one of width/height is required |
| 57 | assert (width is not None) or (height is not None) |
| 58 | |
| 59 | return os.path.isfile(get_path_from_url(_get_thumbnail_path(photo_url, width, height), root, url_root)) |
| 60 | # |
| 61 | |
| 62 | def make_thumbnail(photo_url, width=None, height=None, root=MEDIA_ROOT, url_root=MEDIA_URL): |
| 63 | """ create thumbnail """ |
| 64 | |
| 65 | # one of width/height is required |
| 66 | assert (width is not None) or (height is not None) |
| 67 | |
| 68 | if not HAS_PIL: return None # no PIL - no thumbnail |
| 69 | |
| 70 | th_url = _get_thumbnail_path(photo_url, width, height) |
| 71 | th_path = get_path_from_url(th_url, root, url_root) |
| 72 | photo_path = get_path_from_url(photo_url, root, url_root) |
| 73 | |
| 74 | if has_thumbnail(photo_url, width, height, root, url_root): |
| 75 | # thumbnail already exists |
| 76 | if not (os.path.getmtime(photo_path) > os.path.getmtime(th_path)): |
| 77 | # if photo mtime is newer than thumbnail recreate thumbnail |
| 78 | return th_url |
| 79 | |
| 80 | # make thumbnail |
| 81 | |
| 82 | # get original image size |
| 83 | orig_w, orig_h = get_image_size(photo_url, root, url_root) |
| 84 | |
| 85 | if (orig_w == width) and (orig_h == height): |
| 86 | # same dimensions |
| 87 | return None |
| 88 | |
| 89 | img = Image.open(photo_path).copy() |
| 90 | # make proper size |
| 91 | if (width is not None) and (height is not None): |
| 92 | size = (width, height) |
| 93 | elif width is not None: |
| 94 | size = (width, orig_h) |
| 95 | elif height is not None: |
| 96 | size = (orig_w, height) |
| 97 | |
| 98 | img.thumbnail(size, Image.ANTIALIAS) |
| 99 | img.save(th_path) |
| 100 | |
| 101 | return th_url |
| 102 | # |
| 103 | |
| 104 | def get_thumbnail_url(photo_url, width=None, height=None, root=MEDIA_ROOT, url_root=MEDIA_URL): |
| 105 | """ return thumbnail URL for requested photo_url and required width and/or height |
| 106 | |
| 107 | if thumbnail file do not exists returns original URL |
| 108 | """ |
| 109 | |
| 110 | # one of width/height is required |
| 111 | assert (width is not None) or (height is not None) |
| 112 | |
| 113 | if has_thumbnail(photo_url, width, height, root, url_root): |
| 114 | return _get_thumbnail_path(photo_url, width, height) |
| 115 | else: |
| 116 | return photo_url |
| 117 | |
| 118 | def _no_pil_image_size(fname): |
| 119 | """ |
| 120 | Determine the image type of FNAME and return its size. |
| 121 | ripped from draco |
| 122 | |
| 123 | returns tuple (width, height) or None |
| 124 | """ |
| 125 | |
| 126 | try: |
| 127 | filehandle = file(fname, 'rb') |
| 128 | except IOError: |
| 129 | return None |
| 130 | |
| 131 | head = filehandle.read(24) |
| 132 | if len(head) != 24: |
| 133 | return |
| 134 | if head[:4] == '\x89PNG': |
| 135 | # PNG |
| 136 | check = struct.unpack('>i', head[4:8])[0] |
| 137 | if check != 0x0d0a1a0a: |
| 138 | return |
| 139 | width, height = struct.unpack('>ii', head[16:24]) |
| 140 | elif head[:6] in ('GIF87a', 'GIF89a'): |
| 141 | # GIF |
| 142 | width, height = struct.unpack('<HH', head[6:10]) |
| 143 | elif head[:4] == '\xff\xd8\xff\xe0' and head[6:10] == 'JFIF': |
| 144 | # JPEG |
| 145 | try: |
| 146 | filehandle.seek(0) # Read 0xff next |
| 147 | size = 2 |
| 148 | filetype = 0 |
| 149 | while not 0xc0 <= filetype <= 0xcf: |
| 150 | filehandle.seek(size, 1) |
| 151 | byte = filehandle.read(1) |
| 152 | while ord(byte) == 0xff: |
| 153 | byte = filehandle.read(1) |
| 154 | filetype = ord(byte) |
| 155 | size = struct.unpack('>H', filehandle.read(2))[0] - 2 |
| 156 | # We are at a SOFn block |
| 157 | filehandle.seek(1, 1) # Skip `precision' byte. |
| 158 | height, width = struct.unpack('>HH', filehandle.read(4)) |
| 159 | except: |
| 160 | raise |
| 161 | return |
| 162 | else: |
| 163 | return |
| 164 | return width, height |
| 165 | # |
| 166 | |
| 167 | def get_image_size(photo_url, root=MEDIA_ROOT, url_root=MEDIA_URL): |
| 168 | """ returns image size, use PIL if present or _no_pil_image_size if no PIL is found. |
| 169 | |
| 170 | TODO: add image size caching. |
| 171 | """ |
| 172 | |
| 173 | path = get_path_from_url(photo_url, root, url_root) |
| 174 | |
| 175 | if HAS_PIL: |
| 176 | size = Image.open(path).size |
| 177 | else: |
| 178 | size = _no_pil_image_size(path) |
| 179 | |
| 180 | return size |
| 181 | # |