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: