def delete(self, id): from auth import get_profile atts = get_profile() if atts['superuser']: self.bundle = self.model.find_one({'_id': str(id)}) result = self.delete_obj() if not result: return result else: for location in self.bundle['@graph']['ma:locator']: basename = location['@id'] duplicates = self.model.find_one({ "@graph.ma:locator": { "$elemMatch": { "@id": basename } } }) if duplicates is not None: return result extension = location['ma:hasFormat'].split('/')[-1] filename = "{0}.{1}".format(basename, extension) try: remove(config.MEDIA_DIRECTORY + filename) except (IOError, OSError): pass return result else: return action_401()
def delete_subtitle(self, filename): from os import remove from helpers import endpoint_404 query = { "@graph.ma:hasRelatedResource": { "$elemMatch": { "@id": filename } } } bundle = self.model.find_one(query) if bundle is None: return endpoint_404() if not self.acl_write_check(bundle): return action_401() l = bundle.get("@graph").get("ma:hasRelatedResource") l[:] = [d for d in l if d.get('@id') != filename] bundle.save() remove(config.SUBTITLE_DIRECTORY + filename) return mongo_jsonify(bundle)
def videoCreationBatch(): from auth import get_user, is_superuser if not is_superuser(): return action_401() if request.method=="GET": chdir(config.INGEST_DIRECTORY) files=[f for f in listdir(getcwd()) if f[0] != '.'] return json.dumps(files) else: from PIL import Image from shutil import move from helpers import getVideoInfo packet=request.json for up in packet: filepath=unicode(config.INGEST_DIRECTORY + up['filepath']) new_file=unicode(config.MEDIA_DIRECTORY + up['id'] + ".mp4") if path.isfile(new_file): return bundle_400("That file already exists; try another unique ID.") if path.isfile(filepath.encode('utf-8')): md=getVideoInfo(filepath.encode('utf-8')) poster = config.POSTERS_DIRECTORY + "%s.jpg" % (up["id"]) thumb = config.POSTERS_DIRECTORY + "%s_thumb.jpg" % (up["id"]) move(filepath.encode('utf-8'), new_file.encode('utf-8')) assets.update({"_id":up["pid"]},{"$set":{ "@graph.ma:frameRate":float(md["framerate"]), "@graph.ma:averageBitRate":int(float(md["bitrate"])), "@graph.ma:frameWidth":int(md["width"]), "@graph.ma:frameHeight":int(md["height"]), "@graph.ma:duration":int( round(float(md["duration"])) )/60, "@graph.ma:locator": [ { "@id": up["id"], "ma:hasFormat": "video/mp4", "ma:hasCompression": {"@id":"http://www.freebase.com/view/en/h_264_mpeg_4_avc","name": "avc.42E01E"} }, { "@id": up["id"], "ma:hasFormat": "video/webm", "ma:hasCompression": {"@id":"http://www.freebase.com/m/0c02yk5","name":"vp8.0"} } ] }}) imgcmd = "avconv -i '%s' -q:v 1 -r 1 -t 00:00:01 -ss 00:00:30 -f image2 '%s'" % (new_file,poster) system(imgcmd.encode('utf-8')) chmod(poster,0775) im=Image.open(poster) im.thumbnail((160,90)) im.save(thumb) chmod(thumb,0775) if not app.config.get('TESTING'): from gearman import GearmanClient client = GearmanClient(config.GEARMAN_SERVERS) client.submit_job("generate_webm", str(up["id"])) else: from ingest import generate_webm result = generate_webm(file_id=up['id']) if result == "ERROR": raise Exception("Could not convert media file.") return "Success"
def videoMembershipBatch(): status = {} packet = request.json # ensure the user has write access to the collection for up in packet: bundle = ags.find_one({'_id': str(up['collection']['id'])}) group = AssetGroup(request=request) if not group.acl_write_check(bundle): return action_401() for up in packet: status[up['collection']['id']] = assets.update( {'@graph.pid': { '$in': up['videos'] }}, { '$addToSet': { "@graph.ma:isMemberOf": { '@id': up['collection']['id'], 'title': up['collection']['id'] } } }, multi=True) return jsonify(status)
def videoCreationBatch(): from auth import get_user, is_superuser if not is_superuser(): return action_401() if request.method=="GET": chdir(config.INGEST_DIRECTORY) files=[f for f in listdir(getcwd()) if f[0] != '.'] return json.dumps(files) else: packet=request.json ids = [up.get('id') for up in packet] if len(set(ids)) is not len(ids): return bundle_400("Duplicate IDs found. Cannot update." % s) # test everything before we start ingesting, otherwise half the batch # might get completed before we get an error. easier to just reject everything # than half of everything for up in packet: i = up['id'] dupe = assets.find_one({'@graph.ma:locator': {'$elemMatch': {'@id': i}}}) mp4 = (unicode(config.MEDIA_DIRECTORY) + i + '.mp4').encode('utf-8') webm = (unicode(config.MEDIA_DIRECTORY) + i + '.webm').encode('utf-8') if path.isfile(mp4) or path.isfile(webm) or dupe is not None: return bundle_400("That file (%s) already exists; try another unique ID." %i) for up in packet: result = MediaAsset.set_new_file(up['pid'], up['id'], up['filepath']) if not result[0]: return bundle_400(result[1]) return "Success"
def get(self,id,limit=0): q=self.set_query() if id: try: q['_id']=ObjectId(str(id)) except Exception as e: return bundle_400("The ID you submitted is malformed.") if "enrollments" in self.request.args: info = self.get_bundle(q) en = self.get_enrollments(info['username'], info['userid']) en.update({"username": info['username'], '_id': q['_id']}) self.bundle = en else: self.bundle=self.get_bundle(q) if self.bundle: self.bundle=self.auth_filter(self.bundle) if not self.bundle: return action_401() self.set_resource() return self.serialize_bundle(self.bundle) else: return bundle_404() else: self.bundle=self.collection.find(q).limit(limit) return self.get_list()
def post(self, id=None): # Verify that a user is logged in. from auth import get_profile userid = get_profile()['userid'] if not userid: return action_401() data = self.request.get_json() # Require clips to have a start time. # End time is optional. if 'start' not in data: return bundle_400('clips must have a start time!') # Validate media id. if 'mediaid' in data: query = {'_id': data['mediaid']} if not assets.find_one(query): return bundle_400('invalid media id!') else: return bundle_400('clips must have a valid media id!') # Populate the new object with all our data. self.bundle = self.model() self.set_attrs() self.bundle['userid'] = userid self.bundle['_id'] = str(ObjectId()) return self.save_bundle()
def audioCreationBatch(): import mutagen import uuid import os from werkzeug.utils import secure_filename from auth import is_superuser if not is_superuser(): return action_401() files = request.files.getlist('audio[]') if not len(files): return bundle_400("Missing form field 'audio[]'") incompatible = filter(lambda x: not x.filename.endswith('mp3'), files) if len(incompatible): return bundle_400("Only MP3 files are supported.") results = [] for f in files: ext = f.filename.split('.')[-1] filename = secure_filename(f.filename.split('.', 1)[0]) \ + str(uuid.uuid4()) + '.' + ext path = os.path.join(config.MEDIA_DIRECTORY, filename) f.save(path) id3 = mutagen.File(path, easy=True) # metadata _id = str(ObjectId()) audio = assets.Video() audio['_id'] = _id audio['@graph']['dc:type'] = 'hummedia:type/humaudio' audio['@graph']['pid'] = _id audio['@graph']['ma:title'] = id3.get('title', [f.filename])[0] audio['@graph']['ma:hasContributor'] = [{ '@id': '', 'name': x } for x in id3.get('artist', [])] try: audio['@graph']["ma:date"] = int(id3.get('date')[0]) except: audio['@graph'][ 'ma:date'] = 1970 # TODO: this requires a number, but I don't have one for it audio['@graph']["ma:locator"] = [{ "@id": '.'.join(filename.split('.')[0:-1]), "ma:hasFormat": "audio/" + ext, "ma:hasCompression": {} }] audio.save() audio['@graph']['ma:locator'][0]['@id'] += '.mp3' results.append(audio['@graph']) return mongo_jsonify(results)
def audioCreationBatch(): import mutagen import uuid import os from werkzeug.utils import secure_filename from auth import is_superuser if not is_superuser(): return action_401() files = request.files.getlist('audio[]') if not len(files): return bundle_400("Missing form field 'audio[]'") incompatible = filter(lambda x: not x.filename.endswith('mp3'), files) if len(incompatible): return bundle_400("Only MP3 files are supported.") results = [] for f in files: ext = f.filename.split('.')[-1] filename = secure_filename(f.filename.split('.', 1)[0]) \ + str(uuid.uuid4()) + '.' + ext path = os.path.join(config.MEDIA_DIRECTORY, filename) f.save(path) id3 = mutagen.File(path, easy=True) # metadata _id = str(ObjectId()) audio = assets.Video() audio['_id'] = _id audio['@graph']['dc:type'] = 'hummedia:type/humaudio' audio['@graph']['pid'] = _id audio['@graph']['ma:title'] = id3.get('title',[f.filename])[0] audio['@graph']['ma:hasContributor'] = [{'@id': '', 'name': x} for x in id3.get('artist',[])] try: audio['@graph']["ma:date"] = int(id3.get('date')[0]) except: audio['@graph']['ma:date'] = 1970 # TODO: this requires a number, but I don't have one for it audio['@graph']["ma:locator"] = [ { "@id": '.'.join(filename.split('.')[0:-1]), "ma:hasFormat": "audio/" + ext, "ma:hasCompression": {} } ] audio.save() audio['@graph']['ma:locator'][0]['@id'] += '.mp3' results.append(audio['@graph']) return mongo_jsonify(results)
def delete(self, id): # Verify that a user is logged in. from auth import get_profile userid = get_profile()['userid'] if not userid: return action_401() query = {'_id': id, 'userid': userid} result = self.collection.delete_one(query) return jsonify({'success': result.deleted_count == 1})
def get_list(self): # Verify that a user is logged in. from auth import get_profile userid = get_profile()['userid'] if not userid: return action_401() query = {'userid': userid} results = self.collection.find(query) return mongo_jsonify(list(results))
def videoMembershipBatch(): status={} packet=request.json # ensure the user has write access to the collection for up in packet: bundle = ags.find_one({'_id': str(up['collection']['id'])}) group = AssetGroup(request=request) if not group.acl_write_check(bundle): return action_401() for up in packet: status[up['collection']['id']]=assets.update({'@graph.pid':{'$in':up['videos']}}, {'$addToSet':{"@graph.ma:isMemberOf":{'@id':up['collection']['id'],'title':up['collection']['id']}}},multi=True) return jsonify(status)
def get(self, id): if not id: return self.get_list() else: from auth import get_profile userid = get_profile()['userid'] if not userid: return action_401() query = {'_id': str(id), 'userid': userid} bundle = self.get_bundle(query) if bundle: return self.serialize_bundle(bundle) else: return bundle_404()
def get_expired(self, limit=0): '''Find any expired media and show why they are considered expired.''' # Only superusers should be able to review this report. from auth import get_profile atts = get_profile() if not atts['superuser']: return action_401() current_year = datetime.utcnow().year stale_date = datetime.utcnow() - timedelta(days=730) stale_str = datetime.strftime(stale_date, '%Y-%m-%d') not_hlr_query = {'$or': [{'@graph.dc:hlr': {'$exists': False}}, {'@graph.dc:hlr': False}]} exp_date_query = {'$and': [not_hlr_query, {'@graph.dc:expirationdate': {'$lt': current_year}}]} stale_query = {'$and': [not_hlr_query, {'@graph.dc:lastviewed': {'$lt': stale_str}}]} never_viewed_query = {'$and': [not_hlr_query, {'@graph.dc.lastviewed': {'$exists': False}}]} exp_date = [v for v in self.model.find(exp_date_query).limit(limit)] stale = [v for v in self.model.find(stale_query).limit(limit)] never_viewed = [v for v in self.model.find(never_viewed_query).limit(limit)] title = 'Expired Media' body = '<h1>%s Media</h1>' % title if len(exp_date) > 0: exp_date_fields = ['ma:title', 'dc:creator', 'dc:expirationdate', 'ma:isMemberOf'] exp_date_headings = ['Title', 'Owner', 'Expiration Date', 'Collections'] exp_date_table = build_html_table(exp_date, exp_date_fields, exp_date_headings) body += '<h2>%s</h2>%s' % ('Past Expiration Date', exp_date_table) if len(stale) > 0: stale_fields = ['ma:title', 'dc:creator', 'dc:date', 'dc:lastviewed', 'ma:isMemberOf'] stale_headings = ['Title', 'Owner', 'Upload Date', 'Last Viewed', 'Collections'] stale_table = build_html_table(stale, stale_fields, stale_headings) body += '<h2>%s</h2>%s' % ('Not Viewed Recently', stale_table) if len(never_viewed) > 0: never_viewed_fields = ['ma:title', 'dc:creator', 'dc:date', 'ma:isMemberOf'] never_viewed_headings = ['Title', 'Owner', 'Upload Date', 'Collections'] never_viewed_table = build_html_table(never_viewed, never_viewed_fields, never_viewed_headings) body += '<h2>%s</h2>%s' % ('Never Viewed', never_viewed_table) html = '<html><head><title>%s</title></head><body>%s</body></html>' % (title, body) return Response(html, status=200, mimetype="text/html")
def post(self,pid=None): if self.acl_write_check(): self.bundle=self.model() if not pid: if "username" in self.request.json: self.bundle["_id"]=str(self.request.json.username) else: return bundle_400() else: self.bundle["_id"]=str(ObjectId(pid)) self.preprocess_bundle() setattrs=self.set_attrs() if setattrs.get("resp")==200: return self.save_bundle() else: return bundle_400(setattrs.get("msg")) else: return action_401()
def update_subtitle(self, filename, new_file): from helpers import endpoint_404 query = { "@graph.ma:hasRelatedResource":{"$elemMatch": {"@id":filename}} } bundle = self.model.find_one(query) if bundle is None: return endpoint_404() if not self.acl_write_check(bundle): return action_401() new_file.save(config.SUBTITLE_DIRECTORY + filename) return mongo_jsonify(bundle)
def patch(self, id): from auth import get_profile atts = get_profile() if self.request.json is None or 'replacement_file' not in self.request.json: return super(MediaAsset, self).patch(id) if not atts['superuser']: return action_401() found = assets.find_one({'_id': id}) if not found: return bundle_404() file_id = None to_delete = [] # find existing filenames for f in found['@graph']['ma:locator']: if file_id is not None and f['@id'] != file_id: raise Exception( "Cannot replace file; multiple files with different IDs") file_id = f['@id'] extension = f['ma:hasFormat'].split('/')[-1] fpath = config.MEDIA_DIRECTORY + f['@id'] + '.' + extension to_delete.append(fpath) from os import remove for f in to_delete: try: remove(f) except OSError: pass assets.update({'_id': id}, {'$set': {'@graph.ma:locator': []}}) result = self.set_new_file(id, file_id, self.request.json['replacement_file']) if not result[0]: return bundle_400(result[1]) return self.serialize_bundle(assets.find_one({'_id': id}))
def get(self,id,limit=0): q=self.set_query() if id: try: q['_id']=ObjectId(str(id)) except Exception as e: return bundle_400("The ID you submitted is malformed.") self.bundle=self.get_bundle(q) if self.bundle: self.bundle=self.auth_filter(self.bundle) if not self.bundle: return action_401() self.set_resource() return self.serialize_bundle(self.bundle) else: return bundle_404() else: self.bundle=self.collection.find(q).limit(limit) return self.get_list()
def patch(self, id): from auth import get_profile atts=get_profile() if self.request.json is None or 'replacement_file' not in self.request.json: return super(MediaAsset, self).patch(id) if not atts['superuser']: return action_401() found = assets.find_one({'_id': id}) if not found: return bundle_404() file_id = None to_delete = [] # find existing filenames for f in found['@graph']['ma:locator']: if file_id is not None and f['@id'] != file_id: raise Exception("Cannot replace file; multiple files with different IDs") file_id = f['@id'] extension = f['ma:hasFormat'].split('/')[-1] fpath = config.MEDIA_DIRECTORY + f['@id'] + '.' + extension to_delete.append(fpath) from os import remove for f in to_delete: try: remove(f) except OSError: pass assets.update({'_id': id}, {'$set': {'@graph.ma:locator': []}}) result = self.set_new_file(id, file_id, self.request.json['replacement_file']) if not result[0]: return bundle_400(result[1]) return self.serialize_bundle(assets.find_one({'_id': id}))
def update_lastview(self, id, limit=0): '''Update the last view time if the 'view' url parameter is used.''' self.bundle = self.model.find_one({'$or': [{'_id': str(id)}, {'_id': ObjectId(id)}]}) if self.bundle: self.bundle = self.auth_filter(self.bundle) if not self.bundle: return action_401() self.bundle[u'@graph'][u'dc:lastviewed'] = datetime.utcnow() self.set_attrs() # Sometimes in the development testing database the ma:duration filed is missing. # This causes an error when the lastview field is updated. # This eats the error, which is probably not a best practice but makes the user # experience better. from mongokit.schema_document import RequireFieldError try: self.bundle.save() except RequireFieldError: return mongo_jsonify({"resp":200}) return mongo_jsonify({"resp":200}) else: return bundle_400('The ID you submitted is malformed.')
def delete_subtitle(self, filename): from os import remove from helpers import endpoint_404 query = { "@graph.ma:hasRelatedResource":{"$elemMatch": {"@id":filename}} } bundle = self.model.find_one(query) if bundle is None: return endpoint_404() if not self.acl_write_check(bundle): return action_401() l = bundle.get("@graph").get("ma:hasRelatedResource") l[:] = [d for d in l if d.get('@id') != filename] bundle.save() remove(config.SUBTITLE_DIRECTORY + filename) return mongo_jsonify(bundle)
def delete(self,id): from auth import get_profile atts=get_profile() if atts['superuser']: self.bundle=self.model.find_one({'_id': str(id)}) result = self.delete_obj() if not result: return result else: for location in self.bundle['@graph']['ma:locator']: basename = location['@id'] duplicates = self.model.find_one({"@graph.ma:locator": {"$elemMatch": {"@id": basename}}}) if duplicates is not None: return result extension = location['ma:hasFormat'].split('/')[-1] filename = "{0}.{1}".format(basename, extension) try: remove(config.MEDIA_DIRECTORY + filename) except IOError: pass return result else: return action_401()