source: telemeta/models/media.py @ 6229b12

cremcrem2crem3devdev2diademsdj1.6feature/breadcrumbsfeature/ts-0.5feature/ts-0.5.4feature/writecacheformagenericinstru_searchlamlam2mapsv3mergenlivemultiproductionrelease/1.4.4sabiodsearchsecurityserversocialstoragetelecastertestvideo
Last change on this file since 6229b12 was 6229b12, checked in by yomguy <yomguy@…>, 3 years ago

fix computed duration with no .

  • Property mode set to 100644
File size: 24.6 KB
Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2007-2010 Samalyse SARL
3# Copyright (C) 2010-2011 Parisson SARL
4
5# This software is a computer program whose purpose is to backup, analyse,
6# transcode and stream any audio content with its metadata over a web frontend.
7
8# This software is governed by the CeCILL  license under French law and
9# abiding by the rules of distribution of free software.  You can  use,
10# modify and/ or redistribute the software under the terms of the CeCILL
11# license as circulated by CEA, CNRS and INRIA at the following URL
12# "http://www.cecill.info".
13
14# As a counterpart to the access to the source code and  rights to copy,
15# modify and redistribute granted by the license, users are provided only
16# with a limited warranty  and the software's author,  the holder of the
17# economic rights,  and the successive licensors  have only  limited
18# liability.
19
20# In this respect, the user's attention is drawn to the risks associated
21# with loading,  using,  modifying and/or developing or reproducing the
22# software by the user in light of its specific status of free software,
23# that may mean  that it is complicated to manipulate,  and  that  also
24# therefore means  that it is reserved for developers  and  experienced
25# professionals having in-depth computer knowledge. Users are therefore
26# encouraged to load and test the software's suitability as regards their
27# requirements in conditions enabling the security of their systems and/or
28# data to be ensured and,  more generally, to use and operate it in the
29# same conditions as regards security.
30
31# The fact that you are presently reading this means that you have had
32# knowledge of the CeCILL license and that you accept its terms.
33#
34# Authors: Olivier Guilyardi <olivier@samalyse.com>
35#          David LIPSZYC <davidlipszyc@gmail.com>
36#          Guillaume Pellerin <yomguy@parisson.com>
37
38from django.contrib.auth.models import User
39from django.utils.translation import ugettext_lazy as _
40from django.core.exceptions import ValidationError
41from telemeta.models.core import *
42from telemeta.models.enum import ContextKeyword
43from telemeta.util.unaccent import unaccent_icmp
44import re
45import mimetypes
46from telemeta.models.location import LocationRelation, Location
47from telemeta.models.system import Revision
48from telemeta.models.query import *
49from telemeta.models.instrument import *
50from telemeta.models.enum import *
51from django.db.models.fields import URLField
52
53       
54collection_published_code_regex   = '[A-Za-z0-9._-]*'
55collection_unpublished_code_regex = '[A-Za-z0-9._-]*'
56collection_code_regex             = '(?:%s|%s)' % (collection_published_code_regex, 
57                                                    collection_unpublished_code_regex)
58
59item_published_code_regex    = '[A-Za-z0-9._-]*'
60item_unpublished_code_regex  = '[A-Za-z0-9._-]*'
61item_code_regex              = '(?:%s|%s)' % (item_published_code_regex, item_unpublished_code_regex)
62
63PUBLIC_ACCESS_CHOICES = (('none', 'none'), ('metadata', 'metadata'), ('full', 'full'))
64
65class MediaResource(ModelCore):
66    "Base class of all media objects"
67
68    def public_access_label(self):
69        if self.public_access == 'metadata':
70            return _('Metadata only')
71        elif self.public_access == 'full':
72            return _('Sound and metadata')
73
74        return _('Private data')
75    public_access_label.verbose_name = _('public access')
76
77    def set_revision(self, user):
78        "Save a media object and add a revision"
79        Revision.touch(self, user)   
80
81    def get_revision(self):
82        return Revision.objects.filter(element_type=self.element_type, element_id=self.id).order_by('-time')[0]
83
84    class Meta:
85        abstract = True
86
87class MediaRelated(MediaResource):
88    "Related media"
89   
90    element_type = 'media'
91   
92    title           = CharField(_('title'))
93    date            = DateTimeField(_('date'), auto_now=True)
94    description     = TextField(_('description'))
95    mime_type       = CharField(_('mime_type'))
96    url             = CharField(_('url'), max_length=500)
97    credits         = CharField(_('credits'))
98    file            = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename")
99   
100    def is_image(self):
101        is_url_image = False
102        if self.url:
103            url_types = ['.png', '.jpg', '.gif', '.jpeg']
104            for type in url_types:
105                if type in self.url:
106                    is_url_image = True
107        return 'image' in self.mime_type or is_url_image
108       
109    def save(self, force_insert=False, force_update=False):
110        super(MediaRelated, self).save(force_insert, force_update)
111       
112    def set_mime_type(self):
113        if self.file:
114            self.mime_type = mimetypes.guess_type(self.file.path)[0]
115   
116    def __unicode__(self):
117        if self.title and not re.match('^ *N *$', self.title):
118            title = self.title
119        else:
120            title = unicode(self.item)
121        return title
122   
123    class Meta:
124        abstract = True
125
126
127class MediaCorpus(MediaResource):
128    "Describe a corpus of collections"
129   
130    element_type = 'corpus'
131
132    # General informations
133    reference             = CharField(_('reference'), unique=True, null=True)
134    title                 = CharField(_('title'), required=True)
135    description           = CharField(_('description'))
136    code                  = CharField(_('code'), unique=True, required=True)
137    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, 
138                                      max_length=16, default="metadata")
139                                     
140    def __unicode__(self):
141        return self.code
142
143    @property
144    def public_id(self):
145        return self.code
146
147    def save(self, force_insert=False, force_update=False, user=None, code=None):
148        super(MediaCorpus, self).save(force_insert, force_update)
149       
150    class Meta(MetaCore):
151        db_table = 'media_corpus'
152        ordering = ['code']
153
154
155class MediaCorpusCollectionRelation(ModelCore):
156    "Relations between Corpus and Collections"
157   
158    collection        = ForeignKey('MediaCollection', related_name="parent_relation", 
159                                   verbose_name=_('collection'))
160    corpus            = ForeignKey('MediaCorpus', related_name="child_relation", 
161                                   verbose_name=_('corpus'))
162
163    class Meta(MetaCore):
164        db_table = 'media_corpus_collection_relations'
165        unique_together = (('collection', 'corpus'),)
166   
167    def __unicode__(self):
168        sep = ' > '
169        return self.corpus.code + sep + self.collection.code
170
171                                                       
172class MediaCollection(MediaResource):
173    "Describe a collection of items"
174   
175    element_type = 'collection'
176
177    def is_valid_collection_code(value):
178        "Check if the collection code is well formed"
179        regex = '^' + collection_code_regex + '$'
180        if not re.match(regex, value):
181            raise ValidationError(u'%s is not a valid collection code' % value)
182   
183    # General informations
184    reference             = CharField(_('reference'), unique=True, null=True)
185    title                 = CharField(_('title'), required=True)
186    alt_title             = CharField(_('original title / translation'))
187    creator               = CharField(_('depositor / contributor'))
188    recording_context     = WeakForeignKey('RecordingContext', related_name="collections", 
189                                           verbose_name=_('recording context'))
190    recorded_from_year    = IntegerField(_('recording year (from)'))
191    recorded_to_year      = IntegerField(_('recording year (until)'))
192    year_published        = IntegerField(_('year published'))
193   
194    # Geographic and cultural informations
195    ## See "countries" and "ethnic_groups" methods below
196
197    # Legal notices
198    collector             = CharField(_('recordist'))
199    publisher             = WeakForeignKey('Publisher', related_name="collections", 
200                                           verbose_name=_('publisher / status'))     
201    publisher_collection  = WeakForeignKey('PublisherCollection', related_name="collections", 
202                                            verbose_name=_('publisher collection'))
203    publisher_serial      = CharField(_('publisher serial number'))
204    booklet_author        = CharField(_('author of published notice'))
205    external_references   = TextField(_('bibliographic references'))
206    doctype_code          = IntegerField(_('document type'))
207    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, 
208                                      max_length=16, default="metadata")
209    legal_rights          = WeakForeignKey('LegalRight', related_name="collections", 
210                                           verbose_name=_('legal rights'))
211   
212    # Archiving data
213    acquisition_mode      = WeakForeignKey('AcquisitionMode', related_name="collections", 
214                                            verbose_name=_('mode of acquisition'))
215    cnrs_contributor      = CharField(_('CNRS depositor'))
216    metadata_author       = WeakForeignKey('MetadataAuthor', related_name="collections", 
217                                           verbose_name=_('record author'))
218    booklet_description   = TextField(_('related documentation'))
219    publishing_status     = WeakForeignKey('PublishingStatus', related_name="collections", 
220                                           verbose_name=_('secondary edition'))
221    alt_ids               = CharField(_('copies'))
222    comment               = TextField(_('comment'))
223    metadata_writer       = WeakForeignKey('MetadataWriter', related_name="collections", 
224                                           verbose_name=_('record writer'))
225    travail               = CharField(_('archiver notes'))
226    items_done            = CharField(_('items finished'))
227    collector_is_creator  = BooleanField(_('recordist identical to depositor'))
228    is_published          = BooleanField(_('published'))
229    conservation_site     = CharField(_('conservation site'))
230   
231    # Technical data
232    code                  = CharField(_('code'), unique=True, required=True, validators=[is_valid_collection_code])
233    old_code              = CharField(_('old code'), unique=False, null=True, blank=True)
234    approx_duration       = DurationField(_('approximative duration'))
235    physical_items_num    = IntegerField(_('number of components (medium / piece)'))
236    physical_format       = WeakForeignKey('PhysicalFormat', related_name="collections", 
237                                           verbose_name=_('archive format'))
238    ad_conversion         = WeakForeignKey('AdConversion', related_name='collections', 
239                                           verbose_name=_('digitization'))
240    state                 = TextField(_('status'))
241    a_informer_07_03      = CharField(_('a_informer_07_03'))
242   
243    # All
244    objects               = MediaCollectionManager()
245       
246    def __unicode__(self):
247        return self.code
248
249    @property
250    def public_id(self):
251        return self.code
252   
253    @property
254    def has_mediafile(self):
255        "Tell wether this collection has any media files attached to its items"
256        items = self.items.all()
257        for item in items:
258            if item.file:
259                return True
260        return False
261   
262    def __name_cmp(self, obj1, obj2):
263        return unaccent_icmp(obj1.name, obj2.name)
264
265    def countries(self):
266        "Return the countries of the items"
267        countries = []
268        for item in self.items.filter(location__isnull=False):
269            for country in item.location.countries():
270                if not country in countries:
271                    countries.append(country)
272           
273        countries.sort(self.__name_cmp)               
274
275        return countries
276    countries.verbose_name = _("states / nations")
277
278    def ethnic_groups(self):
279        "Return the ethnic groups of the items"
280        groups = []
281        items = self.items.all()
282        for item in items:
283            if item.ethnic_group and not item.ethnic_group in groups:
284                groups.append(item.ethnic_group)
285
286        cmp = lambda a, b: unaccent_icmp(a.value, b.value)
287        groups.sort(cmp)               
288
289        return groups
290    ethnic_groups.verbose_name = _('populations / social groups')
291
292    def computed_duration(self):
293        duration = Duration()
294        for item in self.items.all():
295            duration += item.computed_duration()
296        return duration
297       
298    computed_duration.verbose_name = _('computed duration')       
299           
300    def save(self, force_insert=False, force_update=False, user=None, code=None):
301        super(MediaCollection, self).save(force_insert, force_update)
302       
303    class Meta(MetaCore):
304        db_table = 'media_collections'
305        ordering = ['code']
306
307
308class MediaCollectionRelated(MediaRelated):
309    "Collection related media"
310   
311    collection      = ForeignKey('MediaCollection', related_name="related", verbose_name=_('collection'))
312   
313    class Meta(MetaCore):
314        db_table = 'media_collection_related'
315
316       
317class MediaItem(MediaResource):
318    "Describe an item"
319   
320    element_type = 'item'
321   
322    # Main Informations
323    title                 = CharField(_('title'))
324    alt_title             = CharField(_('original title / translation'))
325    collector             = CharField(_('recordist'))
326    collection            = ForeignKey('MediaCollection', related_name="items", 
327                                       verbose_name=_('collection'))   
328    recorded_from_date    = DateField(_('recording date (from)'))
329    recorded_to_date      = DateField(_('recording date (until)'))
330   
331    # Geographic and cultural informations
332    location              = WeakForeignKey('Location', verbose_name=_('location'))
333    location_comment      = CharField(_('location details'))
334    cultural_area         = CharField(_('cultural area'))
335    ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items", 
336                                           verbose_name=_('population / social group'))
337    language              = CharField(_('language'))
338    context_comment       = TextField(_('comments / ethnographic context'))
339    moda_execut           = CharField(_('moda_execut'))
340   
341    # Musical informations
342    vernacular_style      = WeakForeignKey('VernacularStyle', related_name="items", 
343                                           verbose_name=_('vernacular style'))
344    generic_style         = WeakForeignKey('GenericStyle', related_name="items", 
345                                           verbose_name=_('generic style'))
346    author                = CharField(_('author / compositor'))
347   
348    # General informations
349    comment               = TextField(_('remarks'))
350    collector_selection   = CharField(_('recordist selection'))
351    collector_from_collection = BooleanField(_('recordist as in collection'))
352   
353    # Archiving data
354    code                  = CharField(_('code'), unique=True, blank=True)
355    old_code              = CharField(_('old code'), unique=False, blank=True)
356    track                 = CharField(_('item number'))
357    creator_reference     = CharField(_('reference'))
358    external_references   = TextField(_('published references'))
359    copied_from_item      = WeakForeignKey('self', related_name="copies", verbose_name=_('copy of'))
360    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
361    file                  = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename")
362   
363    # Technical data
364    approx_duration       = DurationField(_('approximative duration'))
365   
366    # All
367    objects               = MediaItemManager()
368
369    def keywords(self):
370        return ContextKeyword.objects.filter(item_relations__item = self)
371    keywords.verbose_name = _('keywords')
372
373    @property
374    def public_id(self):
375        if self.code:
376            return self.code
377        return self.id
378
379    class Meta(MetaCore):
380        db_table = 'media_items'
381        permissions = (("can_play_all_items", "Can play all media items"), 
382                       ("can_download_all_items", "Can download all media items"), )
383
384    def is_valid_code(self, code):
385        "Check if the item code is well formed"
386        if not re.match('^' + self.collection.code, self.code):
387            return False
388        if self.collection.is_published:
389            regex = '^' + item_published_code_regex + '$'
390        else:
391            regex = '^' + item_unpublished_code_regex + '$'
392        if re.match(regex, code):
393            return True
394        return False
395
396    def clean(self):
397        if self.code and not self.is_valid_code(self.code):
398            raise ValidationError("%s is not a valid item code for collection %s" 
399                                        % (self.code, self.collection.code))
400                                       
401    def save(self, force_insert=False, force_update=False):
402        super(MediaItem, self).save(force_insert, force_update)
403
404    def computed_duration(self):
405        "Tell the length in seconds of this item media data"
406        return self.approx_duration
407
408    computed_duration.verbose_name = _('computed duration')       
409
410    def __unicode__(self):
411        if self.title and not re.match('^ *N *$', self.title):
412            title = self.title
413        else:
414            title = unicode(self.collection)
415        if self.track:
416            title += ' ' + self.track
417        return title
418
419
420class MediaItemRelated(MediaRelated):
421    "Item related media"
422   
423    item            = ForeignKey('MediaItem', related_name="related", verbose_name=_('item'))
424   
425    class Meta(MetaCore):
426        db_table = 'media_item_related'
427
428
429class MediaItemKeyword(ModelCore):
430    "Item keyword"
431    item    = ForeignKey('MediaItem', verbose_name=_('item'), related_name="keyword_relations")
432    keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'), related_name="item_relations")
433
434    class Meta(MetaCore):
435        db_table = 'media_item_keywords'
436        unique_together = (('item', 'keyword'),)
437
438
439class MediaItemPerformance(ModelCore):
440    "Item performance"
441    media_item      = ForeignKey('MediaItem', related_name="performances", 
442                                 verbose_name=_('item'))
443    instrument      = WeakForeignKey('Instrument', related_name="performances", 
444                                     verbose_name=_('composition'))
445    alias           = WeakForeignKey('InstrumentAlias', related_name="performances", 
446                                     verbose_name=_('vernacular name'))
447    instruments_num = CharField(_('number'))
448    musicians       = CharField(_('interprets'))
449
450    class Meta(MetaCore):
451        db_table = 'media_item_performances'
452
453
454class MediaItemAnalysis(ModelCore):
455    "Item analysis result computed by TimeSide"
456
457    element_type = 'analysis'   
458    item  = ForeignKey('MediaItem', related_name="analysis", verbose_name=_('item'))
459    analyzer_id = CharField(_('id'), required=True)
460    name = CharField(_('name'))
461    value = CharField(_('value'))
462    unit = CharField(_('unit'))
463   
464    class Meta(MetaCore):
465        db_table = 'media_analysis'
466        ordering = ['name']
467       
468    def to_dict(self):
469        if self.analyzer_id == 'duration':
470            if '.' in value:
471                value = self.value.split('.')
472                self.value = '.'.join([value[0], value[1][:2]])
473        return {'id': self.analyzer_id, 'name': self.name, 'value': self.value, 'unit': self.unit}
474       
475       
476class MediaPart(MediaResource):
477    "Describe an item part"
478    element_type = 'part'
479    item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item'))
480    title = CharField(_('title'), required=True)
481    start = FloatField(_('start'), required=True)
482    end   = FloatField(_('end'), required=True)
483   
484    class Meta(MetaCore):
485        db_table = 'media_parts'
486
487    def __unicode__(self):
488        return self.title
489
490class Playlist(ModelCore):
491    "Item, collection or marker playlist"
492    element_type = 'playlist'
493    public_id      = CharField(_('public_id'), required=True)
494    author         = ForeignKey(User, related_name="playlists", db_column="author")
495    title          = CharField(_('title'), required=True)
496    description    = TextField(_('description'))
497
498    class Meta(MetaCore):
499        db_table = 'playlists'
500
501    def __unicode__(self):
502        return self.title
503       
504
505class PlaylistResource(ModelCore):
506    "Playlist components"
507    RESOURCE_TYPE_CHOICES = (('item', 'item'), ('collection', 'collection'), ('marker', 'marker'))
508    element_type = 'playlist_resource'
509    public_id          = CharField(_('public_id'), required=True)
510    playlist           = ForeignKey('Playlist', related_name="resources", verbose_name=_('playlist'))
511    resource_type      = CharField(_('resource_type'), choices=RESOURCE_TYPE_CHOICES, required=True)
512    resource_id        = CharField(_('resource_id'), required=True)
513
514    class Meta(MetaCore):
515        db_table = 'playlist_resources'
516       
517       
518class MediaItemMarker(MediaResource):
519    "2D marker object : text value vs. time"
520   
521    element_type = 'marker'
522   
523    item            = ForeignKey('MediaItem', related_name="markers", verbose_name=_('item'))
524    public_id       = CharField(_('public_id'), required=True)
525    time            = FloatField(_('time'))
526    title           = CharField(_('title'))
527    date            = DateTimeField(_('date'), auto_now=True)
528    description     = TextField(_('description'))
529    author          = ForeignKey(User, related_name="markers", verbose_name=_('author'))
530   
531    class Meta(MetaCore):
532        db_table = 'media_markers'
533
534    def __unicode__(self):
535        if self.title:
536            return self.title
537        else:
538            return self.public_id
539
540
541class MediaItemTranscodingFlag(ModelCore):
542    "Item flag to know if the MediaItem has been transcoded to a given format"
543   
544    item            = ForeignKey('MediaItem', related_name="transcoding", verbose_name=_('item'))
545    mime_type       = CharField(_('mime_type'), required=True)
546    date            = DateTimeField(_('date'), auto_now=True)
547    value           = BooleanField(_('transcoded'))
548   
549    class Meta(MetaCore):
550        db_table = 'media_transcoding'
551       
552       
553class Search(ModelCore):
554    "Keywork search"
555   
556    element_type = 'search'
557   
558    username = ForeignKey(User, related_name="searches", db_column="username")
559    keywords = CharField(_('keywords'), required=True)
560    date = DateField(_('date'), auto_now_add=True)
561
562    class Meta(MetaCore):
563        db_table = 'searches'
564
565    def __unicode__(self):
566        return self.keywords
567
568
569class DublinCoreToFormatMetadata(object):
570    """ a mapping class to get item DublinCore metadata dictionaries
571    in various audio metadata format (MP3, OGG, etc...)"""
572   
573    #FIXME: should be given by timeside
574    unavailable_extensions = ['wav', 'aiff', 'aif']
575   
576    metadata_mapping = { 
577                    'mp3' : {
578                         'title': 'TIT2', #title2
579                         'creator': 'TCOM', #composer
580                         'creator': 'TPE1', #lead
581                         'identifier': 'UFID', #unique ID
582                         'relation': 'TALB', #album
583                         'type': 'TCON', #genre
584                         'publisher': 'TPUB', #publisher
585                         'date': 'TDRC', #year
586#                         'coverage': 'COMM',  #comment
587                         }, 
588                    'ogg': {
589                        'creator': 'artist',
590                        'relation': 'album', 
591                        'all': 'all', 
592                       }, 
593                    'flac': {
594                        'creator': 'artist',
595                        'relation': 'album', 
596                        'all': 'all', 
597                       }, 
598                    'wav': {
599                        'creator': 'artist',
600                        'relation': 'album', 
601                        'all': 'all', 
602                       }, 
603                    }
604   
605    def __init__(self, format):
606        self.format = format
607       
608    def get_metadata(self, dc_metadata):
609        mapp = self.metadata_mapping[self.format]
610        metadata = {}
611        keys_done = []
612        for data in dc_metadata:
613            key = data[0]
614            value = data[1].encode('utf-8')
615            if value:
616                if key == 'date':
617                    value = value.split(';')[0].split('=')
618                    if len(value) > 1:
619                        value  = value[1]
620                        value = value.split('-')[0]
621                    else:
622                        value = value[0].split('-')[0]
623                if key in mapp:
624                    metadata[mapp[key]] = value.decode('utf-8')
625                elif 'all' in mapp.keys():
626                    metadata[key] = value.decode('utf-8')
627                keys_done.append(key)
628        return metadata
Note: See TracBrowser for help on using the repository browser.