source: telemeta/web/base.py @ d281e63

cremcrem2devdev2diademsfeature/breadcrumbsfeature/ts-0.5feature/ts-0.5.4feature/writecacheformagenericinstru_searchlamlam2mapsv3mergenlivemultiproductionrelease/1.4.4sabiodsecurityserversocialstoragetelecastertestvideo
Last change on this file since d281e63 was d281e63, checked in by Guillaume Pellerin <yomguy@…>, 4 years ago

fix file close in cache, add MIME types to analyzers, fix timeside decoder call bug

  • Property mode set to 100644
File size: 21.3 KB
Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2007 Samalyse SARL
3
4# This software is a computer program whose purpose is to backup, analyse,
5# transcode and stream any audio content with its metadata over a web frontend.
6
7# This software is governed by the CeCILL  license under French law and
8# abiding by the rules of distribution of free software.  You can  use,
9# modify and/ or redistribute the software under the terms of the CeCILL
10# license as circulated by CEA, CNRS and INRIA at the following URL
11# "http://www.cecill.info".
12
13# As a counterpart to the access to the source code and  rights to copy,
14# modify and redistribute granted by the license, users are provided only
15# with a limited warranty  and the software's author,  the holder of the
16# economic rights,  and the successive licensors  have only  limited
17# liability.
18
19# In this respect, the user's attention is drawn to the risks associated
20# with loading,  using,  modifying and/or developing or reproducing the
21# software by the user in light of its specific status of free software,
22# that may mean  that it is complicated to manipulate,  and  that  also
23# therefore means  that it is reserved for developers  and  experienced
24# professionals having in-depth computer knowledge. Users are therefore
25# encouraged to load and test the software's suitability as regards their
26# requirements in conditions enabling the security of their systems and/or
27# data to be ensured and,  more generally, to use and operate it in the
28# same conditions as regards security.
29
30# The fact that you are presently reading this means that you have had
31# knowledge of the CeCILL license and that you accept its terms.
32
33# Author: Olivier Guilyardi <olivier@samalyse.com>
34
35import re
36import os
37import sys
38import datetime
39import timeside
40
41from django.template import RequestContext, loader
42from django import template
43from django.http import HttpResponse
44from django.http import Http404
45from django.shortcuts import render_to_response, redirect
46from django.views.generic import list_detail
47from django.conf import settings
48from django.contrib import auth
49from django.contrib.auth.decorators import login_required
50
51from telemeta.models import MediaItem, Location, MediaCollection, EthnicGroup
52from telemeta.models import dublincore, Enumeration
53import telemeta.interop.oai as oai
54from telemeta.interop.oaidatasource import TelemetaOAIDataSource
55from django.core.exceptions import ObjectDoesNotExist
56from telemeta.util.unaccent import unaccent
57from telemeta.util.unaccent import unaccent_icmp
58from telemeta.util.logger import Logger
59from telemeta.cache import TelemetaCache
60import telemeta.web.pages as pages
61
62def render(request, template, data = None, mimetype = None):
63    return render_to_response(template, data, context_instance=RequestContext(request), 
64                              mimetype=mimetype)
65
66def stream_from_processor(decoder, processor):
67    while True:
68        frames,  eod = decoder.process()
69        _chunk,  eod_proc = processor.process(frames, eod)
70        if eod_proc:
71            break
72        yield _chunk
73
74def stream_from_file(file):
75    chunk_size = 0xFFFF
76    f = open(file, 'r')
77    while True:
78        _chunk = f.read(chunk_size)
79        if not len(_chunk):
80            f.close()
81            break
82        yield _chunk
83   
84   
85class WebView(object):
86    """Provide web UI methods"""
87
88    graphers = timeside.core.processors(timeside.api.IGrapher)
89    decoders = timeside.core.processors(timeside.api.IDecoder)
90    encoders= timeside.core.processors(timeside.api.IEncoder)
91    analyzers = timeside.core.processors(timeside.api.IAnalyzer)
92    cache = TelemetaCache(settings.TELEMETA_DATA_CACHE_DIR)
93    cache_export = TelemetaCache(settings.TELEMETA_EXPORT_CACHE_DIR)
94   
95    def index(self, request):
96        """Render the homepage"""
97
98        template = loader.get_template('telemeta/index.html')
99        ids = [id for id in MediaItem.objects.all().values_list('id', flat=True).order_by('?')[0:4]]
100        items = MediaItem.objects.enriched().filter(pk__in=ids)
101
102        context = RequestContext(request, {
103                    'page_content': pages.get_page_content(request, 'parts/home', ignore_slash_issue=True),
104                    'items': items})
105        return HttpResponse(template.render(context))
106
107    def collection_detail(self, request, public_id, template=''):
108        collection = MediaCollection.objects.get(public_id=public_id)
109        return render(request, template, {'collection': collection})
110
111
112    def item_detail(self, request, public_id, template='telemeta/mediaitem_detail.html'):
113        """Show the details of a given item"""
114        item = MediaItem.objects.get(public_id=public_id)
115       
116        formats = []
117        for encoder in self.encoders:
118            formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
119
120        graphers = []
121        for grapher in self.graphers:
122            graphers.append({'name':grapher.name(), 'id': grapher.id()})
123        if request.REQUEST.has_key('grapher_id'):
124            grapher_id = request.REQUEST['grapher_id']
125        else:
126            grapher_id = 'waveform'
127       
128        analyze_file = public_id + '.xml'
129       
130        if self.cache.exists(analyze_file):
131            analyzers = self.cache.read_analyzer_xml(analyze_file)
132            if not item.approx_duration:
133                for analyzer in analyzers:
134                    if analyzer['id'] == 'duration':
135                        value = analyzer['value']
136                        time = value.split(':')
137                        time[2] = time[2].split('.')[0]
138                        time = ':'.join(time)
139                        item.approx_duration = time
140                        item.save()
141        else:
142            analyzers = []
143            analyzers_sub = []
144            if item.file:
145                decoder  = timeside.decoder.FileDecoder(item.file.path)
146                pipe = decoder
147                mime_type = decoder.mimetype
148               
149                for analyzer in self.analyzers:
150                    subpipe = analyzer()
151                    analyzers_sub.append(subpipe)
152                    pipe = pipe | subpipe
153                   
154                pipe.run()
155               
156                for analyzer in analyzers_sub:
157                    value = analyzer.result()
158                    if analyzer.id() == 'duration':
159                        approx_value = int(round(value))
160                        item.approx_duration = approx_value
161                        item.save()
162                        value = datetime.timedelta(0,value)
163                    if analyzer.id() == 'mime_type':
164                        value = decoder.format()
165                       
166                    analyzers.append({'name':analyzer.name(),
167                                      'id':analyzer.id(),
168                                      'unit':analyzer.unit(),
169                                      'value':str(value)})
170               
171            self.cache.write_analyzer_xml(analyzers, analyze_file)
172           
173       
174        return render(request, template, 
175                    {'item': item, 'export_formats': formats, 
176                    'visualizers': graphers, 'visualizer_id': grapher_id,'analysers': analyzers,  #FIXME analysers
177                    'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', True)
178                    })
179
180    def item_analyze(self):
181        pass
182       
183    def item_visualize(self, request, public_id, visualizer_id, width, height):
184        item = MediaItem.objects.get(public_id=public_id)
185        mime_type = 'image/png'
186        grapher_id = visualizer_id
187       
188        for grapher in self.graphers:
189            if grapher.id() == grapher_id:
190                break
191
192        if grapher.id() != grapher_id:
193            raise Http404
194       
195        size = width + '_' + height
196        image_file = '.'.join([public_id, grapher_id, size, 'png'])
197
198        if not self.cache.exists(image_file):
199            if item.file:
200                decoder  = timeside.decoder.FileDecoder(item.file.path)
201                graph = grapher(width = int(width), height = int(height))
202                pipe = decoder | graph
203                pipe.run()
204                graph.render(self.cache.dir + os.sep + image_file)
205               
206        response = HttpResponse(self.cache.read_stream_bin(image_file), mimetype = mime_type)
207        return response
208
209    def list_export_extensions(self):
210        "Return the recognized item export file extensions, as a list"
211        list = []
212        for encoder in self.encoders:
213            list.append(encoder.file_extension())
214        return list
215
216    def item_export(self, request, public_id, extension):                   
217        """Export a given media item in the specified format (OGG, FLAC, ...)"""
218
219        if extension != 'mp3' and not getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', False):
220            raise Http404 # FIXME: should be some sort of permissions denied error
221
222        for encoder in self.encoders:
223            if encoder.file_extension() == extension:
224                break
225
226        if encoder.file_extension() != extension:
227            raise Http404('Unknown export file extension: %s' % extension)
228
229        mime_type = encoder.mime_type()
230        file = public_id + '.' + encoder.file_extension()
231        item = MediaItem.objects.get(public_id=public_id)
232        audio = item.file.path
233        decoder = timeside.decoder.FileDecoder(audio)
234
235        if decoder.format() == mime_type:
236            # source > stream
237            response = HttpResponse(stream_from_file(audio), mimetype = mime_type)
238           
239        else:       
240            if not self.cache_export.exists(file):
241                # source > encoder > stream
242                media = self.cache_export.dir + os.sep + file
243                proc = encoder(media)
244#                metadata = dublincore.express_item(item).to_list()
245#                enc.set_metadata(metadata)
246                pipe = decoder | proc
247                pipe.run()
248                response = HttpResponse(stream_from_processor(decoder, proc), mimetype = mime_type)
249            else:
250                response = HttpResponse(self.cache_export.read_stream_bin(file), mimetype = mime_type)
251       
252        response['Content-Disposition'] = 'attachment'
253        return response
254
255    def edit_search(self, request, criteria=None):
256        year_min, year_max = MediaCollection.objects.all().recording_year_range()
257        rec_years = year_min and year_max and range(year_min, year_max + 1) or []
258        year_min, year_max = MediaCollection.objects.all().publishing_year_range()
259        pub_years = year_min and year_max and range(year_min, year_max + 1) or []
260        return render(request, 'telemeta/search_criteria.html', {
261            'rec_years': rec_years,
262            'pub_years': pub_years,
263            'ethnic_groups': MediaItem.objects.all().ethnic_groups(),
264            'criteria': criteria
265        })
266
267    def complete_location(self, request, with_items=True):
268        input = request.REQUEST
269       
270        token = input['q']
271        limit = int(input['limit'])
272        if with_items:
273            locations = MediaItem.objects.all().locations()
274        else:
275            locations = Location.objects.all()
276
277        locations = locations.filter(name__istartswith=token).order_by('name')[:limit]
278        data = [unicode(l) + " (%d items)" % l.items().count() for l in locations]
279
280        return HttpResponse("\n".join(data))
281
282    def search(self, request, type = None):
283        """Perform a search through collections and items metadata"""
284        collections = MediaCollection.objects.enriched()
285        items = MediaItem.objects.enriched()
286        input = request.REQUEST
287        criteria = {}
288
289        switch = {
290            'pattern': lambda value: ( 
291                collections.quick_search(value), 
292                items.quick_search(value)),
293            'title': lambda value: (
294                collections.word_search('title', value), 
295                items.by_title(value)),
296            'location': lambda value: (
297                collections.by_location(Location.objects.get(name=value)), 
298                items.by_location(Location.objects.get(name=value))),
299            'continent': lambda value: (
300                collections.by_continent(value), 
301                items.filter(continent = value)),
302            'ethnic_group': lambda value: (
303                collections.by_ethnic_group(value), 
304                items.filter(ethnic_group = value),
305                EthnicGroup.objects.get(pk=value)),
306            'creator': lambda value: (
307                collections.word_search('creator', value),
308                items.word_search('collection__creator', value)),
309            'collector': lambda value: (
310                collections.by_fuzzy_collector(value),
311                items.by_fuzzy_collector(value)),
312            'rec_year_from': lambda value: (
313                collections.by_recording_year(int(value), int(input.get('rec_year_to', value))), 
314                items.by_recording_date(datetime.date(int(value), 1, 1), 
315                                        datetime.date(int(input.get('rec_year_to', value)), 12, 31))),
316            'rec_year_to': lambda value: (collections, items),
317            'pub_year_from': lambda value: (
318                collections.by_publish_year(int(value), int(input.get('pub_year_to', value))), 
319                items.by_publish_year(int(value), int(input.get('pub_year_to', value)))),
320            'pub_year_to': lambda value: (collections, items),
321        }
322       
323        for key, value in input.items():
324            func = switch.get(key)
325            if func and value and value != "0":
326                try:
327                    res = func(value)
328                    if len(res) > 2:
329                        collections, items, value = res
330                    else: 
331                        collections, items = res
332                except ObjectDoesNotExist:
333                    collections = collections.none()
334                    items = items.none()
335
336                criteria[key] = value
337
338        if type is None:
339            if collections.count() and not items.count():
340                type = 'collections'
341            else:
342                type = 'items'
343
344        if type == 'items':
345            objects = items
346        else:
347            objects = collections
348
349        return list_detail.object_list(request, objects, 
350            template_name='telemeta/search_results.html', paginate_by=20,
351            extra_context={'criteria': criteria, 'collections_num': collections.count(), 
352                'items_num': items.count(), 'type' : type})
353
354    def __get_enumerations_list(self):
355        from django.db.models import get_models
356        models = get_models(telemeta.models)
357
358        enumerations = []
359        for model in models:
360            if issubclass(model, Enumeration):
361                enumerations.append({"name": model._meta.verbose_name, 
362                    "id": model._meta.module_name})
363
364        cmp = lambda obj1, obj2: unaccent_icmp(obj1['name'], obj2['name'])
365        enumerations.sort(cmp)
366        return enumerations                   
367   
368    def __get_admin_context_vars(self):
369        return {"enumerations": self.__get_enumerations_list()}
370
371    @login_required
372    def admin_index(self, request):
373        return render(request, 'telemeta/admin.html', self. __get_admin_context_vars())
374
375    def __get_enumeration(self, id):
376        from django.db.models import get_models
377        models = get_models(telemeta.models)
378        for model in models:
379            if model._meta.module_name == id:
380                break
381
382        if model._meta.module_name != id:
383            return None
384
385        return model
386
387    @login_required
388    def edit_enumeration(self, request, enumeration_id):       
389
390        enumeration  = self.__get_enumeration(enumeration_id)
391        if enumeration == None:
392            raise Http404
393
394        vars = self.__get_admin_context_vars()
395        vars["enumeration_id"] = enumeration._meta.module_name
396        vars["enumeration_name"] = enumeration._meta.verbose_name           
397        vars["enumeration_values"] = enumeration.objects.all()
398        return render(request, 'telemeta/enumeration_edit.html', vars)
399
400    @login_required
401    def add_to_enumeration(self, request, enumeration_id):       
402
403        enumeration  = self.__get_enumeration(enumeration_id)
404        if enumeration == None:
405            raise Http404
406
407        enumeration_value = enumeration(value=request.POST['value'])
408        enumeration_value.save()
409
410        return self.edit_enumeration(request, enumeration_id)
411
412    @login_required
413    def update_enumeration(self, request, enumeration_id):       
414       
415        enumeration  = self.__get_enumeration(enumeration_id)
416        if enumeration == None:
417            raise Http404
418
419        if request.POST.has_key("remove"):
420            enumeration.objects.filter(id__in=request.POST.getlist('sel')).delete()
421
422        return self.edit_enumeration(request, enumeration_id)
423
424    @login_required
425    def edit_enumeration_value(self, request, enumeration_id, value_id):       
426
427        enumeration  = self.__get_enumeration(enumeration_id)
428        if enumeration == None:
429            raise Http404
430       
431        vars = self.__get_admin_context_vars()
432        vars["enumeration_id"] = enumeration._meta.module_name
433        vars["enumeration_name"] = enumeration._meta.verbose_name           
434        vars["enumeration_record"] = enumeration.objects.get(id__exact=value_id)
435        return render(request, 'telemeta/enumeration_edit_value.html', vars)
436
437    @login_required
438    def update_enumeration_value(self, request, enumeration_id, value_id):       
439
440        if request.POST.has_key("save"):
441            enumeration  = self.__get_enumeration(enumeration_id)
442            if enumeration == None:
443                raise Http404
444       
445            record = enumeration.objects.get(id__exact=value_id)
446            record.value = request.POST["value"]
447            record.save()
448
449        return self.edit_enumeration(request, enumeration_id)
450 
451    def collection_playlist(self, request, public_id, template, mimetype):
452        try:
453            collection = MediaCollection.objects.get(public_id=public_id)
454        except ObjectDoesNotExist:
455            raise Http404
456
457        template = loader.get_template(template)
458        context = RequestContext(request, {'collection': collection, 'host': request.META['HTTP_HOST']})
459        return HttpResponse(template.render(context), mimetype=mimetype)
460
461    def item_playlist(self, request, public_id, template, mimetype):
462        try:
463            item = MediaItem.objects.get(public_id=public_id)
464        except ObjectDoesNotExist:
465            raise Http404
466
467        template = loader.get_template(template)
468        context = RequestContext(request, {'item': item, 'host': request.META['HTTP_HOST']})
469        return HttpResponse(template.render(context), mimetype=mimetype)
470
471    def list_continents(self, request):
472        continents = MediaItem.objects.all().countries(group_by_continent=True)
473        return render(request, 'telemeta/geo_continents.html', 
474                    {'continents': continents, 'gmap_key': settings.TELEMETA_GMAP_KEY })
475
476    def country_info(self, request, id):
477        country = Location.objects.get(pk=id)
478        return render(request, 'telemeta/country_info.html', {
479            'country': country, 'continent': country.continents()[0]})
480
481    def list_countries(self, request, continent):                   
482        continent = Location.objects.by_flatname(continent)[0]
483        countries = MediaItem.objects.by_location(continent).countries()
484
485        return render(request, 'telemeta/geo_countries.html', {
486            'continent': continent,
487            'countries': countries
488        })
489
490    def list_country_collections(self, request, continent, country):
491        continent = Location.objects.by_flatname(continent)[0]
492        country = Location.objects.by_flatname(country)[0]
493        objects = MediaCollection.objects.enriched().by_location(country)
494        return list_detail.object_list(request, objects, 
495            template_name='telemeta/geo_country_collections.html', paginate_by=20,
496            extra_context={'country': country, 'continent': continent})
497
498    def list_country_items(self, request, continent, country):
499        continent = Location.objects.by_flatname(continent)[0]
500        country = Location.objects.by_flatname(country)[0]
501        objects = MediaItem.objects.enriched().by_location(country)
502        return list_detail.object_list(request, objects, 
503            template_name='telemeta/geo_country_items.html', paginate_by=20,
504            extra_context={'country': country, 'continent': continent})
505
506    def handle_oai_request(self, request):
507        url         = 'http://' + request.META['HTTP_HOST'] + request.path
508        datasource  = TelemetaOAIDataSource()
509        admin       = settings.ADMINS[0][1]
510        provider    = oai.DataProvider(datasource, "Telemeta", url, admin)
511        args        = request.GET.copy()
512        args.update(request.POST)
513        return HttpResponse(provider.handle(args), mimetype='text/xml')
514       
515    def render_flatpage(self, request, path):
516        try:
517            content = pages.get_page_content(request, path)
518        except pages.MalformedPagePath:
519            return redirect(request.path + '/')
520
521        if isinstance(content, pages.PageAttachment):
522            return HttpResponse(content, content.mimetype())
523        else:
524            return render(request, 'telemeta/flatpage.html', {'page_content': content })
525
526    def logout(self, request):
527        auth.logout(request)
528        return redirect('telemeta-home')
Note: See TracBrowser for help on using the repository browser.