source: telemeta/models/media.py @ 1caf64b

cremcrem2devdev2diademsfeature/breadcrumbsfeature/ts-0.5feature/ts-0.5.4feature/writecacheformagenericinstru_searchlamlam2mapsv3mergenlivemultiproductionrelease/1.4.4sabiodsecurityserversocialstoragetelecastertest
Last change on this file since 1caf64b was 1caf64b, checked in by yomguy <yomguy@…>, 3 years ago

fix item codes

  • Property mode set to 100644
File size: 21.4 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
45from telemeta.models.location import LocationRelation, Location
46from telemeta.models.system import Revision
47from telemeta.models.query import *
48from telemeta.models.instrument import *
49from telemeta.models.enum import *
50from django.forms import ModelForm
51
52
53class MediaResource(ModelCore):
54    "Base class of all media objects"
55
56    def public_access_label(self):
57        if self.public_access == 'metadata':
58            return _('Metadata only')
59        elif self.public_access == 'full':
60            return _('Sound and metadata')
61
62        return _('Private data')
63    public_access_label.verbose_name = _('public access')
64
65    def set_revision(self, user):
66        "Save a media object and add a revision"
67        Revision.touch(self, user)   
68
69    def get_revision(self):
70        return Revision.objects.filter(element_type=self.element_type, element_id=self.id).order_by('-time')[0]
71
72    class Meta:
73        abstract = True
74
75
76collection_published_code_regex   = 'CNRSMH_E_[0-9]{4}(?:_[0-9]{3}){2}'
77collection_unpublished_code_regex = 'CNRSMH_I_[0-9]{4}_[0-9]{3}'
78collection_code_regex             = '(?:%s|%s)' % (collection_published_code_regex, 
79                                                    collection_unpublished_code_regex)
80                                                       
81class MediaCollection(MediaResource):
82    "Describe a collection of items"
83    element_type = 'collection'
84    PUBLIC_ACCESS_CHOICES = (('none', 'none'), ('metadata', 'metadata'), ('full', 'full'))
85
86    def is_valid_collection_code(value):
87        "Check if the collection code is well formed"
88        regex = '^' + collection_code_regex + '$'
89        if not re.match(regex, value):
90            raise ValidationError(u'%s is not a valid collection code' % value)
91   
92    # General informations
93    reference             = CharField(_('reference'), unique=True, null=True)
94    title                 = CharField(_('title'), required=True)
95    alt_title             = CharField(_('original title / translation'))
96    creator               = CharField(_('depositor / contributor'))
97    recording_context     = WeakForeignKey('RecordingContext', related_name="collections", 
98                                           verbose_name=_('recording context'))
99    recorded_from_year    = IntegerField(_('recording year (from)'))
100    recorded_to_year      = IntegerField(_('recording year (until)'))
101    year_published        = IntegerField(_('year published'))
102   
103    # Geographic and cultural informations
104    ## See "countries" and "ethnic_groups" methods below
105
106    # Legal notices
107    collector             = CharField(_('recordist'))
108    publisher             = WeakForeignKey('Publisher', related_name="collections", 
109                                           verbose_name=_('publisher / status'))     
110    publisher_collection  = WeakForeignKey('PublisherCollection', related_name="collections", 
111                                            verbose_name=_('publisher collection'))
112    publisher_serial      = CharField(_('publisher serial number'))
113    booklet_author        = CharField(_('author of published notice'))
114    external_references   = TextField(_('bibliographic references'))
115    doctype_code          = IntegerField(_('document type'))
116    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, 
117                                      max_length=16, default="metadata")
118    legal_rights          = WeakForeignKey('LegalRight', related_name="collections", 
119                                           verbose_name=_('legal rights'))
120   
121    # Archiving data
122    acquisition_mode      = WeakForeignKey('AcquisitionMode', related_name="collections", 
123                                            verbose_name=_('mode of acquisition'))
124    cnrs_contributor      = CharField(_('CNRS depositor'))
125    metadata_author       = WeakForeignKey('MetadataAuthor', related_name="collections", 
126                                           verbose_name=_('record author'))
127    booklet_description   = TextField(_('related documentation'))
128    publishing_status     = WeakForeignKey('PublishingStatus', related_name="collections", 
129                                           verbose_name=_('secondary edition'))
130    alt_ids               = CharField(_('copies'))
131    comment               = TextField(_('comment'))
132    metadata_writer       = WeakForeignKey('MetadataWriter', related_name="collections", 
133                                           verbose_name=_('record writer'))
134    travail               = CharField(_('archiver notes'))
135    items_done            = CharField(_('items finished'))
136    collector_is_creator  = BooleanField(_('recordist identical to depositor'))
137    is_published          = BooleanField(_('published'))
138   
139    # Technical data
140    code                  = CharField(_('code'), unique=True, required=True, validators=[is_valid_collection_code])
141    old_code              = CharField(_('old code'), unique=False, null=True, blank=True)
142    approx_duration       = DurationField(_('approximative duration'))
143    physical_items_num    = IntegerField(_('number of components (medium / piece)'))
144    physical_format       = WeakForeignKey('PhysicalFormat', related_name="collections", 
145                                           verbose_name=_('archive format'))
146    ad_conversion         = WeakForeignKey('AdConversion', related_name='collections', 
147                                           verbose_name=_('digitization'))
148    state                 = TextField(_('status'))
149    a_informer_07_03      = CharField(_('a_informer_07_03'))
150   
151    # All
152    objects               = MediaCollectionManager()
153       
154    def __unicode__(self):
155        return self.code
156
157    @property
158    def public_id(self):
159        return self.code
160
161    def has_mediafile(self):
162        "Tell wether this collection has any media files attached to its items"
163        items = self.items.all()
164        for item in items:
165            if item.file:
166                return True
167        return False
168
169    def __name_cmp(self, obj1, obj2):
170        return unaccent_icmp(obj1.name, obj2.name)
171
172    def countries(self):
173        "Return the countries of the items"
174        countries = []
175        for item in self.items.filter(location__isnull=False):
176            for country in item.location.countries():
177                if not country in countries:
178                    countries.append(country)
179           
180        countries.sort(self.__name_cmp)               
181
182        return countries
183    countries.verbose_name = _("states / nations")
184
185    def ethnic_groups(self):
186        "Return the ethnic groups of the items"
187        groups = []
188        items = self.items.all()
189        for item in items:
190            if item.ethnic_group and not item.ethnic_group in groups:
191                groups.append(item.ethnic_group)
192
193        cmp = lambda a, b: unaccent_icmp(a.value, b.value)
194        groups.sort(cmp)               
195
196        return groups
197    ethnic_groups.verbose_name = _('populations / social groups')
198
199    def computed_duration(self):
200        duration = Duration()
201        for item in self.items.all():
202            duration += item.computed_duration()
203        return duration
204       
205    computed_duration.verbose_name = _('computed duration')       
206           
207    def save(self, force_insert=False, force_update=False, user=None, code=None):
208        super(MediaCollection, self).save(force_insert, force_update)
209       
210    class Meta(MetaCore):
211        db_table = 'media_collections'
212        ordering = ['code']
213   
214class MediaCollectionForm(ModelForm):
215    class Meta:
216        model = MediaCollection
217       
218
219item_published_code_regex    = collection_published_code_regex + '(?:_[0-9]{2,3}){1,2}'
220item_unpublished_code_regex  = collection_unpublished_code_regex + '_[0-9]{2,3}(?:_[0-9]{2}){0,2}'
221item_code_regex              = '(?:%s|%s)' % (item_published_code_regex, item_unpublished_code_regex)
222
223class MediaItem(MediaResource):
224    "Describe an item"
225    element_type = 'item'
226    PUBLIC_ACCESS_CHOICES = (('none', 'none'), ('metadata', 'metadata'), ('full', 'full'))
227   
228    # Main Informations
229    title                 = CharField(_('title'))
230    alt_title             = CharField(_('original title / translation'))
231    collector             = CharField(_('recordist'))
232    collection            = ForeignKey('MediaCollection', related_name="items", 
233                                       verbose_name=_('collection'))   
234    recorded_from_date    = DateField(_('recording date (from)'))
235    recorded_to_date      = DateField(_('recording date (until)'))
236   
237    # Geographic and cultural informations
238    location              = WeakForeignKey('Location', verbose_name=_('location'))
239    location_comment      = CharField(_('location details'))
240    cultural_area         = CharField(_('cultural area'))
241    ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items", 
242                                           verbose_name=_('population / social group'))
243    context_comment       = TextField(_('comments'))
244    moda_execut           = CharField(_('moda_execut'))
245    language              = CharField(_('language'))
246   
247    # Musical informations
248    vernacular_style      = WeakForeignKey('VernacularStyle', related_name="items", 
249                                           verbose_name=_('vernacular style'))
250    generic_style         = WeakForeignKey('GenericStyle', related_name="items", 
251                                           verbose_name=_('generic style'))
252    author                = CharField(_('author / compositor'))
253   
254    # General informations
255    comment               = TextField(_('remarks'))
256    collector_selection   = CharField(_('recordist selection'))
257    collector_from_collection = BooleanField(_('recordist as in collection'))
258   
259    # Archiving data
260    code                  = CharField(_('code'), unique=True, null=True)
261    old_code              = CharField(_('old code'), unique=False, null=True, blank=True)
262    track                 = CharField(_('item number'))
263    creator_reference     = CharField(_('reference'))
264    external_references   = TextField(_('published references'))
265    copied_from_item      = WeakForeignKey('self', related_name="copies", verbose_name=_('copy of'))
266    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
267    file                  = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename")
268   
269    # Technical data
270    approx_duration       = DurationField(_('approximative duration'))
271   
272    # All
273    objects               = MediaItemManager()
274
275    def keywords(self):
276        return ContextKeyword.objects.filter(item_relations__item = self)
277    keywords.verbose_name = _('keywords')
278
279    @property
280    def public_id(self):
281        if self.code:
282            return self.code
283        return self.id
284
285    class Meta(MetaCore):
286        db_table = 'media_items'
287#        ordering = ['code', 'old_code']
288
289    def is_valid_code(self, code):
290        "Check if the item code is well formed"
291        if not re.match('^' + self.collection.code, self.code):
292            return False
293        if self.collection.is_published:
294            regex = '^' + item_published_code_regex + '$'
295        else:
296            regex = '^' + item_unpublished_code_regex + '$'
297        if re.match(regex, code):
298            return True
299        return False
300
301    def clean(self):
302        if self.code and not self.is_valid_code(self.code):
303            raise ValidationError("%s is not a valid item code for collection %s" 
304                                        % (self.code, self.collection.code))
305                                       
306    def save(self, force_insert=False, force_update=False):
307        super(MediaItem, self).save(force_insert, force_update)
308
309    def computed_duration(self):
310        "Tell the length in seconds of this item media data"
311        return self.approx_duration
312
313    computed_duration.verbose_name = _('computed duration')       
314
315    def __unicode__(self):
316        if self.title and not re.match('^ *N *$', self.title):
317            title = self.title
318        else:
319            title = unicode(self.collection)
320
321        if self.track:
322            title += ' ' + self.track
323
324        return title
325
326class MediaItemForm(ModelForm):
327    class Meta:
328        model = MediaItem
329       
330class MediaItemKeyword(ModelCore):
331    "Item keyword"
332    item    = ForeignKey('MediaItem', verbose_name=_('item'), related_name="keyword_relations")
333    keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'), related_name="item_relations")
334
335    class Meta(MetaCore):
336        db_table = 'media_item_keywords'
337        unique_together = (('item', 'keyword'),)
338
339class MediaItemKeywordForm(ModelForm):
340    class Meta:
341        model = MediaItemKeyword
342
343class MediaItemPerformance(ModelCore):
344    "Item performance"
345    media_item      = ForeignKey('MediaItem', related_name="performances", 
346                                 verbose_name=_('item'))
347    instrument      = WeakForeignKey('Instrument', related_name="performances", 
348                                     verbose_name=_('composition'))
349    alias           = WeakForeignKey('InstrumentAlias', related_name="performances", 
350                                     verbose_name=_('vernacular name'))
351    instruments_num = CharField(_('number'))
352    musicians       = CharField(_('interprets'))
353
354    class Meta(MetaCore):
355        db_table = 'media_item_performances'
356
357class MediaItemPerformanceForm(ModelForm):
358    class Meta:
359        model = MediaItemPerformance
360       
361    def __init__(self, *args, **kwds):
362        super(MediaItemPerformanceForm, self).__init__(*args, **kwds)
363        self.fields['instrument'].queryset = Instrument.objects.order_by('name')
364        self.fields['alias'].queryset = InstrumentAlias.objects.order_by('name')
365
366class MediaItemAnalysis(ModelCore):
367    "Item analysis result computed by TimeSide"
368
369    element_type = 'analysis'   
370    item  = ForeignKey('MediaItem', related_name="analysis", verbose_name=_('item'))
371    analyzer_id = CharField(_('id'), required=True)
372    name = CharField(_('name'))
373    value = CharField(_('value'))
374    unit = CharField(_('unit'))
375   
376    class Meta(MetaCore):
377        db_table = 'media_analysis'
378        ordering = ['name']
379       
380    def to_dict(self):
381        return {'id': self.analyzer_id, 'name': self.name, 'value': self.value, 'unit': self.unit}
382       
383       
384class MediaPart(MediaResource):
385    "Describe an item part"
386    element_type = 'part'
387    item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item'))
388    title = CharField(_('title'), required=True)
389    start = FloatField(_('start'), required=True)
390    end   = FloatField(_('end'), required=True)
391   
392    class Meta(MetaCore):
393        db_table = 'media_parts'
394
395    def __unicode__(self):
396        return self.title
397
398class Playlist(ModelCore):
399    "Item, collection or marker playlist"
400    element_type = 'playlist'
401    public_id      = CharField(_('public_id'), required=True)
402    author         = ForeignKey(User, related_name="playlists", db_column="author")
403    title          = CharField(_('title'), required=True)
404    description    = TextField(_('description'))
405
406    class Meta(MetaCore):
407        db_table = 'playlists'
408
409    def __unicode__(self):
410        return self.title
411       
412class PlaylistForm(ModelForm):
413    class Meta:
414        model = Playlist
415
416class PlaylistResource(ModelCore):
417    "Playlist components"
418    RESOURCE_TYPE_CHOICES = (('item', 'item'), ('collection', 'collection'), ('marker', 'marker'))
419    element_type = 'playlist_resource'
420    public_id          = CharField(_('public_id'), required=True)
421    playlist           = ForeignKey('Playlist', related_name="resources", verbose_name=_('playlist'))
422    resource_type      = CharField(_('resource_type'), choices=RESOURCE_TYPE_CHOICES, required=True)
423    resource_id        = CharField(_('resource_id'), required=True)
424
425    class Meta(MetaCore):
426        db_table = 'playlist_resources'
427       
428       
429class MediaItemMarker(MediaResource):
430    "2D marker object : text value vs. time"
431   
432    element_type = 'marker'
433   
434    item            = ForeignKey('MediaItem', related_name="markers", verbose_name=_('item'))
435    public_id       = CharField(_('public_id'), required=True)
436    time            = FloatField(_('time'))
437    title           = CharField(_('title'))
438    date            = DateTimeField(_('date'), auto_now=True)
439    description     = TextField(_('description'))
440    author          = ForeignKey(User, related_name="markers", verbose_name=_('author'))
441   
442    class Meta(MetaCore):
443        db_table = 'media_markers'
444
445    def __unicode__(self):
446        if self.title:
447            return self.title
448        else:
449            return self.public_id
450
451
452class MediaItemTranscodingFlag(ModelCore):
453    "Item flag to know if the MediaItem has been transcoded to a given format"
454   
455    item            = ForeignKey('MediaItem', related_name="transcoding", verbose_name=_('item'))
456    mime_type       = CharField(_('mime_type'), required=True)
457    date            = DateTimeField(_('date'), auto_now=True)
458    value           = BooleanField(_('transcoded'))
459   
460    class Meta(MetaCore):
461        db_table = 'media_transcoding'
462
463
464class Search(ModelCore):
465    "Keywork search"
466   
467    element_type = 'search'
468   
469    username = ForeignKey(User, related_name="searches", db_column="username")
470    keywords = CharField(_('keywords'), required=True)
471    date = DateField(_('date'), auto_now_add=True)
472
473    class Meta(MetaCore):
474        db_table = 'searches'
475
476    def __unicode__(self):
477        return self.keywords
478
479
480class DublinCoreToFormatMetadata(object):
481    """ a mapping class to get item DublinCore metadata dictionaries
482    in various audio metadata format (MP3, OGG, etc...)"""
483   
484    metadata_mapping = { 
485                    'mp3' : {
486                         'title': 'TIT2', #title2
487                         'creator': 'TCOM', #composer
488                         'creator': 'TPE1', #lead
489                         'identifier': 'UFID', #unique ID
490                         'relation': 'TALB', #album
491                         'type': 'TCON', #genre
492                         'publisher': 'TPUB', #publisher
493                         'date': 'TDRC', #year
494#                         'coverage': 'COMM',  #comment
495                         }, 
496                    'ogg': {
497                        'creator': 'artist',
498                        'relation': 'album', 
499                        'all': 'all', 
500                       }, 
501                    'flac': {
502                        'creator': 'artist',
503                        'relation': 'album', 
504                        'all': 'all', 
505                       }, 
506                    }
507   
508    def __init__(self, format):
509        self.format = format
510       
511    def get_metadata(self, dc_metadata):
512        mapp = self.metadata_mapping[self.format]
513        metadata = {}
514        keys_done = []
515        for data in dc_metadata:
516            key = data[0]
517            value = data[1].encode('utf-8')
518            if value:
519                if key == 'date':
520                    value = value.split(';')[0].split('=')
521                    if len(value) > 1:
522                        value  = value[1]
523                        value = value.split('-')[0]
524                    else:
525                        value = value[0].split('-')[0]
526                if key in mapp:
527                    metadata[mapp[key]] = value.decode('utf-8')
528                elif 'all' in mapp.keys():
529                    metadata[key] = value.decode('utf-8')
530                keys_done.append(key)
531        return metadata
Note: See TracBrowser for help on using the repository browser.