Merge branch 'master' into rwd
[wolnelektury.git] / apps / catalogue / models / bookmedia.py
1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 #
5 import json
6 from collections import namedtuple
7 from django.db import models
8 from django.utils.translation import ugettext_lazy as _
9 from django.utils.datastructures import SortedDict
10 import jsonfield
11 from catalogue.fields import OverwritingFileField
12 from catalogue.utils import book_upload_path
13
14
15 class BookMedia(models.Model):
16     """Represents media attached to a book."""
17     FileFormat = namedtuple("FileFormat", "name ext")
18     formats = SortedDict([
19         ('mp3', FileFormat(name='MP3', ext='mp3')),
20         ('ogg', FileFormat(name='Ogg Vorbis', ext='ogg')),
21         ('daisy', FileFormat(name='DAISY', ext='daisy.zip')),
22     ])
23     format_choices = [(k, _('%s file') % t.name)
24             for k, t in formats.items()]
25
26     type        = models.CharField(_('type'), db_index=True, choices=format_choices, max_length="100")
27     name        = models.CharField(_('name'), max_length="100")
28     file        = OverwritingFileField(_('file'), upload_to=book_upload_path())
29     uploaded_at = models.DateTimeField(_('creation date'), auto_now_add=True, editable=False, db_index=True)
30     extra_info  = jsonfield.JSONField(_('extra information'), default={}, editable=False)
31     book = models.ForeignKey('Book', related_name='media')
32     source_sha1 = models.CharField(null=True, blank=True, max_length=40, editable=False)
33
34     def __unicode__(self):
35         return "%s (%s)" % (self.name, self.file.name.split("/")[-1])
36
37     class Meta:
38         ordering            = ('type', 'name')
39         verbose_name        = _('book media')
40         verbose_name_plural = _('book media')
41         app_label = 'catalogue'
42
43     def save(self, *args, **kwargs):
44         from fnpdjango.utils.text.slughifi import slughifi
45         from catalogue.utils import ExistingFile, remove_zip
46
47         try:
48             old = BookMedia.objects.get(pk=self.pk)
49         except BookMedia.DoesNotExist:
50             old = None
51         else:
52             # if name changed, change the file name, too
53             if slughifi(self.name) != slughifi(old.name):
54                 self.file.save(None, ExistingFile(self.file.path), save=False, leave=True)
55
56         super(BookMedia, self).save(*args, **kwargs)
57
58         # remove the zip package for book with modified media
59         if old:
60             remove_zip("%s_%s" % (old.book.slug, old.type))
61         remove_zip("%s_%s" % (self.book.slug, self.type))
62
63         extra_info = self.extra_info
64         if isinstance(extra_info, basestring):
65             # Walkaround for weird jsonfield 'no-decode' optimization.
66             extra_info = json.loads(extra_info)
67         extra_info.update(self.read_meta())
68         self.extra_info = extra_info
69         self.source_sha1 = self.read_source_sha1(self.file.path, self.type)
70         return super(BookMedia, self).save(*args, **kwargs)
71
72     def read_meta(self):
73         """
74             Reads some metadata from the audiobook.
75         """
76         import mutagen
77         from mutagen import id3
78
79         artist_name = director_name = project = funded_by = ''
80         if self.type == 'mp3':
81             try:
82                 audio = id3.ID3(self.file.path)
83                 artist_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE1'))
84                 director_name = ', '.join(', '.join(tag.text) for tag in audio.getall('TPE3'))
85                 project = ", ".join([t.data for t in audio.getall('PRIV') 
86                         if t.owner=='wolnelektury.pl?project'])
87                 funded_by = ", ".join([t.data for t in audio.getall('PRIV') 
88                         if t.owner=='wolnelektury.pl?funded_by'])
89             except:
90                 pass
91         elif self.type == 'ogg':
92             try:
93                 audio = mutagen.File(self.file.path)
94                 artist_name = ', '.join(audio.get('artist', []))
95                 director_name = ', '.join(audio.get('conductor', []))
96                 project = ", ".join(audio.get('project', []))
97                 funded_by = ", ".join(audio.get('funded_by', []))
98             except:
99                 pass
100         else:
101             return {}
102         return {'artist_name': artist_name, 'director_name': director_name,
103                 'project': project, 'funded_by': funded_by}
104
105     @staticmethod
106     def read_source_sha1(filepath, filetype):
107         """
108             Reads source file SHA1 from audiobok metadata.
109         """
110         import mutagen
111         from mutagen import id3
112
113         if filetype == 'mp3':
114             try:
115                 audio = id3.ID3(filepath)
116                 return [t.data for t in audio.getall('PRIV') 
117                         if t.owner=='wolnelektury.pl?flac_sha1'][0]
118             except:
119                 return None
120         elif filetype == 'ogg':
121             try:
122                 audio = mutagen.File(filepath)
123                 return audio.get('flac_sha1', [None])[0] 
124             except:
125                 return None
126         else:
127             return None