diff --git a/chapters.py b/chapters.py
index a0d35b2ea50b6974ec4e6187a89450b1b045a296..e8dea983284e30320479bc591be96519634d9802 100644
--- a/chapters.py
+++ b/chapters.py
@@ -1,5 +1,6 @@
 import json
 from server import *
+from jobmanagement import job_handler
 
 @job_handler('probe', 'probe-raw')
 def import_xmp_chapters(jobid, jobtype, data, state, status):
diff --git a/encoding.py b/encoding.py
index 13ad6995b883c4fb7267b81e6dfd1b2779ae22dc..88763110bd4f660aa42e89606874b7a82ee057ff 100644
--- a/encoding.py
+++ b/encoding.py
@@ -1,37 +1,6 @@
 from server import *
 import os.path
-from jobmanagement import schedule_job
-
-def set_metadata(dest, course, lecture):
-	chapters = query('SELECT text, time FROM chapters WHERE lecture_id = ? AND visible ORDER BY time', 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, videoid=None):
-	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
-		if videoid and video['id'] != videoid:
-			continue
-		data = {'path': video['path'], 'srcpath': video['srcpath'],
-			'srchash': video['srchash'], 'video_id': video['id']}
-		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)
+from jobtypes import schedule_remux, schedule_transcode
 
 @app.route('/internal/jobs/add/remux', methods=['GET', 'POST'])
 @mod_required
@@ -44,37 +13,6 @@ def add_remux_job():
 	schedule_remux(lectureid, videoid)
 	return redirect(request.values.get('ref', url_for('jobs_overview')))
 
-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'])[0]
-		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, queue="background")
-
 @app.route('/internal/jobs/add/reencode', methods=['GET', 'POST'])
 @mod_required
 @csrf_protect
diff --git a/jobmanagement.py b/jobmanagement.py
index 7c2ffa6bb796f00afed8f09b32f605f4c8518b0d..facc14993b7b6934b3d7e1f48c7321564a548f0a 100644
--- a/jobmanagement.py
+++ b/jobmanagement.py
@@ -1,5 +1,6 @@
 from server import modify, query, date_json_handler, sched_func
 from datetime import datetime, timedelta
+import traceback
 import json
 
 job_handlers = {}
diff --git a/jobs.py b/jobs.py
index d0fb57a9175847d761f9221190759ab26e216627..80ff34b821a0349adedb2f491e7e792a468d7c02 100644
--- a/jobs.py
+++ b/jobs.py
@@ -1,5 +1,4 @@
 from server import *
-import traceback
 import json
 import random
 from time import sleep
diff --git a/jobtypes.py b/jobtypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..e897608f6cf9cacf6bf6ece4f393de41de19498b
--- /dev/null
+++ b/jobtypes.py
@@ -0,0 +1,77 @@
+from jobmanagement import schedule_job
+import json
+from server import query
+
+def set_metadata(dest, course, lecture):
+	chapters = query('SELECT text, time FROM chapters WHERE lecture_id = ? AND visible ORDER BY time', 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_thumbnail(lectureid):
+	videos = query('''
+		SELECT videos.path
+		FROM videos
+		JOIN formats ON (videos.video_format = formats.id)
+		WHERE videos.lecture_id = ?
+		ORDER BY formats.prio DESC''', lectureid)
+	schedule_job('thumbnail', {'lectureid': str(lectureid), 'path': videos[0]['path']})
+
+
+def schedule_remux(lectureid, videoid=None):
+	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
+		if videoid and video['id'] != videoid:
+			continue
+		data = {'path': video['path'], 'srcpath': video['srcpath'],
+			'srchash': video['srchash'], 'video_id': video['id']}
+		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'])[0]
+		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, queue="background")
+
diff --git a/livestreams.py b/livestreams.py
index e55eac2669e3036a69b7b7ff9fc03bfaae5c10d5..8551541d8e9854f7a0a1ffd5a3d60570b6af3313 100644
--- a/livestreams.py
+++ b/livestreams.py
@@ -1,5 +1,5 @@
 from server import *
-from jobmanagement import schedule_job, restart_job
+from jobmanagement import cancel_job, restart_job, schedule_job, job_handler
 
 @app.route('/internal/streaming/legacy_auth', methods=['GET', 'POST'])
 @app.route('/internal/streaming/legacy_auth/<server>', methods=['GET', 'POST'])
diff --git a/server.py b/server.py
index 33cd9602824144b57f737c23b050da04be6f3b65..4288f8797adaa3497ed8a362c3353fe8932a1f4b 100644
--- a/server.py
+++ b/server.py
@@ -476,7 +476,7 @@ def dbstatus():
 def date_json_handler(obj):
 	return obj.isoformat() if hasattr(obj, 'isoformat') else obj
 
-from jobs import job_handler, schedule_job, cancel_job, restart_job
+import jobs
 from edit import edit_handler
 import feeds
 import importer
@@ -484,7 +484,6 @@ import stats
 if 'ICAL_URL' in config:
 	import meetings
 import l2pauth
-from encoding import schedule_remux
 import sorter
 import timetable
 import chapters
diff --git a/sorter.py b/sorter.py
index c39a4dc1f705140cb26a6b5787902cea49e7c132..2e543ec75938b500b0b7307e2fee83f3ef81bfdd 100644
--- a/sorter.py
+++ b/sorter.py
@@ -1,5 +1,6 @@
 from server import *
-from jobmanagement import schedule_job
+from jobmanagement import schedule_job, job_handler
+from jobtypes import schedule_thumbnail
 import traceback
 import os.path
 
@@ -54,15 +55,6 @@ def insert_video(lectureid, dbfilepath, fileformatid, hash="", filesize=-1, dura
 	course = query('SELECT * FROM courses WHERE id = ?', lecture['course_id'])[0]
 	notify_mods('new_video', course['id'], course=course, lecture=lecture, video=video)
 
-def schedule_thumbnail(lectureid):
-	videos = query('''
-			SELECT videos.path
-			FROM videos
-			JOIN formats ON (videos.video_format = formats.id)
-			WHERE videos.lecture_id = ?
-			ORDER BY formats.prio DESC''', lectureid)
-	schedule_job('thumbnail', {'lectureid': str(lectureid), 'path': videos[0]['path']})
-
 @app.route('/internal/jobs/add/thumbnail', methods=['GET', 'POST'])
 @mod_required
 @csrf_protect
diff --git a/tests/test_general.py b/tests/test_general.py
index 2febe3873a99d21cfa9e78d459f60d3cfde7b752..5effa3045c0f086c9fd3a9639f8a46a59ab12ee7 100644
--- a/tests/test_general.py
+++ b/tests/test_general.py
@@ -5,11 +5,9 @@ import server
 import flask
 from flask import url_for
 
-
 class VideoTestCase(unittest.TestCase):
 	def tearDown(self):
 		pass
-		#os.unlink(server.app.config['SQLITE_DB'])
 	
 	def setUp(self):
 		server.app.testing = True
@@ -262,6 +260,7 @@ class VideoTestCase(unittest.TestCase):
 			assert len(match) == 1
 			assert match[0]['id'] == 6095
 
+#	@unittest.skip("too slow")
 	def test_campusimport(self):
 		with self.app as c:
 			self.login(c)