diff --git a/edit.py b/edit.py
index a0423b1fb23504e416740a46f97c379de4a8ced7..7194a2b63b27b7fddcc0f976fdafdba6199733ce 100644
--- a/edit.py
+++ b/edit.py
@@ -210,3 +210,15 @@ def set_responsible(course_id, user_id, value):
 		modify('DELETE FROM responsible WHERE course_id = ? AND user_id = ?', course_id, user_id);
 	return "OK", 200
 	
+edit_handlers = {}
+def edit_handler(*tables, field=None):
+	def wrapper(func):
+		for table in tables:
+			if table not in edit_handlers:
+				edit_handlers[table] = {}
+			if field not in edit_handlers[table]:
+				edit_handlers[table][field] = []
+			edit_handlers[table][field].append(func)
+		return func
+	return wrapper
+
diff --git a/jobs.py b/jobs.py
index c984f7c1d9ccff75448c94bf6994412c2324819b..86b5519436c336400aff9b16f1503e759399eb81 100644
--- a/jobs.py
+++ b/jobs.py
@@ -2,7 +2,24 @@ from server import *
 import traceback
 import json
 import random
-from sorter import schedule_thumbnail
+
+job_handlers = {}
+def job_handler(*types, state='finished'):
+	def wrapper(func):
+		for jobtype in types:
+			if jobtype not in job_handlers:
+				job_handlers[jobtype] = {}
+			if state not in job_handlers[jobtype]:
+				job_handlers[jobtype][state] = []
+			job_handlers[jobtype][state].append(func)
+		return func
+	return wrapper
+
+def schedule_job(jobtype, data=None, priority=0, queue="default"):
+	if not data:
+		data = {}
+	modify('INSERT INTO jobs (type, priority, queue, data, time_created) VALUES (?, ?, ?, ?, ?)',
+			jobtype, priority, queue, json.dumps(data, default=date_json_handler), datetime.now())
 
 @app.route('/internal/jobs/overview')
 @register_navbar('Jobs', iconlib='fa', icon='suitcase')
@@ -42,19 +59,14 @@ def jobs_overview():
 def jobs_action(action, jobid=None):
 	if action == 'clear_failed':
 		query('UPDATE jobs SET state="deleted" WHERE state = "failed" AND (id = ? OR ? IS NULL)', jobid, jobid)
-	if action == 'retry_failed':
+	elif action == 'retry_failed':
 		query('UPDATE jobs SET state="ready" WHERE state = "failed" AND (id = ? OR ? IS NULL)', jobid, jobid)
-	if action == 'copy':
+	elif action == 'copy':
 		if jobid:
 			query("INSERT INTO jobs SELECT NULL, type, priority, queue, 'ready', '', '' , ?, '', NULL, data, '{}' FROM jobs where ID=?;", datetime.now(), jobid)
-	if action == 'delete':
+	elif action == 'delete':
 		if jobid:
 			query('UPDATE jobs SET state="deleted" WHERE id = ?', jobid)
-	if action == 'add':
-		jobtype = request.values.get('type', None)
-		if jobtype == 'thumbnail':
-			lectureid = int(request.values.get('lecture_id', -1))
-			schedule_thumbnail(lectureid)
 	return redirect(request.values.get('ref', url_for('jobs_overview')))
 
 def jobs_api_token_required(func):
diff --git a/livestreams.py b/livestreams.py
index bd0b97d4ef41b1ccf19ee86b910abe636bc4a873..3ccbe1de4a49a6dd06955faedc60f1ce9ccd8731 100644
--- a/livestreams.py
+++ b/livestreams.py
@@ -3,9 +3,8 @@ from server import *
 @sched_func(30)
 def livestream_thumbnail():
 	livestreams = query('SELECT streams.lecture_id, streams.handle AS livehandle FROM streams WHERE streams.active')
-
 	for v in genlive(livestreams):
-		sorter.schedule_thumbnail(v['lecture_id'], v['path'])
+		schedule_job('thumbnail', {'lectureid': v['lecture_id'], 'path': v['path']})
 
 @app.route('/internal/streaming/legacy_auth', methods=['GET', 'POST'])
 def streamauth():
diff --git a/server.py b/server.py
index 84f35fc36f3fad8621bd1318813cb0324fab2204..17db3756d602c324c19103c373481998ac03f091 100644
--- a/server.py
+++ b/server.py
@@ -483,47 +483,17 @@ def dbstatus():
 		clusters[cluster].append(host)
 	return render_template('dbstatus.html', clusters=clusters, statuses=status, vars=variables), 200
 
-job_handlers = {}
-def job_handler(*types, state='finished'):
-	def wrapper(func):
-		for jobtype in types:
-			if jobtype not in job_handlers:
-				job_handlers[jobtype] = {}
-			if state not in job_handlers[jobtype]:
-				job_handlers[jobtype][state] = []
-			job_handlers[jobtype][state].append(func)
-		return func
-	return wrapper
-
 def date_json_handler(obj):
 	return obj.isoformat() if hasattr(obj, 'isoformat') else obj
 
-def schedule_job(jobtype, data=None, priority=0, queue="default"):
-	if not data:
-		data = {}
-	modify('INSERT INTO jobs (type, priority, queue, data, time_created) VALUES (?, ?, ?, ?, ?)',
-			jobtype, priority, queue, json.dumps(data, default=date_json_handler), datetime.now())
-
-edit_handlers = {}
-def edit_handler(*tables, field=None):
-	def wrapper(func):
-		for table in tables:
-			if table not in edit_handlers:
-				edit_handlers[table] = {}
-			if field not in edit_handlers[table]:
-				edit_handlers[table][field] = []
-			edit_handlers[table][field].append(func)
-		return func
-	return wrapper
-
-import edit
+from jobs import job_handler, schedule_job
+from edit import edit_handler
 import feeds
 import importer
 import stats
 if 'ICAL_URL' in config:
 	import meetings
 import l2pauth
-import jobs
 import sorter
 import encoding
 import timetable
diff --git a/sorter.py b/sorter.py
index 1f7029b8db3e304794099c271329a4cc82235258..86b51538bfef41474334d01e535fcb3863425eb4 100644
--- a/sorter.py
+++ b/sorter.py
@@ -49,21 +49,22 @@ def insert_video(lectureid, dbfilepath, fileformatid, hash="", filesize=-1):
 	schedule_thumbnail(lectureid)
 	schedule_job('probe', {'path': dbfilepath, 'lecture_id': lectureid, 'video_id': video_id, 'import-chapters': True})
 
-def schedule_thumbnail(lectureid, filePath=None):
+@app.route('/internal/jobs/add/thumbnail', methods=['GET', 'POST'])
+@mod_required
+@handle_errors('jobs_overview', 'Zu dieser Veranstaltung existieren keine Videos!', 404, IndexError)
+def schedule_thumbnail(lectureid=None):
+	ret = None
+	if not lectureid:
+		lectureid = request.values['lectureid']
+		ret = redirect(request.values.get('ref', url_for('jobs_overview')))
 	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 )
-	if videos:
-		path = videos[0]['path']
-	elif filePath:
-		path = filePath
-	else:
-		return
-	data = '{"lectureid": "'+str(lectureid)+'", "path": "'+path+'"}'
-	query('INSERT INTO jobs (type, data, time_created) VALUES ("thumbnail", ?, ?)', data, datetime.now());
+			ORDER BY formats.prio DESC''', lectureid)
+	schedule_job('thumbnail', {'lectureid': lectureid, 'path': videos[0]['path']})
+	return ret
 
 @job_handler('transcode')
 def insert_transcoded_video(jobid, jobtype, data, state, status):
diff --git a/stats.py b/stats.py
index de8f019675e54f923f57bdfceb6d641ef0e13b7d..6e9df8ffb72378e48ee1d115d708ec1f76550f8b 100644
--- a/stats.py
+++ b/stats.py
@@ -1,6 +1,5 @@
 from server import *
 import json
-from jobs import date_json_handler
 from hashlib import md5
 from datetime import datetime
 
diff --git a/templates/jobs_overview.html b/templates/jobs_overview.html
index ae51248b9241ead6786c92e6250cadfcedf8b9e2..469cf56f118deeae5788ff125f681199fb02a394 100644
--- a/templates/jobs_overview.html
+++ b/templates/jobs_overview.html
@@ -17,10 +17,9 @@
 
 					<div class="tab-content" style="margin-top: 10px;">
 						<div role="tabpanel" class="tab-pane active" id="add_thumbnail">
-							<form class="form-inline" action="{{url_for('jobs_action', action='add', ref=request.url)}}" method="post">
+							<form class="form-inline" action="{{url_for('schedule_thumbnail', ref=request.url)}}" method="post">
 								<div class="form-group">
-									<input type="hidden" name="type" value="thumbnail">
-									<input type="text" class="form-control" id="thumbnail_lectureid" placeholder="Lecture ID" name="lecture_id">
+									<input type="text" class="form-control" placeholder="Lecture ID" name="lectureid">
 									<button type="submit" class="btn btn-primary">Hinzufügen</button>
 								</div>
 							</form>