diff --git a/db_example.sql b/db_example.sql index a65c8568a035a2d750e790e9e99e8e6b4ac60ff2..3c1c2fa09363b4d0147e72c60cd470a542da08c0 100644 --- a/db_example.sql +++ b/db_example.sql @@ -14303,13 +14303,13 @@ INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (1,'SDV','sd,pal','720x576','4:3',1,0,'video/mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (2,'SDV','ws,palws','720x576','16:9',1,0,'video/mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (3,'HDV','hdv','1440x1080','16:9',9,6,'video/mp4'); -INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (4,'1080p','1080p','1920x1080','16:9',10,7,'video/mp4'); -INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (5,'720p','720p','1280x720','16:9',5,10,'video/mp4'); +INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`,`options`,`suffix`) VALUES (4,'1080p','1080p','1920x1080','16:9',10,7,'video/mp4','{"format": "mp4", "options": {"movflags": "faststart"}, "streams": [{"name": "video", "codec": "libx264", "options": {"b": 2000000, "g": 125, "profile": "high", "preset": "veryslow"}, "filters": ["scale=-1:1080,fps=fps=25"]}, {"name": "audio", "codec": "aac", "options": {"b": 128000}, "filters": ["aformat=sample_rates=48000Hz"]}]}','-1080p.mp4'); +INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`,`options`,`suffix`) VALUES (5,'720p','720p','1280x720','16:9',5,10,'video/mp4','{"format": "mp4", "options": {"movflags": "faststart"}, "streams": [{"name": "video", "codec": "libx264", "options": {"b": 1000000, "g": 125, "profile": "main", "preset": "veryslow"}, "filters": ["scale=-1:720,fps=fps=25"]}, {"name": "audio", "codec": "aac", "options": {"b": 128000}, "filters": ["aformat=sample_rates=48000Hz"]}]}','-720p.mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (6,'640x512','','640x512','16:9',-1,0,'video/mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (7,'MP3','mp3','','',-10,0,'video/mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (8,'720p F4V','','1280x720','16:9',4,10,'video/mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (9,'480p','ipod,480p','640x480','4:3',2,8,'video/mp4'); -INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (10,'360p','ipod,360p','640x360','16:9',3,8,'video/mp4'); +INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`,`options`,`suffix`) VALUES (10,'360p','ipod,360p','640x360','16:9',3,8,'video/mp4','{"format": "mp4", "options": {"movflags": "faststart"}, "streams": [{"name": "video", "codec": "libx264", "options": {"b": 500000, "g": 125, "profile": "baseline", "preset": "veryslow"}, "filters": ["scale=-1:360,fps=fps=25"]}, {"name": "audio", "codec": "aac", "options": {"b": 128000}, "filters": ["aformat=sample_rates=48000Hz"]}]}','-360p.mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (11,'AAC','','','',-2,0,'video/mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (12,'270p','ipodsmall,270p','480x270','16:9',1,3,'video/mp4'); INSERT INTO `formats` (`id`,`description`,`keywords`,`resolution`,`aspect`,`prio`,`player_prio`,`mimetype`) VALUES (13,'Screencast','','1024x768','4:3',4,5,'video/mp4'); @@ -14643,4 +14643,7 @@ INSERT INTO `areas` (`area`,`abbreviation`,`default`,`rank`,`coordinates`) VALUE INSERT INTO `areas` (`area`,`abbreviation`,`default`,`rank`,`coordinates`) VALUES ('Melaten','Melaten',1,1,'50.7819866,6.0486125'); INSERT INTO `areas` (`area`,`abbreviation`,`default`,`rank`,`coordinates`) VALUES ('Posthof','Posthof',0,NULL,'50.7702924,6.086709'); INSERT INTO `areas` (`area`,`abbreviation`,`default`,`rank`,`coordinates`) VALUES ('Templergraben','Templer',1,4,'50.7777117,6.0756922'); +INSERT INTO `profiles` (`name`,`format`) VALUES ('default',4); +INSERT INTO `profiles` (`name`,`format`) VALUES ('default',5); +INSERT INTO `profiles` (`name`,`format`) VALUES ('default',10); COMMIT; diff --git a/db_schema.sql b/db_schema.sql index 3dedf8086bbb4e8cfa872a36be0ffac500a999a0..b5f8a4dc1f02c11c1eccc25de5fffc72d3b06096 100644 --- a/db_schema.sql +++ b/db_schema.sql @@ -83,7 +83,8 @@ CREATE TABLE IF NOT EXISTS `formats` ( `prio` INTEGER NOT NULL DEFAULT '0', `player_prio` INTEGER NOT NULL DEFAULT '0', `mimetype` varchar(32) NOT NULL, - `options` text + `options` text, + `suffix` varchar(32) ); CREATE TABLE IF NOT EXISTS `lectures_data` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, @@ -138,8 +139,9 @@ CREATE TABLE IF NOT EXISTS `sources` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `lecture_id` INTEGER NOT NULL, `path` text NOT NULL, - `profile` text NOT NULL, - `hash` varchar(32) NOT NULL + `type` text NOT NULL, + `hash` varchar(32) NOT NULL, + `time_created` datetime NOT NULL ); CREATE TABLE IF NOT EXISTS `log` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, diff --git a/encoding.py b/encoding.py index 51cee1712016c77442d4c77fc74313e1f4023882..d0a40aee4ef001490e24a086cdd25c656381742a 100644 --- a/encoding.py +++ b/encoding.py @@ -1,22 +1,90 @@ from server import * +import os.path -def schedule_remux(lectureid): - lecture = query('SELECT * FROM lectures WHERE id = ?', lectureid)[0] - course = query('SELECT * FROM courses WHERE id = ?', lecture['course_id'])[0] - chapters = query('SELECT text, time FROM chapters WHERE lecture_id = ?', lectureid) - videos = query('SELECT videos.*, sources.path AS srcpath, sources.hash AS srchash FROM videos JOIN sources ON videos.source = sources.id WHERE videos.lecture_id = ?', lectureid) +def set_metadata(dest, course, lecture): + chapters = query('SELECT text, time FROM chapters WHERE lecture_id = ? AND visible', lecture['id']) metadata = {'title': lecture['title'], 'album': course['title'], 'description': lecture['comment'], 'date': lecture['time'].strftime('%m/%d/%Y'), 'artist': lecture['speaker'] if lecture['speaker'] else course['organizer']} + dest['metadata'] = metadata + dest['chapters'] = chapters + +def schedule_remux(lectureid): + lecture = query('SELECT * FROM lectures WHERE id = ?', lectureid)[0] + course = query('SELECT * FROM courses WHERE id = ?', lecture['course_id'])[0] + videos = query('''SELECT videos.*, sources.path AS srcpath, sources.hash AS srchash, formats.options AS fmtopts + FROM videos + JOIN sources ON videos.source = sources.id + JOIN formats ON videos.video_format = formats.id + WHERE videos.lecture_id = ?''', lectureid) for video in videos: if not video['source']: continue - data = {'video_id': video['id'], 'path': video['path'], - 'srcpath': video['srcpath'], 'srchash': video['srchash'], - 'chapters': chapters, 'metadata': metadata} + data = {'path': video['path'], 'srcpath': video['srcpath'], 'srchash': video['srchash']} + fmt = json.loads(video['fmtopts']) + if 'format' in fmt: + data['format'] = fmt['format'] + data['options'] = fmt.get('options', {}) + set_metadata(data, course, lecture) schedule_job('remux', data) +def schedule_transcode(source, fmt_id=None, video=None): + if video: + fmt_id = video['video_format'] + assert(video['lecture_id'] == source['lecture_id']) + assert(fmt_id != None) + fmt = query('SELECT * FROM formats WHERE id = ?', fmt_id)[0] + lecture = query('SELECT * FROM lectures WHERE id = ?', source['lecture_id'])[0] + course = query('SELECT * FROM courses WHERE id = ?', lecture['course_id'])[0] + data = {'input': {'path': source['path'], 'streams': []}, 'output': json.loads(fmt['options']), 'filters': []} + if source['type'] == 'plain': + stream = {'name': 'video', 'type': 'video'} + data['input']['streams'].append(stream) + stream = {'name': 'audio', 'type': 'audio'} + data['input']['streams'].append(stream) + else: + assert(False) + set_metadata(data['output'], course, lecture) + basename = os.path.basename(source['path']).rsplit('.', 1)[0] + data['output']['path'] = 'pub/'+course['handle']+'/'+basename+fmt['suffix'] + if video: + old_source = query('SELECT * FROM sources WHERE id = ?', video['source']) + data['output']['path'] = video['path'] + data['video_id'] = video['id'] + data['srcpath'] = old_source['path'] + data['srchash'] = old_source['hash'] + else: + data['lecture_id'] = lecture['id'] + data['format_id'] = fmt['id'] + data['source_id'] = source['id'] + schedule_job('transcode', data) + +@job_handler('probe-raw') +def update_lecture_videos(jobid, jobtype, data, state, status): + if 'lecture_id' not in data: + return + if 'source_id' not in data: + modify('INSERT INTO sources (lecture_id, path, type, hash, time_created) VALUES (?, ?, ?, ?, ?)', + data['lecture_id'], data['path'], 'plain', status['hash'], datetime.now()) + sources = query('SELECT * FROM sources WHERE sources.lecture_id = ? ORDER BY time_created', data['lecture_id']) + if not sources: + return + latest = sources[-1] + videos = query('SELECT * FROM videos WHERE videos.lecture_id = ?', data['lecture_id']) + current_fmts = [v['video_format'] for v in videos] + formats = query('''SELECT formats.* FROM formats + JOIN profiles ON formats.id = profiles.format + JOIN courses ON profiles.name = courses.profile + JOIN lectures ON courses.id = lectures.course_id + WHERE lectures.id = ?''', data['lecture_id']) + for fmt in formats: + if fmt['id'] not in current_fmts: + schedule_transcode(latest, fmt_id=fmt['id']) + for video in videos: + if video['source'] != latest['id']: + schedule_transcode(latest, video=video) + @edit_handler('chapters') def chapter_changed(table, column, value, id): print('chapter_changed') diff --git a/sorter.py b/sorter.py index bd9f4c3c0e142c51898413c1191517db6917a78b..44995330bf5635596d46c73ac053d0ebf63a31e8 100644 --- a/sorter.py +++ b/sorter.py @@ -1,5 +1,6 @@ from server import * import traceback +import os.path @app.route('/internal/sort/log') @register_navbar('Sortierlog', icon='sort-by-attributes-alt') @@ -63,6 +64,21 @@ def schedule_thumbnail(lectureid, filePath=None): data = '{"lectureid": "'+str(lectureid)+'", "path": "'+path+'"}' query('INSERT INTO jobs (type, data, time_created) VALUES ("thumbnail", ?, ?)', data, datetime.now()); +@job_handler('transcode') +def insert_transcoded_video(jobid, jobtype, data, state, status): + if 'lecture_id' not in data or 'source_id' not in data or 'format_id' not in data: + return + if 'video_id' in data: + return + video_id = modify('''INSERT INTO videos_data + (lecture_id, visible, path, video_format, title, comment, internal, file_modified, time_created, time_updated, created_by, hash, file_size, source) + VALUES + (?, 0, ?, ?, "", "", "", ?, ?, ?, ?, ?, ?, ?)''', + data['lecture_id'], data['output']['path'], data['format_id'], + datetime.now(), datetime.now(), datetime.now(), -1, status['hash'], + status['filesize'], data['source_id']) + schedule_thumbnail(data['lecture_id']) + def sort_file(filename, course=None, lectures=None): # filenames: <handle>-<sorter>-<format>.mp4 # "sorter" musst be found with fuzzy matching. "sorter" musst be one or more of the following types: (inside the loop) @@ -176,6 +192,19 @@ def sort_encoded(filename): schedule_job('publish_video', {'source': filename, 'path': 'pub/'+course['handle']+'/'+filename, 'lecture_id': lecture['id'], 'format_id': fmt}) return 'OK', 200 +@app.route('/internal/sort/autoencode') +@sort_api_token_required +def sort_autoencode(): + filename = request.values['path'] + path = 'autoencode/'+filename + matches, fmt = sort_file(filename) + if len(matches) != 1: + log_sort_error(-1, 'raw/'+path, matches) + return "Could not match filename", 400 + lecture = matches[0] + schedule_job('probe-raw', {'path': path, 'lecture_id': lecture['id'], 'import-chapters': True}) + return 'OK', 200 + @job_handler('publish_video') def handle_published_video(jobid, jobtype, data, state, status): if 'lecture_id' not in data or 'format_id' not in data: