From d511f9fc18c3f9280ea539de7ec00e549a47ff15 Mon Sep 17 00:00:00 2001 From: yomguy Date: Wed, 7 Dec 2011 15:32:19 +0100 Subject: [PATCH] transcode flag only from source streaming, cleanup --- telemeta/__init__.py | 16 +-- telemeta/models/media.py | 214 +++++++++++++++++++-------------------- telemeta/web/base.py | 32 +++--- 3 files changed, 128 insertions(+), 134 deletions(-) diff --git a/telemeta/__init__.py b/telemeta/__init__.py index 28ef93e0..ed85edd8 100644 --- a/telemeta/__init__.py +++ b/telemeta/__init__.py @@ -12,7 +12,7 @@ U{http://telemeta.org} """ __docformat__ = 'epytext en' -__version__ = '1.3' +__version__ = '1.4' __url__ = 'http://telemeta.org' __copyright__ = '(C) 2007-2011 Parisson' __license__ = 'CeCILL-2' @@ -23,20 +23,20 @@ Copyright (C) Parisson SARL (2007-2011) -This software is a computer program whose purpose is to backup, analyse, +This software is a computer program whose purpose is to backup, analyse, transcode and stream any audio content with its metadata. This software is governed by the CeCILL v2 license under French law and -abiding by the rules of distribution of free software. You can use, +abiding by the rules of distribution of free software. You can use, modify and/ or redistribute the software under the terms of the CeCILL license as circulated by CEA, CNRS and INRIA at the following URL -"http://www.cecill.info". +"http://www.cecill.info". As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the successive licensors have only limited -liability. +liability. In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or developing or reproducing the @@ -45,9 +45,9 @@ that may mean that it is complicated to manipulate, and that also therefore means that it is reserved for developers and experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the software's suitability as regards their -requirements in conditions enabling the security of their systems and/or -data to be ensured and, more generally, to use and operate it in the -same conditions as regards security. +requirements in conditions enabling the security of their systems and/or +data to be ensured and, more generally, to use and operate it in the +same conditions as regards security. The fact that you are presently reading this means that you have had knowledge of the CeCILL license and that you accept its terms. diff --git a/telemeta/models/media.py b/telemeta/models/media.py index 1eb75f2f..f641e361 100644 --- a/telemeta/models/media.py +++ b/telemeta/models/media.py @@ -50,10 +50,10 @@ from telemeta.models.instrument import * from telemeta.models.enum import * from django.db.models.fields import URLField - + collection_published_code_regex = '[A-Za-z0-9._-]*' collection_unpublished_code_regex = '[A-Za-z0-9._-]*' -collection_code_regex = '(?:%s|%s)' % (collection_published_code_regex, +collection_code_regex = '(?:%s|%s)' % (collection_published_code_regex, collection_unpublished_code_regex) item_published_code_regex = '[A-Za-z0-9._-]*' @@ -76,7 +76,7 @@ class MediaResource(ModelCore): def set_revision(self, user): "Save a media object and add a revision" - Revision.touch(self, user) + Revision.touch(self, user) def get_revision(self): return Revision.objects.filter(element_type=self.element_type, element_id=self.id).order_by('-time')[0] @@ -86,9 +86,9 @@ class MediaResource(ModelCore): class MediaRelated(MediaResource): "Related media" - + element_type = 'media' - + title = CharField(_('title')) date = DateTimeField(_('date'), auto_now=True) description = TextField(_('description')) @@ -96,7 +96,7 @@ class MediaRelated(MediaResource): url = CharField(_('url'), max_length=500) credits = CharField(_('credits')) file = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename") - + def is_image(self): is_url_image = False if self.url: @@ -105,28 +105,28 @@ class MediaRelated(MediaResource): if type in self.url: is_url_image = True return 'image' in self.mime_type or is_url_image - + def save(self, force_insert=False, force_update=False): super(MediaRelated, self).save(force_insert, force_update) - + def set_mime_type(self): if self.file: self.mime_type = mimetypes.guess_type(self.file.path)[0] - + def __unicode__(self): if self.title and not re.match('^ *N *$', self.title): title = self.title else: title = unicode(self.item) return title - + class Meta: abstract = True class MediaCorpus(MediaResource): "Describe a corpus of collections" - + element_type = 'corpus' # General informations @@ -134,9 +134,9 @@ class MediaCorpus(MediaResource): title = CharField(_('title'), required=True) description = CharField(_('description')) code = CharField(_('code'), unique=True, required=True) - public_access = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, + public_access = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata") - + def __unicode__(self): return self.code @@ -146,7 +146,7 @@ class MediaCorpus(MediaResource): def save(self, force_insert=False, force_update=False, user=None, code=None): super(MediaCorpus, self).save(force_insert, force_update) - + class Meta(MetaCore): db_table = 'media_corpus' ordering = ['code'] @@ -154,24 +154,24 @@ class MediaCorpus(MediaResource): class MediaCorpusCollectionRelation(ModelCore): "Relations between Corpus and Collections" - - collection = ForeignKey('MediaCollection', related_name="parent_relation", + + collection = ForeignKey('MediaCollection', related_name="parent_relation", verbose_name=_('collection')) - corpus = ForeignKey('MediaCorpus', related_name="child_relation", + corpus = ForeignKey('MediaCorpus', related_name="child_relation", verbose_name=_('corpus')) class Meta(MetaCore): db_table = 'media_corpus_collection_relations' unique_together = (('collection', 'corpus'),) - + def __unicode__(self): sep = ' > ' return self.corpus.code + sep + self.collection.code - + class MediaCollection(MediaResource): "Describe a collection of items" - + element_type = 'collection' def is_valid_collection_code(value): @@ -179,77 +179,77 @@ class MediaCollection(MediaResource): regex = '^' + collection_code_regex + '$' if not re.match(regex, value): raise ValidationError(u'%s is not a valid collection code' % value) - + # General informations reference = CharField(_('reference'), unique=True, null=True) title = CharField(_('title'), required=True) alt_title = CharField(_('original title / translation')) creator = CharField(_('depositor / contributor')) - recording_context = WeakForeignKey('RecordingContext', related_name="collections", + recording_context = WeakForeignKey('RecordingContext', related_name="collections", verbose_name=_('recording context')) recorded_from_year = IntegerField(_('recording year (from)')) recorded_to_year = IntegerField(_('recording year (until)')) year_published = IntegerField(_('year published')) - + # Geographic and cultural informations ## See "countries" and "ethnic_groups" methods below # Legal notices collector = CharField(_('recordist')) - publisher = WeakForeignKey('Publisher', related_name="collections", - verbose_name=_('publisher / status')) - publisher_collection = WeakForeignKey('PublisherCollection', related_name="collections", + publisher = WeakForeignKey('Publisher', related_name="collections", + verbose_name=_('publisher / status')) + publisher_collection = WeakForeignKey('PublisherCollection', related_name="collections", verbose_name=_('publisher collection')) publisher_serial = CharField(_('publisher serial number')) booklet_author = CharField(_('author of published notice')) external_references = TextField(_('bibliographic references')) doctype_code = IntegerField(_('document type')) - public_access = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, + public_access = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata") - legal_rights = WeakForeignKey('LegalRight', related_name="collections", + legal_rights = WeakForeignKey('LegalRight', related_name="collections", verbose_name=_('legal rights')) - + # Archiving data - acquisition_mode = WeakForeignKey('AcquisitionMode', related_name="collections", + acquisition_mode = WeakForeignKey('AcquisitionMode', related_name="collections", verbose_name=_('mode of acquisition')) cnrs_contributor = CharField(_('CNRS depositor')) - metadata_author = WeakForeignKey('MetadataAuthor', related_name="collections", + metadata_author = WeakForeignKey('MetadataAuthor', related_name="collections", verbose_name=_('record author')) booklet_description = TextField(_('related documentation')) - publishing_status = WeakForeignKey('PublishingStatus', related_name="collections", + publishing_status = WeakForeignKey('PublishingStatus', related_name="collections", verbose_name=_('secondary edition')) alt_ids = CharField(_('copies')) comment = TextField(_('comment')) - metadata_writer = WeakForeignKey('MetadataWriter', related_name="collections", + metadata_writer = WeakForeignKey('MetadataWriter', related_name="collections", verbose_name=_('record writer')) travail = CharField(_('archiver notes')) items_done = CharField(_('items finished')) collector_is_creator = BooleanField(_('recordist identical to depositor')) is_published = BooleanField(_('published')) conservation_site = CharField(_('conservation site')) - + # Technical data code = CharField(_('code'), unique=True, required=True, validators=[is_valid_collection_code]) old_code = CharField(_('old code'), unique=False, null=True, blank=True) approx_duration = DurationField(_('approximative duration')) physical_items_num = IntegerField(_('number of components (medium / piece)')) - physical_format = WeakForeignKey('PhysicalFormat', related_name="collections", + physical_format = WeakForeignKey('PhysicalFormat', related_name="collections", verbose_name=_('archive format')) - ad_conversion = WeakForeignKey('AdConversion', related_name='collections', + ad_conversion = WeakForeignKey('AdConversion', related_name='collections', verbose_name=_('digitization')) state = TextField(_('status')) a_informer_07_03 = CharField(_('a_informer_07_03')) - + # All objects = MediaCollectionManager() - + def __unicode__(self): return self.code @property def public_id(self): return self.code - + @property def has_mediafile(self): "Tell wether this collection has any media files attached to its items" @@ -258,7 +258,7 @@ class MediaCollection(MediaResource): if item.file: return True return False - + def __name_cmp(self, obj1, obj2): return unaccent_icmp(obj1.name, obj2.name) @@ -269,8 +269,8 @@ class MediaCollection(MediaResource): for country in item.location.countries(): if not country in countries: countries.append(country) - - countries.sort(self.__name_cmp) + + countries.sort(self.__name_cmp) return countries countries.verbose_name = _("states / nations") @@ -284,7 +284,7 @@ class MediaCollection(MediaResource): groups.append(item.ethnic_group) cmp = lambda a, b: unaccent_icmp(a.value, b.value) - groups.sort(cmp) + groups.sort(cmp) return groups ethnic_groups.verbose_name = _('populations / social groups') @@ -294,12 +294,12 @@ class MediaCollection(MediaResource): for item in self.items.all(): duration += item.computed_duration() return duration - - computed_duration.verbose_name = _('computed duration') - + + computed_duration.verbose_name = _('computed duration') + def save(self, force_insert=False, force_update=False, user=None, code=None): super(MediaCollection, self).save(force_insert, force_update) - + class Meta(MetaCore): db_table = 'media_collections' ordering = ['code'] @@ -307,49 +307,49 @@ class MediaCollection(MediaResource): class MediaCollectionRelated(MediaRelated): "Collection related media" - + collection = ForeignKey('MediaCollection', related_name="related", verbose_name=_('collection')) - + class Meta(MetaCore): db_table = 'media_collection_related' - + class MediaItem(MediaResource): "Describe an item" - + element_type = 'item' - + # Main Informations title = CharField(_('title')) alt_title = CharField(_('original title / translation')) collector = CharField(_('recordist')) - collection = ForeignKey('MediaCollection', related_name="items", - verbose_name=_('collection')) + collection = ForeignKey('MediaCollection', related_name="items", + verbose_name=_('collection')) recorded_from_date = DateField(_('recording date (from)')) recorded_to_date = DateField(_('recording date (until)')) - + # Geographic and cultural informations location = WeakForeignKey('Location', verbose_name=_('location')) location_comment = CharField(_('location details')) cultural_area = CharField(_('cultural area')) - ethnic_group = WeakForeignKey('EthnicGroup', related_name="items", + ethnic_group = WeakForeignKey('EthnicGroup', related_name="items", verbose_name=_('population / social group')) language = CharField(_('language')) context_comment = TextField(_('comments / ethnographic context')) moda_execut = CharField(_('moda_execut')) - + # Musical informations - vernacular_style = WeakForeignKey('VernacularStyle', related_name="items", + vernacular_style = WeakForeignKey('VernacularStyle', related_name="items", verbose_name=_('vernacular style')) - generic_style = WeakForeignKey('GenericStyle', related_name="items", + generic_style = WeakForeignKey('GenericStyle', related_name="items", verbose_name=_('generic style')) author = CharField(_('author / compositor')) - + # General informations comment = TextField(_('remarks')) collector_selection = CharField(_('recordist selection')) collector_from_collection = BooleanField(_('recordist as in collection')) - + # Archiving data code = CharField(_('code'), unique=True, blank=True) old_code = CharField(_('old code'), unique=False, blank=True) @@ -359,10 +359,10 @@ class MediaItem(MediaResource): copied_from_item = WeakForeignKey('self', related_name="copies", verbose_name=_('copy of')) public_access = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata") file = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename") - + # Technical data approx_duration = DurationField(_('approximative duration')) - + # All objects = MediaItemManager() @@ -378,7 +378,7 @@ class MediaItem(MediaResource): class Meta(MetaCore): db_table = 'media_items' - permissions = (("can_play_all_items", "Can play all media items"), + permissions = (("can_play_all_items", "Can play all media items"), ("can_download_all_items", "Can download all media items"), ) def is_valid_code(self, code): @@ -395,9 +395,9 @@ class MediaItem(MediaResource): def clean(self): if self.code and not self.is_valid_code(self.code): - raise ValidationError("%s is not a valid item code for collection %s" + raise ValidationError("%s is not a valid item code for collection %s" % (self.code, self.collection.code)) - + def save(self, force_insert=False, force_update=False): super(MediaItem, self).save(force_insert, force_update) @@ -405,7 +405,7 @@ class MediaItem(MediaResource): "Tell the length in seconds of this item media data" return self.approx_duration - computed_duration.verbose_name = _('computed duration') + computed_duration.verbose_name = _('computed duration') def __unicode__(self): if self.title and not re.match('^ *N *$', self.title): @@ -419,9 +419,9 @@ class MediaItem(MediaResource): class MediaItemRelated(MediaRelated): "Item related media" - + item = ForeignKey('MediaItem', related_name="related", verbose_name=_('item')) - + class Meta(MetaCore): db_table = 'media_item_related' @@ -438,11 +438,11 @@ class MediaItemKeyword(ModelCore): class MediaItemPerformance(ModelCore): "Item performance" - media_item = ForeignKey('MediaItem', related_name="performances", + media_item = ForeignKey('MediaItem', related_name="performances", verbose_name=_('item')) - instrument = WeakForeignKey('Instrument', related_name="performances", + instrument = WeakForeignKey('Instrument', related_name="performances", verbose_name=_('composition')) - alias = WeakForeignKey('InstrumentAlias', related_name="performances", + alias = WeakForeignKey('InstrumentAlias', related_name="performances", verbose_name=_('vernacular name')) instruments_num = CharField(_('number')) musicians = CharField(_('interprets')) @@ -454,25 +454,25 @@ class MediaItemPerformance(ModelCore): class MediaItemAnalysis(ModelCore): "Item analysis result computed by TimeSide" - element_type = 'analysis' + element_type = 'analysis' item = ForeignKey('MediaItem', related_name="analysis", verbose_name=_('item')) analyzer_id = CharField(_('id'), required=True) name = CharField(_('name')) value = CharField(_('value')) unit = CharField(_('unit')) - + class Meta(MetaCore): db_table = 'media_analysis' ordering = ['name'] - + def to_dict(self): if self.analyzer_id == 'duration': if '.' in self.value: value = self.value.split('.') self.value = '.'.join([value[0], value[1][:2]]) return {'id': self.analyzer_id, 'name': self.name, 'value': self.value, 'unit': self.unit} - - + + class MediaPart(MediaResource): "Describe an item part" element_type = 'part' @@ -480,7 +480,7 @@ class MediaPart(MediaResource): title = CharField(_('title'), required=True) start = FloatField(_('start'), required=True) end = FloatField(_('end'), required=True) - + class Meta(MetaCore): db_table = 'media_parts' @@ -500,7 +500,7 @@ class Playlist(ModelCore): def __unicode__(self): return self.title - + class PlaylistResource(ModelCore): "Playlist components" @@ -513,13 +513,13 @@ class PlaylistResource(ModelCore): class Meta(MetaCore): db_table = 'playlist_resources' - - + + class MediaItemMarker(MediaResource): "2D marker object : text value vs. time" - + element_type = 'marker' - + item = ForeignKey('MediaItem', related_name="markers", verbose_name=_('item')) public_id = CharField(_('public_id'), required=True) time = FloatField(_('time')) @@ -527,7 +527,7 @@ class MediaItemMarker(MediaResource): date = DateTimeField(_('date'), auto_now=True) description = TextField(_('description')) author = ForeignKey(User, related_name="markers", verbose_name=_('author')) - + class Meta(MetaCore): db_table = 'media_markers' @@ -540,21 +540,21 @@ class MediaItemMarker(MediaResource): class MediaItemTranscodingFlag(ModelCore): "Item flag to know if the MediaItem has been transcoded to a given format" - + item = ForeignKey('MediaItem', related_name="transcoding", verbose_name=_('item')) mime_type = CharField(_('mime_type'), required=True) date = DateTimeField(_('date'), auto_now=True) value = BooleanField(_('transcoded')) - + class Meta(MetaCore): db_table = 'media_transcoding' - - + + class Search(ModelCore): "Keywork search" - + element_type = 'search' - + username = ForeignKey(User, related_name="searches", db_column="username") keywords = CharField(_('keywords'), required=True) date = DateField(_('date'), auto_now_add=True) @@ -567,13 +567,13 @@ class Search(ModelCore): class DublinCoreToFormatMetadata(object): - """ a mapping class to get item DublinCore metadata dictionaries + """ a mapping class to get item DublinCore metadata dictionaries in various audio metadata format (MP3, OGG, etc...)""" - + #FIXME: should be given by timeside - unavailable_extensions = ['wav', 'aiff', 'aif'] - - metadata_mapping = { + unavailable_extensions = ['wav', 'aiff', 'aif', 'flac'] + + metadata_mapping = { 'mp3' : { 'title': 'TIT2', #title2 'creator': 'TCOM', #composer @@ -584,27 +584,27 @@ class DublinCoreToFormatMetadata(object): 'publisher': 'TPUB', #publisher 'date': 'TDRC', #year # 'coverage': 'COMM', #comment - }, + }, 'ogg': { 'creator': 'artist', - 'relation': 'album', - 'all': 'all', - }, + 'relation': 'album', + 'all': 'all', + }, 'flac': { 'creator': 'artist', - 'relation': 'album', - 'all': 'all', - }, + 'relation': 'album', + 'all': 'all', + }, 'wav': { 'creator': 'artist', - 'relation': 'album', - 'all': 'all', - }, + 'relation': 'album', + 'all': 'all', + }, } - + def __init__(self, format): self.format = format - + def get_metadata(self, dc_metadata): mapp = self.metadata_mapping[self.format] metadata = {} diff --git a/telemeta/web/base.py b/telemeta/web/base.py index 2627ad90..d9fcb6b2 100644 --- a/telemeta/web/base.py +++ b/telemeta/web/base.py @@ -82,10 +82,13 @@ def render(request, template, data = None, mimetype = None): return render_to_response(template, data, context_instance=RequestContext(request), mimetype=mimetype) -def stream_from_processor(__decoder, __processor, __flag): +def stream_from_processor(__decoder, __processor, __flag, metadata=None): while True: - __frames, eodproc = __processor.process(*__decoder.process()) - if eodproc: + __frames, __eodproc = __processor.process(*__decoder.process()) + if __eodproc or not len(__frames): + if metadata: + __processor.set_metadata(metadata) + __processor.write_metadata() __flag.value = True __flag.save() break @@ -875,32 +878,23 @@ class ItemView(object): if mime_type in format: # source > stream if not extension in mapping.unavailable_extensions: - try: - proc = encoder(audio) - proc.set_metadata(metadata) - proc.write_metadata() - except: - pass + proc = encoder(audio) + proc.set_metadata(metadata) + proc.write_metadata() response = HttpResponse(stream_from_file(audio), mimetype = mime_type) else: media = self.cache_export.dir + os.sep + file - if not self.cache_export.exists(file) or flag.value == False: + if not self.cache_export.exists(file) or not flag.value: # source > encoder > stream decoder = timeside.decoder.FileDecoder(audio) decoder.setup() proc = encoder(media, streaming=True) proc.setup(channels=decoder.channels(), samplerate=decoder.samplerate()) - proc.set_metadata(metadata) - response = HttpResponse(stream_from_processor(decoder, proc, flag), mimetype = mime_type) + if extension in mapping.unavailable_extensions: + metadata=None + response = HttpResponse(stream_from_processor(decoder, proc, flag, metadata=metadata), mimetype = mime_type) else: # cache > stream - if not extension in mapping.unavailable_extensions: - try: - proc = encoder(media) - proc.set_metadata(metadata) - proc.write_metadata() - except: - pass response = HttpResponse(self.cache_export.read_stream_bin(file), mimetype = mime_type) response['Content-Disposition'] = 'attachment' -- 2.39.5