diff --git a/config.py.example b/config.py.example
index a9dfd82df9beedb642444a3d7f948aca7b16f954..186b638439da3da382799862eb27ab9d449e4038 100644
--- a/config.py.example
+++ b/config.py.example
@@ -29,3 +29,4 @@ LDAP_GROUPS = ['users']
 #ICAL_URL = 'https://user:password@mail.fsmpi.rwth-aachen.de/SOGo/....ics'
 ERROR_PAGE = 'static/500.html'
 RWTH_IP_RANGES = ['134.130.0.0/16', '137.226.0.0/16', '134.61.0.0/16', '192.35.229.0/24', '2a00:8a60::/32']
+FSMPI_IP_RANGES = ['137.226.35.192/29', '137.226.75.0/27', '137.226.127.32/27', '137.226.231.192/26', '134.130.102.0/26' ]
diff --git a/db.py b/db.py
index 53523bde8f7512ed8b0761f1eb32dc202c06819e..866d3d1d77e280931912085d680c9f39954a6c39 100644
--- a/db.py
+++ b/db.py
@@ -13,7 +13,7 @@ if config['DB_ENGINE'] == 'sqlite':
 			hours, minutes, seconds = map(int, timepart_full[0].split(b":"))
 			val = datetime(year, month, day, hours, minutes, seconds, 0)
 		except ValueError:
-			val = None
+			val = datetime.fromtimestamp(0)
 		return val
 
 	sqlite3.register_converter('datetime', convert_timestamp)
diff --git a/edit.py b/edit.py
index cfd7d1e7debcc85dbd8ef8f29bf259e7def2296d..556b2ccdd56e93f60e0f87dc1a825a6531d82a1f 100644
--- a/edit.py
+++ b/edit.py
@@ -14,19 +14,19 @@ editable_tables = {
 		'idcolumn': 'id',
 		'editable_fields': {
 			'visible':	{'type': 'boolean'},
-			'listed':	{'type': 'boolean'},
+			'listed':	{'type': 'boolean', 'description': 'Soll die Veranstaltung auf der Hauptseite gelistet werden?'},
 			'title':	{'type': 'shortstring'},
-			'short':	{'type': 'shortstring'},
+			'short':	{'type': 'shortstring', 'description': 'Abkürzung für die Veranstaltung, z.B. für den Drehplan'},
 			'handle':	{'type': 'shortstring'},
 			'organizer':	{'type': 'shortstring'},
 			'subject':	{'type': 'shortstring'},
 			'semester':	{'type': 'shortstring'},
-			'downloadable':	{'type': 'boolean'},
+			'downloadable':	{'type': 'boolean', 'description': 'Hiermit kann der Download-Button disabled werden'},
 			'internal':	{'type': 'text'},
 			'responsible':	{'type': 'shortstring'},
 			'deleted':	{'type': 'boolean'},
 			'description':	{'type': 'text'},
-			'external':  {'type': 'boolean'}},
+			'external':	{'type': 'boolean', 'description': 'Soll die Veranstaltung nicht im Drehplan angezeigt werden?'}},
 		'creationtime_fields': ['created_by', 'time_created', 'time_updated'] },
 	'lectures': {
 		'table': 'lectures_data',
@@ -42,7 +42,7 @@ editable_tables = {
 			'duration':	{'type': 'duration'},
 			'jumplist':	{'type': ''},
 			'deleted':	{'type': 'boolean'},
-			'live': {'type': 'boolean'},
+			'live':		{'type': 'boolean', 'description': 'Ist ein Livestream geplant?'},
 			'norecording': {'type': 'boolean'}},
 		'creationtime_fields': ['course_id', 'time_created', 'time_updated'] },
 	'videos': {
@@ -110,6 +110,13 @@ def parseeditpath(path):
 	assert column in editable_tables[table]['editable_fields']
 	type = editable_tables[table]['editable_fields'][column]['type']
 	return {'table': table, 'id': id, 'column': column, 'type': type, 'tableinfo': editable_tables[table]}
+@app.template_filter(name='getfielddescription')
+def getfielddescription(path):
+	p = parseeditpath(path)
+	desc = p['tableinfo']['editable_fields'][p['column']].get('description', '')
+	if desc != '':
+		desc = '<br>'+desc
+	return desc
 
 @app.route('/internal/edit', methods=['GET', 'POST'])
 @mod_required
diff --git a/icalexport.py b/icalexport.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ab175842f0ccc80c3464213a34ff817aeb88027
--- /dev/null
+++ b/icalexport.py
@@ -0,0 +1,57 @@
+from server import *
+import icalendar
+from werkzeug.datastructures import Headers
+from datetime import timedelta, datetime
+
+def export_lectures(lectures, name):
+	cal = icalendar.Calendar()
+	cal.add('prodid', '-//Video AG//rwth.video//')
+	cal.add('version', '1.0')
+	for l in lectures:
+		event = icalendar.Event()
+		event.add('summary', l['course']['short']+': '+l['title'])
+		event.add('description', '\n\n'.join([s for s in [
+					l['comment'],
+					l['internal'],
+					'Zuständig: '+l['course']['responsible'] if l['course']['responsible'] else ''
+			] if s]))
+		event.add('uid', '%i@rwth.video'%l['id'])
+		event.add('dtstamp', datetime.utcnow())
+		event.add('categories', l['course']['short'])
+		event.add('dtstart', l['time'])
+		event.add('location', l['place'])
+		event.add('dtend', l['time'] + timedelta(minutes=l['duration']))
+		cal.add_component(event)
+	h = Headers()
+	h.add_header("Content-Disposition", "inline", filename=name)
+	return Response(cal.to_ical(), mimetype="text/calendar", headers=h)
+
+def calperm(func):
+	@wraps(func)
+	def decorator(*args, **kwargs):
+		permission = ismod()
+		if 'X-Real-IP' in request.headers:
+			ip = ip_address(request.headers['X-Real-IP'])
+			for net in config['FSMPI_IP_RANGES']:
+				if ip in ip_network(net):
+					permission = True
+		if permission:
+			return func(*args, **kwargs)
+		else:
+			flash('Diese Funktion ist nur aus dem FSMPI-Netz(für SOGO-Import) oder eingeloggt verfügbar!')
+			return redirect(url_for('index'))
+	return decorator
+
+@app.route('/internal/ical/all')
+@calperm
+def ical_all():
+	return export_lectures(query('''SELECT lectures.*, "course" AS sep, courses.*
+				FROM lectures JOIN courses ON courses.id = lectures.course_id
+				WHERE NOT norecording AND NOT external ORDER BY time DESC LIMIT 1000'''),'videoag_all.ics')
+
+@app.route('/internal/ical/course/<course>')
+@calperm
+def ical_course(course):
+	return export_lectures(query('''SELECT lectures.*, "course" AS sep, courses.*
+				FROM lectures JOIN courses ON courses.id = lectures.course_id
+				WHERE courses.handle = ? AND NOT norecording AND NOT external ORDER BY time DESC''', course),'videoag_course_'+course+'.ics')
diff --git a/server.py b/server.py
index 7503ead60ecf03487bb4109c9c0f9efcce56b0fe..1631071f941a81499b1444d25fe8ccda7d9f8d9f 100644
--- a/server.py
+++ b/server.py
@@ -404,7 +404,7 @@ def course(id=None, handle=None):
 			if perm['lecture_id'] == lecture['id']:
 				lecture['perm'].append(perm)
 	videos = query('''
-			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, formats.description AS format_description, formats.player_prio, formats.prio
+			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, "formats" AS sep, formats.*
 			FROM videos
 			JOIN lectures ON (videos.lecture_id = lectures.id)
 			JOIN formats ON (videos.video_format = formats.id)
@@ -412,7 +412,7 @@ def course(id=None, handle=None):
 			WHERE lectures.course_id= ? AND (? OR videos.visible)
 			ORDER BY lectures.time, formats.prio DESC
 			''', course['id'], ismod())
-	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, formats.description AS format_description, formats.player_prio, formats.prio
+	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
 			FROM streams
 			JOIN lectures ON lectures.id = streams.lecture_id
 			JOIN formats ON formats.keywords = "hls"
@@ -434,14 +434,14 @@ def faq():
 def lecture(id, course=None, courseid=None):
 	lecture = query('SELECT * FROM lectures WHERE id = ? AND (? OR visible)', id, ismod())[0]
 	videos = query('''
-			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, formats.description AS format_description, formats.player_prio, formats.prio, formats.mimetype
+			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, "formats" AS sep, formats.*
 			FROM videos
 			JOIN formats ON (videos.video_format = formats.id)
 			JOIN courses ON (courses.id = ?)
 			WHERE videos.lecture_id = ? AND (? OR videos.visible)
 			ORDER BY formats.prio DESC
 			''', lecture['course_id'], lecture['id'], ismod())
-	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, formats.description AS format_description, formats.player_prio, formats.prio, formats.mimetype
+	livestreams = query('''SELECT streams.handle AS livehandle, streams.lecture_id, "formats" AS sep, formats.*
 			FROM streams
 			JOIN lectures ON lectures.id = streams.lecture_id
 			JOIN formats ON formats.keywords = "hls"
@@ -687,3 +687,4 @@ if 'JOBS_API_KEY' in config:
 	import jobs
 import timetable
 import chapters
+import icalexport
diff --git a/static/moderator.js b/static/moderator.js
index ed9cb376f85ca7512b3d98e60bbdfcec5afc1bd6..dec55f9f690ebb1d4d7858d2a8f222e1cab98342 100644
--- a/static/moderator.js
+++ b/static/moderator.js
@@ -191,8 +191,11 @@ var moderator = {
 							html += '<option value="none">Kein Zugriff</option>';
 						html += '</select>';
 						html += '<input class="col-xs-12 passwordinput authuser" type="text" placeholder="Benutzername">';
-						html += '<input class="col-xs-12 passwordinput authpassword" type="text" placeholder="Passwort">';
+						html += '<input class="col-xs-10 passwordinput authpassword" type="text" placeholder="Passwort">'
+						html += '<button class="col-xs-2 passwordinput authpgen" type="button" onclick="$(\'.authpassword\',this.parentNode).val(moderator.permissioneditor.randompw());"><span class="fa fa-plus" aria-hidden="true"></span></button>'
+
 						html += '<input class="col-xs-12 authl2p" type="text" placeholder="Lernraum" style="display: none;">';
+
 						html += '<button class="col-xs-6" onclick="moderator.permissioneditor.addbtnclick(this)">Add</button>';
 						//html += '<button class="col-xs-4" onclick="moderator.permissionedior.updatebtnclick(this)">Update</button>';
 						html += '<button class="col-xs-6" onclick="moderator.permissioneditor.delbtnclick(this)">Delete</button>';
@@ -248,58 +251,76 @@ var moderator = {
 					$(".authl2p",element.parentElement).hide();
 					break;
 			}
+		},
+		randompw: function () {;
+			var array = new Uint8Array(10);
+			window.crypto.getRandomValues(array);
+			var result = '';
+			for (var i = 0; i< array.length; i++) {
+				result += String.fromCharCode(48+ (array[i]/255.0)*74);
+			}
+			return result;
 		}
 	},
+	plots: {
+		init: function() {
+			$(window).on("resize", moderator.plots.resize);
+			$(".plotlyresize").on("click", moderator.plots.resize);
+			moderator.plots.createplots(".plot-view")
+		},
+		resize: function() {
+			$(".plot-view").each(function () {Plotly.Plots.resize(this)});
+		},
+		createplots: function (selector) {
+			var l = $(selector);
+			for (var i = 0; i < l.length; i ++) {
+				if (!l[i].id)
+					l[i].id = "plot-"+i;
+				$(l[i]).html('<div class="plot-loader"></div>');
+				$.ajax({
+					divobj: l[i],
+					method: "GET",
+					url: l[i].dataset.url,
+					dataType: "json",
+					error: function (jqXHR, textStatus, errorThrow) {
+						$(this.divobj).html('<div class="plot-error">'+errorThrow+'</div>');
+					},
+					success: function (traces) {
+						var layout = {margin: {l: 30, r: 30, t: 10, b: 70, pad: 0}};
+						for (var i = 0; i < traces.length; i ++) {
+							traces[i].type = this.divobj.dataset.type;
+						}
+						if (this.divobj.dataset.type == "pie")
+							layout.showlegend = false;
+						traces.sort(function (a, b) {
+							asum = 0;
+							bsum = 0;
+							for (var i = 0; i < a.y.length; i++)
+								asum += a.y[i]
+							for (var i = 0; i < b.y.length; i++)
+								bsum += b.y[i]
+							return bsum-asum;
+						});
+						for (var i = 0; i < traces.length; i++) {
+							if (i > 15) {
+								traces[i].visible = "legendonly";
+							}
+						}
+						$(this.divobj).html("");
+						Plotly.newPlot(this.divobj.id, traces, layout, { "modeBarButtonsToRemove": ['sendDataToCloud','hoverCompareCartesian'], "displaylogo": false});
+					}
+				});
+			};
+		},
+	},
 	init: function () {
 		moderator.api.init();
 		moderator.editor.init();
 		moderator.permissioneditor.init();
+		moderator.plots.init();
 	}
 };
 
 $( document ).ready( function () {
 	moderator.init(); 
 } );
-
-$( document ).ready( function () {
-  var l = $(".plot-view");
-  for (var i = 0; i < l.length; i ++) {
-    if (!l[i].id)
-      l[i].id = "plot-"+i;
-		$(l[i]).html('<div class="plot-loader"></div>');
-    $.ajax({
-      divobj: l[i],
-      method: "GET",
-      url: l[i].dataset.url,
-      dataType: "json",
-			error: function (jqXHR, textStatus, errorThrow) {
-				$(this.divobj).html('<div class="plot-error">'+errorThrow+'</div>');
-			},
-      success: function (traces) {
-				var layout = {margin: {l: 30, r: 30, t: 10, b: 70, pad: 0}};
-				for (var i = 0; i < traces.length; i ++) {
-					traces[i].type = this.divobj.dataset.type;
-				}
-				if (this.divobj.dataset.type == "pie")
-					layout.showlegend = false;
-				traces.sort(function (a, b) {
-					asum = 0;
-					bsum = 0;
-					for (var i = 0; i < a.y.length; i++)
-						asum += a.y[i]
-					for (var i = 0; i < b.y.length; i++)
-						bsum += b.y[i]
-					return bsum-asum;
-				});
-				for (var i = 0; i < traces.length; i++)
-					if (i > 20)
-						traces[i].visible = "legendonly";
-				$(this.divobj).html("");
-        Plotly.newPlot(this.divobj.id, traces, layout, { "modeBarButtonsToRemove": ['sendDataToCloud','hoverCompareCartesian'], "displaylogo": false});
-      }
-    });
-  };
-});
-$(window).on("resize", function () {
-	$(".plot-view").each(function () {Plotly.Plots.resize(this)});
-});
diff --git a/static/style.css b/static/style.css
index 31e977c77aad7904a4ce30f76b702eb268314c0b..870481be8d90ca6103f983ccc4c05f8197dc46c1 100644
--- a/static/style.css
+++ b/static/style.css
@@ -10,6 +10,7 @@
 	font-size: 3em;
 	text-align: center;
 	line-height: 130px;
+	text-shadow: 0 0 2px black;
 }
 
 #timetable.table-bordered td:first-child {
diff --git a/static/videojs/videojs-resolution-switcher.js b/static/videojs/videojs-resolution-switcher.js
index eae1c38b579bd397135fdd092f85147b0b26bfdb..fc3647685c5bdcfd5c7c4e093b74774bc3ed887a 100644
--- a/static/videojs/videojs-resolution-switcher.js
+++ b/static/videojs/videojs-resolution-switcher.js
@@ -1,367 +1,406 @@
-/*! videojs-resolution-switcher - 2015-7-26
+/*! videojs-resolution-switcher - 2017-05-20
  * Copyright (c) 2016 Kasper Moskwiak
- * Modified by Pierre Kraft and Derk-Jan Hartman
+ * Modified by Pierre Kraft, Derk-Jan Hartman and Andreas Valder
  * Licensed under the Apache-2.0 license. */
 
 (function() {
-  /* jshint eqnull: true*/
-  /* global require */
-  'use strict';
-  var videojs = null;
-  if(typeof window.videojs === 'undefined' && typeof require === 'function') {
-    videojs = require('video.js');
-  } else {
-    videojs = window.videojs;
-  }
-
-  (function(window, videojs) {
-    var videoJsResolutionSwitcher,
-      defaults = {
-        ui: true
-      };
-
-    /*
-     * Resolution menu item
-     */
-    var MenuItem = videojs.getComponent('MenuItem');
-    var ResolutionMenuItem = videojs.extend(MenuItem, {
-      constructor: function(player, options){
-        options.selectable = true;
-        // Sets this.player_, this.options_ and initializes the component
-        MenuItem.call(this, player, options);
-        this.src = options.src;
-
-        player.on('resolutionchange', videojs.bind(this, this.update));
-      }
-    } );
-    ResolutionMenuItem.prototype.handleClick = function(event){
-      MenuItem.prototype.handleClick.call(this,event);
-      this.player_.currentResolution(this.options_.label);
-    };
-    ResolutionMenuItem.prototype.update = function(){
-      var selection = this.player_.currentResolution();
-      this.selected(this.options_.label === selection.label);
-    };
-    MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem);
-
-    /*
-     * Resolution menu button
-     */
-    var MenuButton = videojs.getComponent('MenuButton');
-    var ResolutionMenuButton = videojs.extend(MenuButton, {
-      constructor: function(player, options){
-        this.label = document.createElement('span');
-        options.label = 'Quality';
-        // Sets this.player_, this.options_ and initializes the component
-        MenuButton.call(this, player, options);
-        this.el().setAttribute('aria-label','Quality');
-        this.controlText('Quality');
-
-        if(options.dynamicLabel){
-          videojs.addClass(this.label, 'vjs-resolution-button-label');
-          this.el().appendChild(this.label);
-        }else{
-          var staticLabel = document.createElement('span');
-          videojs.addClass(staticLabel, 'vjs-menu-icon');
-          this.el().appendChild(staticLabel);
-        }
-        player.on('updateSources', videojs.bind( this, this.update ) );
-      }
-    } );
-    ResolutionMenuButton.prototype.createItems = function(){
-      var menuItems = [];
-      var labels = (this.sources && this.sources.label) || {};
-
-      // FIXME order is not guaranteed here.
-      for (var key in labels) {
-        if (labels.hasOwnProperty(key)) {
-          menuItems.push(new ResolutionMenuItem(
-            this.player_,
-            {
-              label: key,
-              src: labels[key],
-              selected: key === (this.currentSelection ? this.currentSelection.label : false)
-            })
-          );
-        }
-      }
-      return menuItems;
-    };
-    ResolutionMenuButton.prototype.update = function(){
-      this.sources = this.player_.getGroupedSrc();
-      this.currentSelection = this.player_.currentResolution();
-      this.label.innerHTML = this.currentSelection ? this.currentSelection.label : '';
-      return MenuButton.prototype.update.call(this);
-    };
-    ResolutionMenuButton.prototype.buildCSSClass = function(){
-      return MenuButton.prototype.buildCSSClass.call( this ) + ' vjs-resolution-button';
-    };
-    MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton);
-
-    /**
-     * Initialize the plugin.
-     * @param {object} [options] configuration for the plugin
-     */
-    videoJsResolutionSwitcher = function(options) {
-      var settings = videojs.mergeOptions(defaults, options),
-          player = this,
-          groupedSrc = {},
-          currentSources = {},
-          currentResolutionState = {};
-
-      /**
-       * Updates player sources or returns current source URL
-       * @param   {Array}  [src] array of sources [{src: '', type: '', label: '', res: ''}]
-       * @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources
-       */
-      player.updateSrc = function(src){
-        //Return current src if src is not given
-        if(!src){ return player.src(); }
-
-        // Only add those sources which we can (maybe) play
-        src = src.filter( function(source) {
-          try {
-            return ( player.canPlayType( source.type ) !== '' );
-          } catch (e) {
-            // If a Tech doesn't yet have canPlayType just add it
-            return true;
-          }
-        });
-        //Sort sources
-        this.currentSources = src.sort(compareResolutions);
-        this.groupedSrc = bucketSources(this.currentSources);
-        // Pick one by default
-        var chosen = chooseSrc(this.groupedSrc, this.currentSources);
-        this.currentResolutionState = {
-          label: chosen.label,
-          sources: chosen.sources
-        };
-
-        player.trigger('updateSources');
-        player.setSourcesSanitized(chosen.sources, chosen.label);
-        player.trigger('resolutionchange');
-        return player;
-      };
-
-      /**
-       * Returns current resolution or sets one when label is specified
-       * @param {String}   [label]         label name
-       * @param {Function} [customSourcePicker] custom function to choose source. Takes 2 arguments: sources, label. Must return player object.
-       * @returns {Object}   current resolution object {label: '', sources: []} if used as getter or player object if used as setter
-       */
-      player.currentResolution = function(label, customSourcePicker){
-        if(label == null) { return this.currentResolutionState; }
-
-        // Lookup sources for label
-        if(!this.groupedSrc || !this.groupedSrc.label || !this.groupedSrc.label[label]){
-          return;
-        }
-        var sources = this.groupedSrc.label[label];
-        // Remember player state
-        var currentTime = player.currentTime();
-        var isPaused = player.paused();
-
-        // Hide bigPlayButton
-        if(!isPaused && this.player_.options_.bigPlayButton){
-          this.player_.bigPlayButton.hide();
-        }
-
-        // Change player source and wait for loadeddata event, then play video
-        // loadedmetadata doesn't work right now for flash.
-        // Probably because of https://github.com/videojs/video-js-swf/issues/124
-        // If player preload is 'none' and then loadeddata not fired. So, we need timeupdate event for seek handle (timeupdate doesn't work properly with flash)
-        var handleSeekEvent = 'loadeddata';
-        if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') {
-          handleSeekEvent = 'timeupdate';
-        }
-        player
-          .setSourcesSanitized(sources, label, customSourcePicker || settings.customSourcePicker)
-          .one(handleSeekEvent, function() {
-            player.currentTime(currentTime);
-            player.handleTechSeeked_();
-            if(!isPaused){
-              // Start playing and hide loadingSpinner (flash issue ?)
-              player.play().handleTechSeeked_();
-            }
-            player.trigger('resolutionchange');
-          });
-        return player;
-      };
-
-      /**
-       * Returns grouped sources by label, resolution and type
-       * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
-       */
-      player.getGroupedSrc = function(){
-        return this.groupedSrc;
-      };
-
-      player.setSourcesSanitized = function(sources, label, customSourcePicker) {
-        this.currentResolutionState = {
-          label: label,
-          sources: sources
-        };
-        if(typeof customSourcePicker === 'function'){
-          return customSourcePicker(player, sources, label);
-        }
-        player.src(sources.map(function(src) {
-          return {src: src.src, type: src.type, res: src.res};
-        }));
-        return player;
-      };
-
-      /**
-       * Method used for sorting list of sources
-       * @param   {Object} a - source object with res property
-       * @param   {Object} b - source object with res property
-       * @returns {Number} result of comparation
-       */
-      function compareResolutions(a, b){
-        if(!a.res || !b.res){ return 0; }
-        return (+b.res)-(+a.res);
-      }
-
-      /**
-       * Group sources by label, resolution and type
-       * @param   {Array}  src Array of sources
-       * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
-       */
-      function bucketSources(src){
-        var resolutions = {
-          label: {},
-          res: {},
-          type: {}
-        };
-        src.map(function(source) {
-          initResolutionKey(resolutions, 'label', source);
-          initResolutionKey(resolutions, 'res', source);
-          initResolutionKey(resolutions, 'type', source);
-
-          appendSourceToKey(resolutions, 'label', source);
-          appendSourceToKey(resolutions, 'res', source);
-          appendSourceToKey(resolutions, 'type', source);
-        });
-        return resolutions;
-      }
-
-      function initResolutionKey(resolutions, key, source) {
-        if(resolutions[key][source[key]] == null) {
-          resolutions[key][source[key]] = [];
-        }
-      }
-
-      function appendSourceToKey(resolutions, key, source) {
-        resolutions[key][source[key]].push(source);
-      }
-
-      /**
-       * Choose src if option.default is specified
-       * @param   {Object} groupedSrc {res: { key: [] }}
-       * @param   {Array}  src Array of sources sorted by resolution used to find high and low res
-       * @returns {Object} {res: string, sources: []}
-       */
-      function chooseSrc(groupedSrc, src){
-        var selectedRes = settings['default']; // use array access as default is a reserved keyword
-        var selectedLabel = '';
-        if (selectedRes === 'high') {
-          selectedRes = src[0].res;
-          selectedLabel = src[0].label;
-        } else if (selectedRes === 'low' || selectedRes == null || !groupedSrc.res[selectedRes]) {
-          // Select low-res if default is low or not set
-          selectedRes = src[src.length - 1].res;
-          selectedLabel = src[src.length -1].label;
-        } else if (groupedSrc.res[selectedRes]) {
-          selectedLabel = groupedSrc.res[selectedRes][0].label;
-        }
-
-        return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]};
-      }
-
-      function initResolutionForYt(player){
-        // Map youtube qualities names
-        var _yts = {
-          highres: {res: 1080, label: '1080', yt: 'highres'},
-          hd1080: {res: 1080, label: '1080', yt: 'hd1080'},
-          hd720: {res: 720, label: '720', yt: 'hd720'},
-          large: {res: 480, label: '480', yt: 'large'},
-          medium: {res: 360, label: '360', yt: 'medium'},
-          small: {res: 240, label: '240', yt: 'small'},
-          tiny: {res: 144, label: '144', yt: 'tiny'},
-          auto: {res: 0, label: 'auto', yt: 'auto'}
-        };
-        // Overwrite default sourcePicker function
-        var _customSourcePicker = function(_player, _sources, _label){
-          // Note that setPlayebackQuality is a suggestion. YT does not always obey it.
-          player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt);
-          player.trigger('updateSources');
-          return player;
-        };
-        settings.customSourcePicker = _customSourcePicker;
-
-        // Init resolution
-        player.tech_.ytPlayer.setPlaybackQuality('auto');
-
-        // This is triggered when the resolution actually changes
-        player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(event){
-          for(var res in _yts) {
-            if(res.yt === event.data) {
-              player.currentResolution(res.label, _customSourcePicker);
-              return;
-            }
-          }
-        });
-
-        // We must wait for play event
-        player.one('play', function(){
-          var qualities = player.tech_.ytPlayer.getAvailableQualityLevels();
-          var _sources = [];
-
-          qualities.map(function(q){
-            _sources.push({
-              src: player.src().src,
-              type: player.src().type,
-              label: _yts[q].label,
-              res: _yts[q].res,
-              _yt: _yts[q].yt
-            });
-          });
-
-          player.groupedSrc = bucketSources(_sources);
-          var chosen = {label: 'auto', res: 0, sources: player.groupedSrc.label.auto};
-
-          this.currentResolutionState = {
-            label: chosen.label,
-            sources: chosen.sources
-          };
-
-          player.trigger('updateSources');
-          player.setSourcesSanitized(chosen.sources, chosen.label, _customSourcePicker);
-        });
-      }
-
-      player.ready(function(){
-        if( settings.ui ) {
-          var menuButton = new ResolutionMenuButton(player, settings);
-          player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_);
-          player.controlBar.resolutionSwitcher.dispose = function(){
-            this.parentNode.removeChild(this);
-          };
-        }
-        if(player.options_.sources.length > 1){
-          // tech: Html5 and Flash
-          // Create resolution switcher for videos form <source> tag inside <video>
-          player.updateSrc(player.options_.sources);
-        }
-
-        if(player.techName_ === 'Youtube'){
-         // tech: YouTube
-         initResolutionForYt(player);
-        }
-      });
-
-    };
-
-    // register the plugin
-    videojs.plugin('videoJsResolutionSwitcher', videoJsResolutionSwitcher);
-  })(window, videojs);
+	/* jshint eqnull: true*/
+	/* global require */
+	'use strict';
+	var videojs = null;
+	if(typeof window.videojs === 'undefined' && typeof require === 'function') {
+		videojs = require('video.js');
+	} else {
+		videojs = window.videojs;
+	}
+
+	(function(window, videojs) {
+		var videoJsResolutionSwitcher,
+			defaults = {
+				ui: true
+			};
+
+		/*
+		 * Resolution menu item
+		 */
+		var MenuItem = videojs.getComponent('MenuItem');
+		var ResolutionMenuItem = videojs.extend(MenuItem, {
+			constructor: function(player, options){
+				options.selectable = true;
+				// Sets this.player_, this.options_ and initializes the component
+				MenuItem.call(this, player, options);
+				this.src = options.src;
+
+				player.on('resolutionchange', videojs.bind(this, this.update));
+			}
+		} );
+		ResolutionMenuItem.prototype.handleClick = function(event){
+			MenuItem.prototype.handleClick.call(this,event);
+			this.player_.currentResolution(this.options_.label);
+		};
+		ResolutionMenuItem.prototype.update = function(){
+			var selection = this.player_.currentResolution();
+			this.selected(this.options_.label === selection.label);
+		};
+		MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem);
+
+		/*
+		 * Resolution menu button
+		 */
+		var MenuButton = videojs.getComponent('MenuButton');
+		var ResolutionMenuButton = videojs.extend(MenuButton, {
+			constructor: function(player, options){
+				this.label = document.createElement('span');
+				options.label = 'Quality';
+				// Sets this.player_, this.options_ and initializes the component
+				MenuButton.call(this, player, options);
+				this.el().setAttribute('aria-label','Quality');
+				this.controlText('Quality');
+
+				if(options.dynamicLabel){
+					videojs.addClass(this.label, 'vjs-resolution-button-label');
+					this.el().appendChild(this.label);
+				}else{
+					var staticLabel = document.createElement('span');
+					videojs.addClass(staticLabel, 'vjs-menu-icon');
+					this.el().appendChild(staticLabel);
+				}
+				player.on('updateSources', videojs.bind( this, this.update ) );
+			}
+		} );
+		ResolutionMenuButton.prototype.createItems = function(){
+			var menuItems = [];
+
+			// one large hack to sort the labels
+			var labels = (this.sources && this.sources.label) || [];
+			var sortable = [];
+			for (var l in labels) {
+				sortable.push({'key': l, 'value': labels[l]});
+			}
+			sortable.sort(function(a,b) {
+				var calcPixel = function(item) {
+					var exp = item.res.replace('x','*');
+					if (exp != item.res) {
+						return eval(exp);
+					} else {
+						return 0;
+					}
+				};
+				if (a.value[0].res && b.value[0].res) {
+					return calcPixel(a.value[0])-calcPixel(b.value[0]);
+				}
+				return 0;
+			});
+			sortable.reverse();
+			var labels = {}
+			for (var i=0; i<sortable.length; i++) {
+				if (! labels[sortable[i].key]) {
+					labels[sortable[i].key] = [];
+				}
+				for (var l=0; l<sortable[i].value.length; l++) {
+					labels[sortable[i].key].push(sortable[i].value[l]);
+				}
+			}
+			labels = labels || {};
+			//hack ends here
+
+			for (var key in labels) {
+				if (labels.hasOwnProperty(key)) {
+					menuItems.push(new ResolutionMenuItem(
+						this.player_,
+						{
+							label: key,
+							src: labels[key],
+							selected: key === (this.currentSelection ? this.currentSelection.label : false)
+						})
+					);
+				}
+			}
+			return menuItems;
+		};
+		ResolutionMenuButton.prototype.update = function(){
+			this.sources = this.player_.getGroupedSrc();
+			this.currentSelection = this.player_.currentResolution();
+			this.label.innerHTML = this.currentSelection ? this.currentSelection.label : '';
+			return MenuButton.prototype.update.call(this);
+		};
+		ResolutionMenuButton.prototype.buildCSSClass = function(){
+			return MenuButton.prototype.buildCSSClass.call( this ) + ' vjs-resolution-button';
+		};
+		MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton);
+
+		/**
+		 * Initialize the plugin.
+		 * @param {object} [options] configuration for the plugin
+		 */
+		videoJsResolutionSwitcher = function(options) {
+			var settings = videojs.mergeOptions(defaults, options),
+				player = this,
+				groupedSrc = {},
+				currentSources = {},
+				currentResolutionState = {};
+
+			/**
+			 * Updates player sources or returns current source URL
+			 * @param   {Array}  [src] array of sources [{src: '', type: '', label: '', res: ''}]
+			 * @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources
+			 */
+			player.updateSrc = function(src){
+				//Return current src if src is not given
+				if(!src){ return player.src(); }
+				for (var i=0; i<src.length;i++) {
+					src[i].res = src[i]['data-res'];
+					src[i].label = src[i]['data-label'];
+					src[i].prio = -src[i]['data-player_prio'];
+				}
+
+				// Only add those sources which we can (maybe) play
+				src = src.filter( function(source) {
+					try {
+						return ( player.canPlayType( source.type ) !== '' );
+					} catch (e) {
+						// If a Tech doesn't yet have canPlayType just add it
+						return true;
+					}
+				});
+				//Sort sources
+				this.currentSources = src.sort(compareResolutions);
+				this.groupedSrc = bucketSources(this.currentSources);
+				// Pick one by default
+				var chosen = chooseSrc(this.groupedSrc, this.currentSources);
+				this.currentResolutionState = {
+					label: chosen.label,
+					sources: chosen.sources
+				};
+
+				player.trigger('updateSources');
+				player.setSourcesSanitized(chosen.sources, chosen.label);
+				player.trigger('resolutionchange');
+				return player;
+			};
+
+			/**
+			 * Returns current resolution or sets one when label is specified
+			 * @param {String}   [label]         label name
+			 * @param {Function} [customSourcePicker] custom function to choose source. Takes 2 arguments: sources, label. Must return player object.
+			 * @returns {Object}   current resolution object {label: '', sources: []} if used as getter or player object if used as setter
+			 */
+			player.currentResolution = function(label, customSourcePicker){
+				if(label == null) { return this.currentResolutionState; }
+
+				// Lookup sources for label
+				if(!this.groupedSrc || !this.groupedSrc.label || !this.groupedSrc.label[label]){
+					return;
+				}
+				var sources = this.groupedSrc.label[label];
+				// Remember player state
+				var currentTime = player.currentTime();
+				var isPaused = player.paused();
+
+				// Hide bigPlayButton
+				if(!isPaused && this.player_.options_.bigPlayButton){
+					this.player_.bigPlayButton.hide();
+				}
+
+				// Change player source and wait for loadeddata event, then play video
+				// loadedmetadata doesn't work right now for flash.
+				// Probably because of https://github.com/videojs/video-js-swf/issues/124
+				// If player preload is 'none' and then loadeddata not fired. So, we need timeupdate event for seek handle (timeupdate doesn't work properly with flash)
+				var handleSeekEvent = 'loadeddata';
+				if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') {
+					handleSeekEvent = 'timeupdate';
+				}
+				player
+					.setSourcesSanitized(sources, label, customSourcePicker || settings.customSourcePicker)
+					.one(handleSeekEvent, function() {
+						player.currentTime(currentTime);
+						player.handleTechSeeked_();
+						if(!isPaused){
+							// Start playing and hide loadingSpinner (flash issue ?)
+							player.play().handleTechSeeked_();
+						}
+						player.trigger('resolutionchange');
+					});
+				return player;
+			};
+
+			/**
+			 * Returns grouped sources by label, resolution and type
+			 * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
+			 */
+			player.getGroupedSrc = function(){
+				return this.groupedSrc;
+			};
+
+			player.setSourcesSanitized = function(sources, label, customSourcePicker) {
+				this.currentResolutionState = {
+					label: label,
+					sources: sources
+				};
+				if(typeof customSourcePicker === 'function'){
+					return customSourcePicker(player, sources, label);
+				}
+				player.src(sources.map(function(src) {
+					return {src: src.src, type: src.type, res: src.res};
+				}));
+				return player;
+			};
+
+			/**
+			 * Method used for sorting list of sources
+			 * @param   {Object} a - source object with res property
+			 * @param   {Object} b - source object with res property
+			 * @returns {Number} result of comparation
+			 */
+			function compareResolutions(a, b){
+				if(a.prio && b.prio){
+					return (+b.prio)-(+a.prio)
+				}
+				if(!a.res || !b.res){ return 0; }
+				return (+b.res)-(+a.res);
+			}
+
+			/**
+			 * Group sources by label, resolution and type
+			 * @param   {Array}  src Array of sources
+			 * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
+			 */
+			function bucketSources(src){
+				var resolutions = {
+					label: {},
+					res: {},
+					type: {}
+				};
+				src.map(function(source) {
+					initResolutionKey(resolutions, 'label', source);
+					initResolutionKey(resolutions, 'res', source);
+					initResolutionKey(resolutions, 'type', source);
+
+					appendSourceToKey(resolutions, 'label', source);
+					appendSourceToKey(resolutions, 'res', source);
+					appendSourceToKey(resolutions, 'type', source);
+				});
+				return resolutions;
+			}
+
+			function initResolutionKey(resolutions, key, source) {
+				if(resolutions[key][source[key]] == null) {
+					resolutions[key][source[key]] = [];
+				}
+			}
+
+			function appendSourceToKey(resolutions, key, source) {
+				resolutions[key][source[key]].push(source);
+			}
+
+			/**
+			 * Choose src if option.default is specified
+			 * @param   {Object} groupedSrc {res: { key: [] }}
+			 * @param   {Array}  src Array of sources sorted by resolution used to find high and low res
+			 * @returns {Object} {res: string, sources: []}
+			 */
+			function chooseSrc(groupedSrc, src){
+				var selectedRes = settings['default']; // use array access as default is a reserved keyword
+				var selectedLabel = '';
+				if (selectedRes === 'high') {
+					selectedRes = src[0].res;
+					selectedLabel = src[0].label;
+				} else if (selectedRes === 'low' || selectedRes == null || !groupedSrc.res[selectedRes]) {
+					// Select low-res if default is low or not set
+					selectedRes = src[src.length - 1].res;
+					selectedLabel = src[src.length -1].label;
+				} else if (groupedSrc.res[selectedRes]) {
+					selectedLabel = groupedSrc.res[selectedRes][0].label;
+				}
+
+				return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]};
+			}
+
+			function initResolutionForYt(player){
+				// Map youtube qualities names
+				var _yts = {
+					highres: {res: 1080, label: '1080', yt: 'highres'},
+					hd1080: {res: 1080, label: '1080', yt: 'hd1080'},
+					hd720: {res: 720, label: '720', yt: 'hd720'},
+					large: {res: 480, label: '480', yt: 'large'},
+					medium: {res: 360, label: '360', yt: 'medium'},
+					small: {res: 240, label: '240', yt: 'small'},
+					tiny: {res: 144, label: '144', yt: 'tiny'},
+					auto: {res: 0, label: 'auto', yt: 'auto'}
+				};
+				// Overwrite default sourcePicker function
+				var _customSourcePicker = function(_player, _sources, _label){
+					// Note that setPlayebackQuality is a suggestion. YT does not always obey it.
+					player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt);
+					player.trigger('updateSources');
+					return player;
+				};
+				settings.customSourcePicker = _customSourcePicker;
+
+				// Init resolution
+				player.tech_.ytPlayer.setPlaybackQuality('auto');
+
+				// This is triggered when the resolution actually changes
+				player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(event){
+					for(var res in _yts) {
+						if(res.yt === event.data) {
+							player.currentResolution(res.label, _customSourcePicker);
+							return;
+						}
+					}
+				});
+
+				// We must wait for play event
+				player.one('play', function(){
+					var qualities = player.tech_.ytPlayer.getAvailableQualityLevels();
+					var _sources = [];
+
+					qualities.map(function(q){
+						_sources.push({
+							src: player.src().src,
+							type: player.src().type,
+							label: _yts[q].label,
+							res: _yts[q].res,
+							_yt: _yts[q].yt
+						});
+					});
+
+					player.groupedSrc = bucketSources(_sources);
+					var chosen = {label: 'auto', res: 0, sources: player.groupedSrc.label.auto};
+
+					this.currentResolutionState = {
+						label: chosen.label,
+						sources: chosen.sources
+					};
+
+					player.trigger('updateSources');
+					player.setSourcesSanitized(chosen.sources, chosen.label, _customSourcePicker);
+				});
+			}
+
+			player.ready(function(){
+				if( settings.ui ) {
+					var menuButton = new ResolutionMenuButton(player, settings);
+					player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_);
+					player.controlBar.resolutionSwitcher.dispose = function(){
+						this.parentNode.removeChild(this);
+					};
+				}
+				if(player.options_.sources.length > 1){
+					// tech: Html5 and Flash
+					// Create resolution switcher for videos form <source> tag inside <video>
+					player.updateSrc(player.options_.sources);
+				}
+
+				if(player.techName_ === 'Youtube'){
+					// tech: YouTube
+					initResolutionForYt(player);
+				}
+			});
+
+		};
+
+		// register the plugin
+		videojs.plugin('videoJsResolutionSwitcher', videoJsResolutionSwitcher);
+	})(window, videojs);
 })();
diff --git a/stats.py b/stats.py
index 8ef31660c07b1e13c41454b9ee5a716a23fbfa1c..d57e0fc8a09086d6ef7e00a3f05b2dda1f7f6b12 100644
--- a/stats.py
+++ b/stats.py
@@ -2,12 +2,23 @@ from server import *
 import json
 from jobs import date_json_handler
 from hashlib import md5
+from datetime import datetime
 
 @app.route('/internal/stats')
+@app.route('/internal/stats/<semester>')
 @register_navbar('Statistiken', icon='stats')
 @mod_required
 def stats():
-	return render_template('stats.html')
+	semester = query('SELECT DISTINCT semester from courses WHERE semester != ""');
+	for s in semester:
+		year = int(s['semester'][0:4])
+		if s['semester'].endswith('ss'):
+			s['from'] = datetime(year,4,1)
+			s['to'] =   datetime(year,10,1)
+		if s['semester'].endswith('ws'):
+			s['from'] = datetime(year,10,1)
+			s['to'] =   datetime(year+1,4,1)
+	return render_template('stats.html',semester=semester,filter=request.args.get('filter'))
 
 statsqueries = {}
 statsqueries['formats_views'] = "SELECT formats.description AS labels, count(DISTINCT log.id) AS `values` FROM log JOIN videos ON (videos.id = log.video) JOIN formats ON (formats.id = videos.video_format) GROUP BY formats.id"
@@ -18,6 +29,7 @@ statsqueries['organizer_courses'] = "SELECT courses.organizer AS labels, count(c
 statsqueries['categories_lectures'] = "SELECT courses.subject AS labels, count(lectures.id) AS `values` FROM lectures JOIN courses ON (courses.id = lectures.course_id) WHERE lectures.visible GROUP BY courses.subject ORDER BY `values` DESC LIMIT 100"
 statsqueries['lecture_views'] = "SELECT lectures.time AS x, count(DISTINCT log.id) AS y FROM log JOIN videos ON (videos.id = log.video) JOIN lectures ON (lectures.id = videos.lecture_id) WHERE (lectures.course_id = ?) GROUP BY lectures.id ORDER BY lectures.time"
 statsqueries['live_views'] = "SELECT hlslog.segment AS x, COUNT(DISTINCT hlslog.id) AS y FROM hlslog WHERE hlslog.lecture = ? GROUP BY hlslog.segment ORDER BY hlslog.segment"
+statsqueries['lecture_totalviews'] = "SELECT 42"
 
 def plotly_date_handler(obj):
 	return obj.strftime("%Y-%m-%d %H:%M:%S")
@@ -45,10 +57,52 @@ def stats_viewsperday(req, param=""):
 	query_expr = 'SELECT date, trace, value AS y FROM logcache WHERE req = "%s" AND param = ? UNION SELECT * FROM (%s) AS cachetmp'
 	date_subexpr = 'SELECT CASE WHEN MAX(date) IS NULL THEN "2000-00-00" ELSE MAX(date) END AS t FROM `logcache` WHERE req = "%s" AND param = ?'
 	queries = {
-		'lecture': 'SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN formats ON formats.id = videos.video_format WHERE log.date > %T AND videos.lecture_id = ? GROUP BY log.date, videos.video_format UNION SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video WHERE log.date > %T AND videos.lecture_id = ? GROUP BY log.date',
-		'course': 'SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN lectures ON lectures.id = videos.lecture_id JOIN formats ON formats.id = videos.video_format WHERE log.date > %T AND lectures.course_id = ? GROUP BY log.date, videos.video_format UNION SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN lectures ON lectures.id = videos.lecture_id WHERE log.date > %T AND lectures.course_id = ? GROUP BY log.date',
-		'global': 'SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN formats ON formats.id = videos.video_format WHERE log.date > %T GROUP BY log.date, videos.video_format UNION SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y FROM log WHERE log.date > %T GROUP BY log.date',
-		'courses': 'SELECT log.date AS date, courses.handle AS trace, COUNT(DISTINCT log.id) AS y FROM log JOIN videos ON videos.id = log.video JOIN lectures ON lectures.id = videos.lecture_id JOIN courses ON courses.id = lectures.course_id WHERE log.date > %T GROUP BY log.date, courses.id'
+		'lecture': # views per day per lecture (split per format)
+			'''SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y
+			FROM log
+			JOIN videos ON videos.id = log.video
+			JOIN formats ON formats.id = videos.video_format
+			WHERE log.date > %T AND videos.lecture_id = ?
+			GROUP BY log.date, videos.video_format
+			UNION
+				SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y
+				FROM log JOIN videos ON videos.id = log.video
+				WHERE log.date > %T AND videos.lecture_id = ? GROUP BY log.date''',
+
+		'course': # views per day per format for a single course
+			'''SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y
+			FROM log JOIN videos ON videos.id = log.video
+			JOIN lectures ON lectures.id = videos.lecture_id
+			JOIN formats ON formats.id = videos.video_format
+			WHERE log.date > %T AND lectures.course_id = ?
+			GROUP BY log.date, videos.video_format
+			UNION
+				SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y
+				FROM log
+				JOIN videos ON videos.id = log.video
+				JOIN lectures ON lectures.id = videos.lecture_id
+				WHERE log.date > %T AND lectures.course_id = ?
+				GROUP BY log.date''',
+
+		'global': # views per format per day (split per format)
+			'''SELECT log.date AS date, formats.description AS trace, COUNT(DISTINCT log.id) AS y
+			FROM log
+			JOIN videos ON videos.id = log.video
+			JOIN formats ON formats.id = videos.video_format
+			WHERE log.date > %T GROUP BY log.date, videos.video_format
+			UNION
+				SELECT log.date AS date, "total" AS trace, COUNT(DISTINCT log.id) AS y
+				FROM log
+				WHERE log.date > %T
+				GROUP BY log.date''',
+
+		'courses': # views per course per day
+			'''SELECT log.date AS date, courses.handle AS trace, COUNT(DISTINCT log.id) AS y
+			FROM log JOIN videos ON videos.id = log.video
+			JOIN lectures ON lectures.id = videos.lecture_id
+			JOIN courses ON courses.id = lectures.course_id
+			WHERE log.date > %T
+			GROUP BY log.date, courses.id'''
 	}
 	expr = queries[req].replace('%T', '"'+query(date_subexpr%('viewsperday.'+req), param)[0]['t']+'"')
 	params = [param]*expr.count('?')
@@ -71,6 +125,12 @@ def stats_viewsperday(req, param=""):
 		data[row['date']][row['trace']] = row['y']
 	end = date.today()
 	res = [{'name': trace, 'x': [], 'y': []} for trace in traces]
+
+	filter = request.args.get('filter')
+	if filter:
+		filter = filter.split('-')
+		start = date.fromtimestamp(int(filter[0]))
+		end = date.fromtimestamp(int(filter[1]))
 	while start and start <= end:
 		for trace in res:
 			trace['x'].append(start)
diff --git a/templates/base.html b/templates/base.html
index be2177fbb0691cc6c4bbdca3977c6791585fe614..b41321a683d245a991eb284cb37bfa23991f172d 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -42,8 +42,7 @@
 		<nav class="hidden-print navbar navbar-default navbar-static-top" {% if config.DEBUG %} style="background-color: red" {% endif %} >
 			<div class="container-fluid">
 				<div class="navbar-header">
-					<button type="button" class="navbar-toggle" data-toggle="collapse" 
-						 data-target=".navbar-collapse">
+					<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
 						<span class="sr-only">Toggle navigation</span>
 						<span class="icon-bar"></span>
 						<span class="icon-bar"></span>
@@ -52,10 +51,26 @@
 					<a class="navbar-brand" href="/" style="padding: 3px;">
 						<img alt="Brand" src="{{url_for('static', filename='logo.png')}}" style="height: 44px; width: 44px" >
 					</a>
+					<ul class="nav nav-pills" style="margin-top: 5px; padding-left: 40px;">
+						{% for endpoint, caption, iconlib, gly, visible in navbar if visible %}
+							<li{% if endpoint == request.endpoint %} class="active"{% endif %}>
+								<a href="{{ url_for(endpoint) }}" style="padding: 10px 6px;">
+									{% if gly != '' %}
+										{% if iconlib == 'bootstrap' %}
+											<span aria-hidden="true" class="glyphicon glyphicon-{{ gly }}"></span> 
+										{% elif iconlib == 'fa' %}
+											<span aria-hidden="true" class="fa fa-{{ gly }}"></span> 
+										{% endif %}
+									{{ caption }}
+									{% endif %}
+								</a>
+							</li>
+						{% endfor %}
+					</ul>
 				</div>
 				<div class="collapse navbar-collapse">
 					<ul class="nav nav-pills" style="margin-top: 5px;">
-						{% for endpoint, caption, iconlib, gly, visible in navbar if visible or ismod() %}
+						{% for endpoint, caption, iconlib, gly, visible in navbar if (not visible) and ismod() %}
 							<li{% if endpoint == request.endpoint %} class="active"{% endif %}>
 								<a href="{{ url_for(endpoint) }}">
 									{% if gly != '' %}
@@ -70,7 +85,7 @@
 							</li>
 						{% endfor %}
 
-						<li class="col-xs-12 col-sm-4 pull-right">
+						<li class="col-xs-9 col-sm-4 pull-right">
 							<form action="{{ url_for('search') }}" role="search">
 								<div class="input-group" style="margin-top: 3px">
 									<input class="form-control" type="text" name="q" placeholder="Search" value="{{ searchtext }}">
@@ -187,7 +202,11 @@
 	{% endif %}
 	<script>
 		$( function () {
-			$('[data-toggle="tooltip"]').tooltip({ 'trigger': 'hover'  });
+			$('[data-toggle="tooltip"]').tooltip(
+			{ 
+				trigger: 'hover',
+				html: true
+			});
 		});	
 	</script>
 	</body>
diff --git a/templates/course.html b/templates/course.html
index 6bbb9e915a58af67c96861574ed0937f2ddd3f3a..0c85dd20d5618ac18f72d057f6bcc0d4509d53a6 100644
--- a/templates/course.html
+++ b/templates/course.html
@@ -35,7 +35,7 @@
 				</tbody>
 			</table>
 		</div>
-		{% if ismod() %}
+	{% if ismod() %}
 		<div class="col-xs-12" style="margin-top: 20px">
 			<table class="table-condensed table-top-aligned">
 				<tbody>
@@ -51,14 +51,45 @@
 				</tbody>
 			</table>
 		</div>
-		<div class="col-xs-6 plot-view" data-url="{{url_for('stats_viewsperday', req="course", param=course.id)}}"></div>
-		<div class="col-xs-6 plot-view" data-type="bar" data-url="{{url_for('stats_generic', req="lecture_views", param=course.id)}}"></div>
-		{% endif %}
+	{% endif %}
 	</div>
 </div>
+	
+{% if ismod() %}
+<div class="panel panel-default">
+        <div class="panel-heading">
+		<a data-toggle="collapse" href="#statspanel" class="plotlyresize"><h1 class="panel-title">Statistiken</h1></a>
+	</div>
+	<div class="row panel-body collapse out panel-collapse" id="statspanel">
+		<div class="col-md-6 col-xs-12">
+			<p class="text-center">Zuschauer pro Tag</p>
+			<div class="plot-view" data-url="{{url_for('stats_viewsperday', req="course", param=course.id)}}"></div>
+		</div>
+		<div class="col-md-6 col-xs-12">
+			<p class="text-center">Zuschauer pro Termin</p>
+			<div class="plot-view" data-type="bar" data-url="{{url_for('stats_generic', req="lecture_views", param=course.id)}}"></div>
+		</div>
+	</div>
+</div>
+{% endif %}
+
 <div class="panel panel-default">
 	<div class="panel-heading">
-		<h1 class="panel-title">Videos{% if ismod() %} <a class="btn btn-default" style="margin-right: 5px;" href="{{ url_for('create', table='lectures', time=datetime.now(), title='Noch kein Titel', visible='0', course_id=course.id, ref=request.url) }}">Neuer Termin</a><a class="btn btn-default" style="margin-right: 5px;" href="{{url_for('list_import_sources', id=course['id'])}}">Campus Import</a>{% endif %} <a class="fa fa-rss-square pull-right" aria-hidden="true" href="{{url_for('feed', handle=course.handle)}}" style="text-decoration: none"></a> </h1>
+		<h1 class="panel-title">Videos
+			{% if ismod() %}
+				<a class="btn btn-default" style="margin-right: 5px;" href="{{ url_for('create', table='lectures', time=datetime.now(), title='Noch kein Titel', visible='0', course_id=course.id, ref=url_for('course', id=course.id)) }}">Neuer Termin</a>
+				<a class="btn btn-default" style="margin-right: 5px;" href="{{url_for('list_import_sources', id=course['id'])}}">Campus Import</a>
+			{% endif %}
+			<ul class="list-inline pull-right">
+				<li>
+					<a class="fa fa-rss-square" aria-hidden="true" href="{{url_for('feed', handle=course.handle)}}" style="text-decoration: none"></a>
+				</li>
+			{% if ismod() %}
+				<li>
+					<a class="fa fa-calendar" aria-hidden="true" href="{{url_for('ical_course', course=course.handle)}}" style="text-decoration: none"></a>
+				</li>
+			{% endif %}
+		</h1>
 	</div>
 	<ul class="list-group lectureslist">
 		{% for l in lectures %}
@@ -66,4 +97,38 @@
 		{% endfor %}
 	</ul>
 </div>
+
+<script>
+$.ajax({
+	method: "GET",
+	url: "{{url_for('stats_generic', req="lecture_views", param=course.id)}}",
+	dataType: "json",
+	error: function() {
+		var counter = $(".viewcounter");
+		for (var i=0; i<counter.length; i++) {
+			$(counter[i]).text("0");
+		}
+	},
+	success: function (traces) {
+		var dates={};
+		var t = traces[0];
+		if (!t.x) {
+			return;
+		}
+		for (var i=0; i<t.x.length; i++) {
+			dates[t.x[i]] = t.y[i];
+		}
+		var counter = $(".viewcounter");
+		for (var i=0; i<counter.length; i++) {
+			$(counter[i]).text(dates[$(counter[i]).data("lecturedate")]);
+		}
+		var counter = $(".viewcounter");
+		for (var i=0; i<counter.length; i++) {
+			if ($(counter[i]).text() == "loading...") {
+				$(counter[i]).text("0");
+			}
+		}
+	}
+});
+</script>
 {% endblock %}
diff --git a/templates/courses.html b/templates/courses.html
index 70c0dd10b2fbb70d9b03065bb322e40fb6bd4572..c1c461593f2efbfa286b2865efa0232556c80ccd 100644
--- a/templates/courses.html
+++ b/templates/courses.html
@@ -8,6 +8,9 @@
 				 <a class="fa fa-rss-square btn btn-default" aria-hidden="true" href="{{url_for('courses_feed')}}" style="text-decoration: none"></a>
 			</li>
 			{% if ismod() %} 
+			<li>
+				<a class="fa fa-calendar btn btn-default" aria-hidden="true" href="{{url_for('ical_all')}}" style="text-decoration: none"></a>
+			</li>
 			<li>
 				{% set newhandle = 'new'+(randint(0,1000)|string) %}
 				<a class="btn btn-default" href="{{ url_for('create', table='courses', handle=newhandle, title='Neue Veranstaltung', responsible=session.user.givenName, ref=url_for('course', handle=newhandle)) }}">Neue Veranstaltung</a>
@@ -56,7 +59,8 @@
 		<div class="panel-heading">
 			<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion-{{ g.grouper|tagid }}" href="#{{g.grouper|tagid}}" style="color: #222;">
 				{% if groupedby == 'semester' %}
-				<h1 class="panel-title">{{g.grouper|semester(long=True)}} ({{g.list|length}} Veranstaltungen)</h1>
+				<h1 class="panel-title">{{g.grouper|semester(long=True)}} ({{g.list|length}} Veranstaltungen)
+				</h1>
 				{% else %}
 				<h1 class="panel-title">{{g.grouper}}</h1>
 				{% endif %}
diff --git a/templates/lecture.html b/templates/lecture.html
index c7d38a3bdf7b8d210fdfb7da9a84a16d36c30199..d703dc02470fd0881e10b78a788a8305837b4983 100644
--- a/templates/lecture.html
+++ b/templates/lecture.html
@@ -25,7 +25,7 @@
 	<div class="panel-body">
 		<div class="row" style="padding: 0px;">
 			<div class="col-xs-12" style="padding-bottom: 5px;">
-				<a href="{{url_for('course', handle=course.handle)}}#lecture-{{lecture.id}}" class="btn btn-default" >Zur Veranstaltungsseite</a>
+				<a href="{{url_for('course', handle=course.handle)}}#lecture-{{lecture.id}}" class="btn btn-default" ><span class="fa fa-chevron-circle-left" aria-hidden="true"></span> Zur Veranstaltungsseite</a>
 				<ul class="list-inline pull-right">
 					<li>{{ video_embed_btn(lecture.id, course=course.handle) }}</li>
 					<li class="dropdown">{{ video_download_btn(videos) }}</li>
@@ -39,7 +39,6 @@
 			<div class="col-xs-12" style="padding-top: 20px">
 				<p>{{ moderator_editor(['lectures',lecture.id,'comment'], lecture.comment) }}</p>
 			</div>
-			{% if (chapters|length > 0) or ismod() %}
 			<div class="col-xs-12 table-responsive" style="padding-top: 10px;">
 				<p>Kapitel:
 				<button class="btn btn-default" id="hintnewchapter">{% if ismod() %}Neues Kapitel{% else %}Kapitelmarker vorschlagen{% endif %}</button>
@@ -71,14 +70,29 @@
 				{% endfor %}
 				</table>
 			</div>
-			{% endif %}
-			{% if ismod() %}
-				<div class="col-xs-12 plot-view" data-url="{{url_for('stats_viewsperday', req="lecture", param=lecture.id)}}"></div>
-				<div class="col-xs-12 plot-view" data-url="{{url_for('stats_generic', req="live_views", param=lecture.id)}}"></div>
-			{% endif %}
 		</div>
 	</div>
 </div>
+
+{% if ismod() %}
+<div class="panel panel-default">
+        <div class="panel-heading">
+                <a data-toggle="collapse" href="#statspanel" class="plotlyresize"><h1 class="panel-title">Statistiken</h1></a>
+        </div>
+        <div class="row panel-body collapse out panel-collapse" id="statspanel">
+                <div class="col-md-6 col-xs-12">
+                        <p class="text-center">Zuschauer pro Tag</p>
+			<div class="plot-view" data-url="{{url_for('stats_viewsperday', req="lecture", param=lecture.id)}}"></div>
+                </div>
+                <div class="col-md-6 col-xs-12">
+                        <p class="text-center">Zuschauer im Livestream</p>
+			<div class="plot-view" data-url="{{url_for('stats_generic', req="live_views", param=lecture.id)}}"></div>
+                </div>
+        </div>
+</div>
+{% endif %}
+
+
 <script>
 function hintchapterclick (src) {
 	$.ajax({
diff --git a/templates/macros.html b/templates/macros.html
index cc40485bcd93ec93b64cac819e04037b71cc04fa..0c87bac504b0d0ac2b792a8206679fec194c28aa 100644
--- a/templates/macros.html
+++ b/templates/macros.html
@@ -50,8 +50,9 @@
 
 {% macro player(lecture, videos, msgs) %}
 <video id="videoplayer" style="width: 100%" class="video-js vjs-default-skin vjs-big-play-centered" width="640" height="320" controls data-wasnotplayed="1"  data-setup='{ "language":"de", "plugins" : {"hotkeys": {"seekStep": 15, "enableVolumeScroll": false, "alwaysCaptureHotkeys": true}, "videoJsResolutionSwitcher": { "ui": true, "default": "720p", "dynamicLabel": false } }, "customControlsOnMobile": true, "playbackRates": [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4] }'>
-	{% for v in videos|sort(attribute='player_prio', reverse=True) %}
-		<source type="{{ v.mimetype }}" src="{{ config.VIDEOPREFIX }}/{{ v.path }}" label="{{ v.format_description }}"/>
+	{% for v in videos|sort(attribute='formats.player_prio', reverse=True) %}
+	<source type="{{ v.formats.mimetype }}" src="{{ config.VIDEOPREFIX }}/{{ v.path }}" data-label="{{ v.formats.description }}" data-res="{{v.formats.resolution}}" data-aspect="{{v.formats.aspect}}" data-player_prio="{{v.formats.player_prio}}"/>
+	{{ v|safe }}
 	{% endfor %}
 	<track srclang="de" kind="chapters" src="{{ url_for('chapters',lectureid=lecture.id) }}" />
 </video>
@@ -162,8 +163,8 @@ $(function() {
 {% if not ismod() %}
 <span class="btn btn-default dropdown-toggle{% if not videos|selectattr('downloadable')|list and not ismod() %} disabled{% endif %}" type="button" data-toggle="dropdown">Download <span class="caret"></span></span>
 <ul class="dropdown-menu">
-	{% for v in videos|sort(attribute='prio', reverse=True) if (v.downloadable or ismod() ) %}
-	<li><a href="{{ config.VIDEOPREFIX }}/{{v.path}}">{{v.format_description}} ({{v.file_size|filesizeformat(true)}})</a></li>
+	{% for v in videos|sort(attribute='formats.prio', reverse=True) if (v.downloadable or ismod() ) %}
+	<li><a href="{{ config.VIDEOPREFIX }}/{{v.path}}">{{v.formats.description}} ({{v.file_size|filesizeformat(true)}})</a></li>
 	{% endfor %}
 </ul>
 {% endif %}
@@ -171,8 +172,8 @@ $(function() {
 	<noscript> 
 {% endif %}
 <ul class="pull-right list-unstyled" style="margin-left:10px;">
-{% for v in videos|sort(attribute='prio', reverse=True) if (v.downloadable or ismod() ) %}
-	<li>{{moderator_delete(['videos',v.id,'deleted'])}} {{ moderator_checkbox(['videos',v.id,'visible'], v.visible) }} <a href="{{ config.VIDEOPREFIX }}/{{v.path}}">{{v.format_description}} ({{v.file_size|filesizeformat(true)}})</a></li>
+{% for v in videos|sort(attribute='formats.prio', reverse=True) if (v.downloadable or ismod() ) %}
+	<li>{{moderator_delete(['videos',v.id,'deleted'])}} {{ moderator_checkbox(['videos',v.id,'visible'], v.visible) }} <a href="{{ config.VIDEOPREFIX }}/{{v.path}}">{{v.formats.description}} ({{v.file_size|filesizeformat(true)}})</a></li>
 {% endfor %}
 </ul>
 {% if not ismod() %}
@@ -226,16 +227,25 @@ $('#embedcodebtn').popover(
 				<li>Hörsaal: {{ moderator_editor(['lectures',lecture.id,'place'], lecture.place) }} </li>
 				{% endif %}
 			</ul>
-			<ul class="list-inline col-sm-4 col-xs-12">
-				<li class="dropdown">
-					{{ video_download_btn(videos) }}
-				</li>
-				<li class="pull-right">
-					{{ moderator_permissioneditor('lecture', lecture.id, lecture.perm, global_permissions) }}
+			<ul class="col-sm-4 col-xs-12 list-unstyled">
+				<li>
+					<ul class="list-inline">
+						<li class="dropdown">
+							{{ video_download_btn(videos) }}
+						</li>
+						<li class="pull-right">
+							{{ moderator_permissioneditor('lecture', lecture.id, lecture.perm, global_permissions) }}
+						</li>
+						<li class="pull-right">
+							{{ moderator_delete(['lectures',lecture.id,'deleted']) }}
+						</li>
+					</ul>
 				</li>
+				{% if ismod() %}
 				<li class="pull-right">
-					{{ moderator_delete(['lectures',lecture.id,'deleted']) }}
+					<p>Abrufe: <span data-lectureid="{{ lecture.id }}" data-lecturedate="{{ lecture.time }}" class="viewcounter">loading...</span></p>
 				</li>
+				{% endif %}
 			</ul>
 		{% else %}
 			<div class="col-sm-2 col-xs-12">
@@ -257,7 +267,7 @@ $('#embedcodebtn').popover(
 {% macro moderator_editor (path,value,reload=false) %}
 	{% if ismod() %}
 	<span class="moderator_editor" data-path="{{path|join('.')}}" data-reload="{{ reload|int }}" >
-		<a class="moderator_editor_sign btn btn-default" title="{{path|join('.')}}" data-toggle="tooltip" tabindex="0" style="padding: 3px; margin-right: 5px;">
+		<a class="moderator_editor_sign btn btn-default" title="{{path|join('.')}}{{ path|join('.')|getfielddescription }}" data-toggle="tooltip" tabindex="0" style="padding: 3px; margin-right: 5px;">
 			<span class="glyphicon glyphicon-pencil"></span>
 		</a>
 		<span class="moderator_editor_value">{{ value|fixnl|safe }}</span>
@@ -269,13 +279,13 @@ $('#embedcodebtn').popover(
 
 {% macro moderator_checkbox (path,value) %}
 	{% if ismod() %}
-	<input title="{{path|join('.')}}" data-toggle="tooltip" type="checkbox" data-path="{{path|join('.')}}" {% if value %} checked {% endif %} onchange="moderator.editor.changeboxclick(this)"/>
+	<input title="{{path|join('.')}}{{ path|join('.')|getfielddescription }}" data-toggle="tooltip" type="checkbox" data-path="{{path|join('.')}}" {% if value %} checked {% endif %} onchange="moderator.editor.changeboxclick(this)"/>
 	{% endif %}
 {% endmacro %}
 
 {% macro moderator_delete (path) %}
 	{% if ismod() %}
-	<button class="btn btn-default" style="background-color: red;" data-path="{{path|join('.')}}" onclick="moderator.editor.deletebtnclick(this)">
+	<button class="btn btn-default" style="background-color: red;" data-path="{{path|join('.')}}" onclick="moderator.editor.deletebtnclick(this)" >
 		<span class="glyphicon glyphicon-trash"></span>
 	</button>
 	{% endif %}
diff --git a/templates/stats.html b/templates/stats.html
index c7cb96aab7b11b643767610fe6534465afd8ae93..953d5e6656e839aefa48c341ebc2b207e360b779 100644
--- a/templates/stats.html
+++ b/templates/stats.html
@@ -3,21 +3,59 @@
 <div class="panel-group">
 	<div class="panel panel-default">
 		<div class="panel-heading">
-			<h1 class="panel-title">Statistiken</h1>
+			<h1 class="panel-title">Gesamt</h1>
 		</div>
 		<div class="panel-body">
 			<div class="row col-xs-12">
-				<div class="col-xs-12 col-md-6 plot-view" data-url="{{url_for('stats_generic', req="course_count")}}"></div>
-				<div class="col-xs-12 col-md-6 plot-view" data-url="{{url_for('stats_generic', req="lectures_count")}}"></div>
-				<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="categories_courses")}}"></div>
-				<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="categories_lectures")}}"></div>
-				<!--<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="organizer_courses")}}"></div>-->
-				<!--<div class="col-xs-12 col-md-6 plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="formats_views")}}"></div>-->
-				<div class="col-xs-12 plot-view" data-url="{{url_for('stats_viewsperday', req="global")}}"></div>
-				<div class="col-xs-12 plot-view" data-url="{{url_for('stats_viewsperday', req="courses")}}"></div>
+				<div class="col-xs-12 col-md-6">
+					<p class="text-center">Veranstaltungen pro Semester</p>
+					<div class="plot-view" data-url="{{url_for('stats_generic', req="course_count")}}"></div>
+				</div>
+				<div class="col-xs-12 col-md-6">
+					<p class="text-center">Vorlesungen pro Semester</p>
+					<div class="plot-view" data-url="{{url_for('stats_generic', req="lectures_count")}}"></div>
+				</div>
+				<div class="col-xs-12 col-md-6">
+					<p class="text-center">Veranstaltungen pro Kategorie</p>
+					<div class="plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="categories_courses")}}"></div>
+				</div>
+				<div class="col-xs-12 col-md-6">
+					<p class="text-center">Vorlesungen pro Kategorie</p>
+					<div class="plot-view" data-type="pie" data-url="{{url_for('stats_generic', req="categories_lectures")}}"></div>
+				</div>
+				<!--<div class="col-xs-12 col-md-12 plot-view" style="height: 1200px;" data-type="pie" data-url="{{url_for('stats_generic', req="organizer_courses")}}"></div>!-->
+			</div>
+		</div>
+	</div>
+	<div class="panel panel-default">
+		<div class="panel-heading">
+			<span class="panel-title"><a name="semesterstats"></a>Semester <select id="semesterselect" name="semester"><option value="">alle</option></select></span>
+		</div>
+		<div class="panel-body"  >
+			<div class=col-xs-12">
+				<p class="text-center">Zuschauer pro Veranstaltung</p>
+				<div class="plot-view" data-url="{{url_for('stats_viewsperday', req="courses", filter=filter)}}"></div>
+			</div>
+			<div class=col-xs-12">
+				<p class="text-center">Zuschauer pro Format</p>
+				<div class="plot-view" data-url="{{url_for('stats_viewsperday', req="global", filter=filter)}}"></div>
 			</div>
 		</div>
 	</div>
 </div>
+<script>
+$( document ).ready(function () {
+	{% for s in semester if s.semester != '' %}
+	$("#semesterselect").append('<option value="{{ s.from.timestamp()|int }}-{{ s.to.timestamp()|int }}">{{ s.semester }}</option>');
+	{% endfor %}
+	{% if filter %}
+	$("#semesterselect").val("{{ filter }}")
+	{% else %}
+	$("#semesterselect").val("")
+	{% endif %}
+	$("#semesterselect").on("change", function () {
+		window.location.href="{{ url_for('stats') }}?filter="+$("#semesterselect").val()+"#semesterstats";
+	});
+});
 </script>
 {% endblock %}
diff --git a/templates/timetable.html b/templates/timetable.html
index f0dfa4d9f11142df8bb12fd797581fa659c9ca4d..319e322d1ca3b7d5ffe25773c80429e85ce379f6 100644
--- a/templates/timetable.html
+++ b/templates/timetable.html
@@ -3,7 +3,9 @@
 <div class="panel-group" id="accordion">
 	<div class="panel panel-default">
 		<div class="hidden-print panel-heading">
-			<h1 class="panel-title">Drehplan</h1>
+			<h1 class="panel-title">Drehplan
+				<a class="pull-right fa fa-calendar" aria-hidden="true" href="{{url_for('ical_all')}}" style="text-decoration: none"></a>
+			</h1>
 		</div>
 		<div class="row hidden-print">
 			<div  style="margin-top: 10px;" class="col-xs-12">