Merge branch 'master' into static-media
[mygpo.git] / mygpo / web / logo.py
blob547c99cb0cac0eaecbec4e4b5bc98b68675da9c8
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 import os.path
19 import io
20 from datetime import datetime
21 from glob import glob
22 import errno
23 import hashlib
24 import struct
26 from PIL import Image, ImageDraw
28 from django.core.urlresolvers import reverse
29 from django.conf import settings
30 from django.http import Http404, HttpResponse, HttpResponseNotFound
31 from django.views.generic.base import View
32 from django.utils.decorators import method_decorator
33 from django.views.decorators.http import last_modified
34 from django.contrib.staticfiles.storage import staticfiles_storage
36 import logging
37 logger = logging.getLogger(__name__)
39 LOGO_DIR = os.path.join(settings.MEDIA_ROOT, 'logo')
42 def _last_modified(request, size, prefix, filename):
44 target = os.path.join(LOGO_DIR, size, prefix, filename)
46 try:
47 return datetime.fromtimestamp(os.path.getmtime(target))
49 except OSError:
50 return None
53 class CoverArt(View):
55 @method_decorator(last_modified(_last_modified))
56 def get(self, request, size, prefix, filename):
58 size = int(size)
60 target = self.get_thumbnail(size, prefix, filename)
61 original = self.get_original(prefix, filename)
63 if os.path.exists(target):
64 return self.send_file(target)
66 if not os.path.exists(original):
67 raise Http404('Cover Art not available' + original)
69 target_dir = self.get_dir(target)
71 try:
72 im = Image.open(original)
73 if im.mode not in ('RGB', 'RGBA'):
74 im = im.convert('RGB')
75 except IOError:
76 raise Http404('Cannot open cover file')
78 try:
79 im.thumbnail((size, size), Image.ANTIALIAS)
80 resized = im
81 except (struct.error, IOError, IndexError) as ex:
82 # raised when trying to read an interlaced PNG;
83 logger.warn('Could not create thumbnail: %s', str(ex))
85 # we use the original instead
86 return self.send_file(original)
88 # If it's a RGBA image, composite it onto a white background for JPEG
89 if resized.mode == 'RGBA':
90 background = Image.new('RGB', resized.size)
91 draw = ImageDraw.Draw(background)
92 draw.rectangle((-1, -1, resized.size[0]+1, resized.size[1]+1),
93 fill=(255, 255, 255))
94 del draw
95 resized = Image.composite(resized, background, resized)
97 sio = io.BytesIO()
99 try:
100 resized.save(sio, 'JPEG', optimize=True, progression=True,
101 quality=80)
102 except IOError as ex:
103 return self.send_file(original)
105 s = sio.getvalue()
107 fp = open(target, 'wb')
108 fp.write(s)
109 fp.close()
111 return self.send_file(target)
113 # the length of the prefix is defined here and in web/urls.py
114 @staticmethod
115 def get_prefix(filename):
116 return filename[:3]
118 @staticmethod
119 def get_thumbnail(size, prefix, filename):
120 return os.path.join(LOGO_DIR, str(size), prefix, filename)
122 @staticmethod
123 def get_existing_thumbnails(prefix, filename):
124 files = glob(os.path.join(LOGO_DIR, '*', prefix, filename))
125 return [f for f in files if 'original' not in f]
127 @staticmethod
128 def get_original(prefix, filename):
129 return os.path.join(LOGO_DIR, 'original', prefix, filename)
131 @staticmethod
132 def get_dir(filename):
133 path = os.path.dirname(filename)
134 try:
135 os.makedirs(path)
137 except OSError as ose:
138 if ose.errno != errno.EEXIST:
139 raise
141 return path
143 def send_file(self, filename):
144 try:
145 f = open(filename, 'rb')
146 except IOError:
147 return HttpResponseNotFound()
149 resp = HttpResponse(content_type='image/jpeg')
150 resp.status_code = 200
151 resp.write(f.read())
152 return resp
155 def get_logo_url(podcast, size):
156 """ Return the logo URL for the podcast """
158 if podcast.logo_url:
159 filename = hashlib.sha1(podcast.logo_url.encode('utf-8')).hexdigest()
160 prefix = CoverArt.get_prefix(filename)
161 return reverse('logo', args=[size, prefix, filename])
163 else:
164 filename = 'podcast-%d.png' % (hash(podcast.title) % 5, )
165 return staticfiles_storage.url('logo/{0}'.format(filename))