diff --git a/db.py b/db.py
index 55484b9e831550c75331bff91e1445236505ea46..3a2d9f593625ff8bc19913060e56e10645635bd3 100644
--- a/db.py
+++ b/db.py
@@ -25,7 +25,7 @@ def dict_factory(cursor, row):
 	return d
 
 # From sqlite3 module, but with error catching
-def sqlite_convert_timestamp(val):
+def convert_timestamp(val):
 	try:
 		datepart, timepart = val.split(b" ")
 		year, month, day = map(int, datepart.split(b"-"))
@@ -36,12 +36,12 @@ def sqlite_convert_timestamp(val):
 		else:
 				microseconds = 0
 		val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
-	except:
+	except ValueError:
 		val = None
 	return val
 
-sqlite3.register_converter('datetime', sqlite_convert_timestamp)
-sqlite3.register_converter('timestamp', sqlite_convert_timestamp)
+sqlite3.register_converter('datetime', convert_timestamp)
+sqlite3.register_converter('timestamp', convert_timestamp)
 
 def query(operation, *params):
 	if config['DB_ENGINE'] == 'mysql':
diff --git a/feeds.py b/feeds.py
new file mode 100644
index 0000000000000000000000000000000000000000..e44ca6101f5356b0719d8d5ef683e4589b96d394
--- /dev/null
+++ b/feeds.py
@@ -0,0 +1,55 @@
+from server import *
+
+def gen_atomid(s):
+	return 'urn:md5:'+hashlib.md5(s.encode('utf-8')).hexdigest().upper()
+
+def fixdate(d):
+	if not isinstance(d, datetime):
+		return datetime(MINYEAR, 1, 1)
+	return d
+
+@app.route('/feed')
+@app.route('/course/<handle>/feed')
+@handle_errors(None, 'Diese Veranstaltung existiert nicht!', 400, IndexError)
+def feed(handle=None):
+	id = None
+	course = {'id': None, 'title': 'Neueste Videos', 'time_created': None, 'time_updated': None}
+	course['atomid'] = gen_atomid('FROM videos SELECT *')
+	if handle:
+		course = query('SELECT * FROM courses WHERE handle = ? AND visible', handle)[0]
+		course['atomid'] = gen_atomid('Video AG, courses['+str(course['id'])+']: '+course['handle'])
+		id = course['id']
+	entries = query('''
+			SELECT lectures.*, videos.file_size, videos.path, videos.id AS video_id, videos.hash,
+					MAX(videos.time_created, videos.time_updated, lectures.time_created, lectures.time_updated) as updated,
+					courses.title AS course_title, courses.handle AS course_handle, courses.semester, courses.organizer AS course_organizer, courses.short as course_short,
+					formats.description AS format_description, formats.player_prio
+				FROM lectures
+				JOIN courses ON (courses.id = lectures.course_id)
+				JOIN videos ON (lectures.id = videos.lecture_id)
+				JOIN formats ON (formats.id = videos.video_format)
+				WHERE (? IS NULL) OR course_id = ? AND courses.visible AND lectures.visible AND videos.visible
+				ORDER BY time DESC, player_prio
+				LIMIT 100''',
+			course['id'], course['id'])
+	updated = max(course['time_updated'], course['time_created'], key=fixdate)
+	for entry in entries:
+		entry['updated'] = convert_timestamp(entry['updated'].encode('utf-8'))
+		if len(entry['hash']) != 32:
+			entry['atomid'] = gen_atomid('Video AG, videos['+str(entry['video_id'])+']')
+		else:
+			entry['atomid'] = 'urn:md5:'+(entry['hash'].upper())
+		updated = max(updated, entry['updated'], key=fixdate)
+	course['updated'] = updated
+	return Response(render_template('feed.atom', course=course, entries=entries), 200, {'Content-Type': 'application/atom+xml'})
+
+@app.route('/course/feed')
+def feed_courses():
+	courses = query('SELECT *, MAX(time_created, time_updated) AS updated FROM courses WHERE visible AND listed ORDER BY time_created DESC LIMIT 100')
+	atomid = gen_atomid('Video AG, courses')
+	updated = None
+	for course in courses:
+		course['updated'] = convert_timestamp(course['updated'].encode('utf-8'))
+		course['atomid'] = gen_atomid('Video AG, courses['+str(course['id'])+']: '+course['handle'])
+		updated = max(updated, course['updated'], key=fixdate)
+	return Response(render_template('feed_courses.atom', updated=updated, atomid=atomid, courses=courses), 200, {'Content-Type': 'application/atom+xml'})
diff --git a/server.py b/server.py
index 8a5f92aced2c51020baa99e66bc61304e938fd9d..f044766216585f5c3e46965f7a0c5513cfa9117f 100755
--- a/server.py
+++ b/server.py
@@ -1,9 +1,10 @@
 from flask import Flask, g, request, url_for, redirect, session, render_template, flash, Response
 from werkzeug.routing import Rule
 from functools import wraps
-from datetime import date, timedelta, datetime, time
+from datetime import date, timedelta, datetime, time, MINYEAR
 import threading
 import os
+import hashlib
 
 app = Flask(__name__)
 
@@ -36,7 +37,7 @@ config.from_pyfile('config.py', silent=True)
 if config['DEBUG']:
 	app.jinja_env.auto_reload = True
 
-from db import query, searchquery, ldapauth, ldapget
+from db import query, searchquery, ldapauth, ldapget, convert_timestamp
 
 mod_endpoints = []
 
@@ -78,7 +79,10 @@ def handle_errors(endpoint, text, code, *errors, **epargs):
 			try:
 				return func(*args, **kwargs)
 			except errors:
-				return render_endpoint(endpoint, text, **epargs), code
+				if endpoint:
+					return render_endpoint(endpoint, text, **epargs), code
+				else:
+					return text, code
 		return decorator
 	return wrapper
 
@@ -86,6 +90,18 @@ def handle_errors(endpoint, text, code, *errors, **epargs):
 def handle_not_found(e):
 	return render_endpoint('index', 'Diese Seite existiert nicht!'), 404
 
+@app.template_filter(name='semester')
+def human_semester(s):
+	return s
+
+@app.template_filter(name='date')
+def human_date(d):
+	return d.strftime('%x')
+
+@app.template_filter()
+def rfc3339(d):
+	return d.strftime('%Y-%m-%dT%H:%M:%S+02:00')
+
 @app.route('/')
 @register_navbar('Home', icon='home')
 def index():
@@ -212,7 +228,6 @@ def edit():
 	query('COMMIT')
 	return "OK", 200
 
-
 @app.route('/auth')
 def auth(): # For use with nginx auth_request
 	if 'X-Original-Uri' not in request.headers:
@@ -346,3 +361,9 @@ def import_from(numid=None, source=None, id=None):
 		courses = query('SELECT * FROM courses WHERE handle = ?', id)[0]
 	lectures = query('SELECT * FROM lectures WHERE course_id = ?', courses['id'])
 	return render_template('import_campus.html', course=courses, lectures=lectures, campus=campus)
+
+@app.route('/files/<filename>')
+def files(filename):
+	return redirect(config['VIDEOPREFIX']+'/'+filename)
+
+import feeds