From 64e7a534a7a7d5de5cdbd07dfd8ee1203137ec12 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Thu, 29 Sep 2011 13:05:34 +0000 Subject: [PATCH] list icons --- pandora/item/models.py | 11 ++++++- pandora/itemlist/managers.py | 4 +-- pandora/itemlist/models.py | 61 ++++++++++++++++++++++++++++++------ pandora/itemlist/views.py | 39 ++++++++++++++++++++--- pandora/scripts/list_icon | 14 ++++----- pandora/urls.py | 1 + 6 files changed, 106 insertions(+), 24 deletions(-) diff --git a/pandora/item/models.py b/pandora/item/models.py index 6171a0d99..9eefc90d0 100644 --- a/pandora/item/models.py +++ b/pandora/item/models.py @@ -444,6 +444,7 @@ class Item(models.Model): if not keys or 'posterRatio' in keys: i['posterRatio'] = self.poster_width / self.poster_height + streams = self.streams() i['durations'] = [s.duration for s in streams] i['duration'] = sum(i['durations']) @@ -454,8 +455,16 @@ class Item(models.Model): #only needed by admins if keys and 'posters' in keys: i['posters'] = self.get_posters() + + frames = self.get_frames() if keys and 'frames' in keys: - i['frames'] = self.get_frames() + i['frames'] = frames + + if frames: + i['posterFrame'] = filter(lambda f: f['selected'], frames)[0]['position'] + elif self.poster_frame != -1.0: + i['posterFrame'] = self.poster_frame + if keys: info = {} for key in keys: diff --git a/pandora/itemlist/managers.py b/pandora/itemlist/managers.py index 6eb6f4a18..c31fc4c96 100644 --- a/pandora/itemlist/managers.py +++ b/pandora/itemlist/managers.py @@ -10,11 +10,11 @@ import models def parseCondition(condition, user): ''' ''' - print condition, user k = condition.get('key', 'name') k = { 'user': 'user__username', 'position': 'position__position', + 'posterFrames': 'poster_frames', }.get(k, k) if not k: k = 'name' @@ -132,4 +132,4 @@ class ListManager(Manager): qs = qs.filter(Q(status='public') | Q(status='featured')) else: qs = qs.filter(Q(status='public') | Q(status='featured') | Q(user=user)) - return qs.distinct() + return qs diff --git a/pandora/itemlist/models.py b/pandora/itemlist/models.py index 90e33e17c..fa48bb903 100644 --- a/pandora/itemlist/models.py +++ b/pandora/itemlist/models.py @@ -3,13 +3,16 @@ from __future__ import division, with_statement import os import subprocess +from glob import glob from django.db import models from django.contrib.auth.models import User +from django.conf import settings +import ox -from ox.django.fields import DictField - +from ox.django.fields import DictField, TupleField +from archive import extract import managers @@ -31,10 +34,13 @@ class List(models.Model): icon = models.ImageField(default=None, blank=True, upload_to=lambda i, x: i.path("icon.jpg")) + poster_frames = TupleField(default=[], editable=False) + #is through table still required? items = models.ManyToManyField('item.Item', related_name='lists', through='ListItem') + items_sum = models.IntegerField(default=0) subscribed_users = models.ManyToManyField(User, related_name='subscribed_lists') objects = managers.ListManager() @@ -44,9 +50,10 @@ class List(models.Model): self.type = 'static' else: self.type = 'smart' + self.items_sum = self.get_items_sum(self.user) super(List, self).save(*args, **kwargs) - def get_number_of_items(self, user=None): + def get_items_sum(self, user=None): if self.query.get('static', False): return self.items.count() else: @@ -78,11 +85,11 @@ class List(models.Model): def json(self, keys=None, user=None): if not keys: - keys=['id', 'name', 'user', 'type', 'query', 'status', 'subscribed'] + keys=['id', 'name', 'user', 'type', 'query', 'status', 'subscribed', 'posterFrames'] response = {} for key in keys: if key == 'items': - response[key] = self.get_number_of_items(user) + response[key] = self.get_items_sum(user) elif key == 'id': response[key] = self.get_id() elif key == 'user': @@ -90,30 +97,64 @@ class List(models.Model): elif key == 'query': if not self.query.get('static', False): response[key] = self.query + elif key == 'subscribers': + response[key] = self.subscribed_users.all().count() elif key == 'subscribed': if user and not user.is_anonymous(): response[key] = self.subscribed_users.filter(id=user.id).exists() else: - response[key] = getattr(self, key) + response[key] = getattr(self, { + 'posterFrames': 'poster_frames' + }.get(key, key)) return response def path(self, name=''): - h = self.get_id() + h = "%06d" % self.id return os.path.join('lists', h[:2], h[2:4], h[4:6], h[6:], name) - def make_icon(self): + def update_icon(self): frames = [] - self.icon.name = self.path('icon.png') + for i in self.poster_frames: + qs = self.items.filter(itemId=i['item']) + if qs.count() > 0: + frame = qs[0].frame(i['position']) + if frame: + frames.append(frame) + self.icon.name = self.path('icon.jpg') icon = self.icon.path if frames: + while len(frames) < 4: + frames += frames + folder = os.path.dirname(icon) + ox.makedirs(folder) + for f in glob("%s/icon*.jpg" % folder): + os.unlink(f) cmd = [ - 'scripts/list_icon', + settings.LIST_ICON, '-f', ','.join(frames), '-o', icon ] p = subprocess.Popen(cmd) p.wait() + self.save() + def get_icon(self, size=16): + path = self.path('icon%d.jpg' % size) + path = os.path.join(settings.MEDIA_ROOT, path) + if not os.path.exists(path): + folder = os.path.dirname(path) + ox.makedirs(folder) + if self.icon: + source = self.icon.path + max_size = min(self.icon.width, self.icon.height) + else: + source = os.path.join(settings.STATIC_ROOT, 'png/list256.png') + max_size = 256 + if size < max_size: + extract.resize_image(source, path, size=size) + else: + path = source + return path class ListItem(models.Model): created = models.DateTimeField(auto_now_add=True) diff --git a/pandora/itemlist/views.py b/pandora/itemlist/views.py index 563600f70..18641948c 100644 --- a/pandora/itemlist/views.py +++ b/pandora/itemlist/views.py @@ -2,10 +2,12 @@ # vi:si:et:sw=4:sts=4:ts=4 from __future__ import division -from django.db.models import Max +from django.db.models import Max, Sum +from django.http import HttpResponseForbidden, Http404 from ox.utils import json from ox.django.decorators import login_required_json from ox.django.shortcuts import render_to_json_response, get_object_or_404_json, json_response +from ox.django.http import HttpFileResponse import models from api.actions import actions @@ -25,10 +27,13 @@ def _order_query(qs, sort): if operator != '-': operator = '' key = { - 'subscribed': 'subscribed_users' + 'subscribed': 'subscribed_users', + 'items': 'items_sum' }.get(e['key'], e['key']) order = '%s%s' % (operator, key) order_by.append(order) + if key == 'subscribers': + qs = qs.annotate(subscribers=Sum('subscribed_users')) if order_by: qs = qs.order_by(*order_by) return qs @@ -181,6 +186,13 @@ def addList(request): param data { name: value, } + possible keys to create list: + name + description + type + query + items + return { status: {'code': int, 'text': string}, data: { @@ -197,7 +209,7 @@ def addList(request): while not created: list, created = models.List.objects.get_or_create(name=name, user=request.user) num += 1 - name = data['name'] + ' (%d)' % num + name = data['name'] + ' [%d]' % num for key in data: if key == 'query' and not data['query']: @@ -223,6 +235,10 @@ def addList(request): list.description = data['description'] list.save() + if 'items' in data: + for item in Item.objects.filter(itemId__in=data['items']): + list.add(item) + if list.status == 'featured': pos, created = models.Position.objects.get_or_create(list=list, user=request.user, section='featured') @@ -246,9 +262,11 @@ def editList(request): id: listId, key: value, } - keys: name, status, query, position + keys: name, status, query, position, posterFrames if you change status you have to provide position of list + posterFrames: + array with objects that have item/position return { status: {'code': int, 'text': string}, data: { @@ -330,6 +348,9 @@ def editList(request): if list.status == 'private': pos.section = 'personal' pos.save() + if 'posterFrames' in data: + list.poster_frames = tuple(data['posterFrames']) + list.update_icon() list.save() response['data'] = list.json(user=request.user) else: @@ -462,3 +483,13 @@ def sortLists(request): response = json_response() return render_to_json_response(response) actions.register(sortLists, cache=False) + + +def icon(request, id, size=16): + if not size: + size = 16 + list = get_list_or_404_json(id) + icon = list.get_icon(int(size)) + if icon: + return HttpFileResponse(icon, content_type='image/jpeg') + raise Http404 diff --git a/pandora/scripts/list_icon b/pandora/scripts/list_icon index e763cd714..b87f8e2c7 100755 --- a/pandora/scripts/list_icon +++ b/pandora/scripts/list_icon @@ -16,15 +16,14 @@ from optparse import OptionParser from ox.image import drawText, wrapText import sys - static_root = os.path.join(os.path.dirname(__file__), '..', '..', 'static') def render_list_icon(frames, icon): icon_width = 256 icon_height = 256 icon_image = Image.new('RGBA', (icon_width, icon_height)) - frame_height = icon_height / 4 - frame_ratio = 4 / 3 + frame_height = int(icon_height / 2) + frame_ratio = 1 frame_width = int(round(frame_height * frame_ratio)) for i, frame in enumerate(frames): frame_image = Image.open(frame) @@ -38,8 +37,9 @@ def render_list_icon(frames, icon): frame_image = frame_image.resize((frame_width_, int(frame_width_ / frame_image_ratio)), Image.ANTIALIAS) top = int((frame_image.size[1] - frame_height) / 2) frame_image = frame_image.crop((0, top, frame_width_, top + frame_height)) - icon_image.paste(frame_image, (i % 3 * frame_width + (1 if i % 2 == 2 else 0), int(i / 3) * frame_height)) - mask_image = Image.open(ox.path.join(static_root, 'png', 'icon.mask.png')) + icon_image.paste(frame_image, (i % 2 * frame_width + (1 if i % 2 == 2 else 0), + int(i / 2) * frame_height)) + mask_image = Image.open(os.path.join(static_root, 'png', 'iconMask.png')) mask_image = mask_image.resize((icon_width, icon_height)) icon_image.putalpha(mask_image) icon_image.save(icon) @@ -47,7 +47,7 @@ def render_list_icon(frames, icon): def main(): parser = OptionParser() parser.add_option('-f', '--frames', dest='frames', help='Poster frames (image files to be read)', default='') - parser.add_option('-i', '--icon', dest='icon', help='Icon (image file to be written)') + parser.add_option('-o', '--icon', dest='icon', help='Icon (image file to be written)') (options, args) = parser.parse_args() if options.icon == None: parser.print_help() @@ -55,7 +55,7 @@ def main(): frames = options.frames.replace(', ', ',').split(',') - render_list_icon(frames, opt.icon) + render_list_icon(frames, options.icon) if __name__ == "__main__": main() diff --git a/pandora/urls.py b/pandora/urls.py index 4b6c3457a..f94b87534 100644 --- a/pandora/urls.py +++ b/pandora/urls.py @@ -28,6 +28,7 @@ urlpatterns = patterns('', (r'^file/(?P.*)$', 'archive.views.lookup_file'), (r'^api/$', include('api.urls')), (r'', include('item.urls')), + (r'^list/(?P.*?)/icon(?P\d*).jpg$', 'itemlist.views.icon'), (r'^robots.txt$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'robots.txt'), 'content_type': 'text/plain'}), (r'^favicon.ico$', serve_static_file, {'location': os.path.join(settings.STATIC_ROOT, 'png/icon.16.png'), 'content_type': 'image/x-icon'}), )