source: telemeta/models/media.py @ 451866d

cremcrem2devdev2diademsdj1.6feature/breadcrumbsfeature/ts-0.5feature/ts-0.5.4feature/writecacheformagenericinstru_searchlamlam2mapsv3mergenlivemultiproductionrelease/1.4.4sabiodsecurityserversocialstoragetelecastertest
Last change on this file since 451866d was 451866d, checked in by yomguy <yomguy@…>, 3 years ago

yomguy 2011-10-18 fix blank item.code (now recorded as None, not )
yomguy 2011-10-18 fix wrong login url, add internal id to dublincore, fix OAI identifiers
yomguy 2011-10-17 revert ipauth to django.contrib.auth
yomguy 2011-10-17 OAI fix : better id management (database id by default), split multiple coverage, repositoryName becomes settings.TELEMETA_ORGANIZATION

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