From 7f88072669caf2351b2ad20f09d3758c5a341a23 Mon Sep 17 00:00:00 2001
From: Julian Rother <julianr@fsmpi.rwth-aachen.de>
Date: Fri, 27 Jul 2018 02:35:12 +0200
Subject: [PATCH] Completed livestream model and made live sources page visible

---
 livestreams.py        |  64 +++++++++++++++++++++++++++++++++++++++-
 static/smptebars.jpg  | Bin 0 -> 6508 bytes
 templates/course.html |  67 ++++++++++++++++++++++++++----------------
 templates/macros.html |   2 +-
 4 files changed, 105 insertions(+), 28 deletions(-)
 create mode 100644 static/smptebars.jpg

diff --git a/livestreams.py b/livestreams.py
index c89cd82..3464720 100644
--- a/livestreams.py
+++ b/livestreams.py
@@ -62,7 +62,7 @@ def restart_failed_live_transcode(id, type, data, state, status):
 	restart_job(id)
 
 @app.route('/internal/streaming')
-#@register_navbar('Streaming', icon='transfer')
+@register_navbar('Streaming', icon='broadcast-tower', iconlib='fa')
 @mod_required
 def streaming():
 	sources = query('SELECT * FROM live_sources WHERE NOT deleted')
@@ -152,3 +152,65 @@ def streamauth(server):
 		modify('UPDATE live_sources SET server = NULL, clientid = NULL, preview_key = NULL, last_active = ? WHERE server = ? AND clientid = ?', datetime.now(), server, request.values['clientid'])
 		return 'Ok', 200
 	return 'Bad request', 400
+
+def schedule_livestream(lecture_id):
+	def build_filter(l):
+		return ','.join(l) if l else None
+	server = 'rwth.video'
+	lecture = query('SELECT * FROM lectures WHERE id = ?', lecture_id)[0]
+	settings = json.loads(lecture['stream_settings'])
+	data = {'src1': {'afilter': [], 'vfilter': []}, 'src2': {'afilter': [], 'vfilter': []}, 'afilter': [], 'videoag_logo': int(bool(settings.get('video_showlogo'))), 'lecture_id': lecture['id']}
+	src1 = (query('SELECT * FROM live_sources WHERE NOT deleted AND id = ?', settings.get('source1')) or [{}])[0]
+	src2 = (query('SELECT * FROM live_sources WHERE NOT deleted AND id = ?', settings.get('source2')) or [{}])[0]
+	for idx, obj in zip([1,2], [src1, src2]):
+		if obj:
+			server = obj['server']
+			data['src%i'%idx]['url'] = 'rtmp://%s/src/%i'%(obj['server'], obj['id'])
+		mode = settings.get('source%i_audiomode'%idx)
+		leftvol = float(settings.get('source%i_leftvolume'%idx, 100))/100.0
+		rightvol = float(settings.get('source%i_rightvolume'%idx, 100))/100.0
+		if mode == 'mono':
+			data['src%i'%idx]['afilter'].append('pan=mono|c0=%f*c0+%f*c1'%(0.5*leftvol, 0.5*rightvol))
+		elif mode == 'stereo':
+			data['src%i'%idx]['afilter'].append('pan=stereo|c0=%f*c0|c1=%f*c1'%(leftvol, rightvol))
+		elif mode == 'unchanged':
+			pass
+		elif mode == 'off':
+			data['src%i'%idx]['afilter'].append('pan=mono|c0=0*c0')
+		else:
+			raise(Exception())
+	mode = settings.get('videomode')
+	if mode == '1':
+		data['vmix'] = 'streamselect=map=0'
+	elif mode == '2':
+		data['vmix'] = 'streamselect=map=1'
+	elif vmode == 'lecture4:3':
+		data['src1']['vfilter'].append('scale=1440:1080')
+		data['src2']['vfilter'].append('scale=1440:810,pad=1440:1080:0:135,crop=480:1080')
+		data['vmix'] = 'hstack'
+	elif vmode == 'lecture16:9':
+		data['src1']['vfilter'].append('scale=1440:810,pad=1440:1080:0:135')
+		data['src2']['vfilter'].append('scale=1440:810,pad=1440:1080:0:135,crop=480:1080')
+		data['vmix'] = 'hstack'
+	elif vmode == 'sidebyside':
+		data['src1']['vfilter'].append('scale=960:540')
+		data['src2']['vfilter'].append('scale=960:540')
+		data['vmix'] = 'hstack,pad=1920:1080:0:270'
+	if settings.get('audio_normalize'):
+		data['afilter'].append('loudnorm')
+	data['afilter'] = build_filter(data['afilter'])
+	data['src1']['afilter'] = build_filter(data['src1']['afilter'])
+	data['src1']['vfilter'] = build_filter(data['src1']['vfilter'])
+	data['src2']['afilter'] = build_filter(data['src2']['afilter'])
+	data['src2']['vfilter'] = build_filter(data['src2']['vfilter'])
+	data['destbase'] = 'rtmp://%s/hls/l_%i'%(server, lecture['id'])
+	job_id = schedule_job('complex_live_transcode', data, priority=10)
+	return job_id
+
+@app.route('/internal/streaming/start', methods=['POST'])
+@mod_required
+def start_stream():
+	lecture_id = int(request.values['lecture_id'])
+	course = (query('SELECT courses.* FROM courses JOIN lectures ON (courses.id = lectures.course_id) WHERE lectures.id = ?', lecture_id) or [None])[0]
+	schedule_livestream(lecture_id)
+	return redirect(url_for('course', handle=course['handle']))
diff --git a/static/smptebars.jpg b/static/smptebars.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2e0e5598fe39d82591d6b3c5f689b9cf6f9236bc
GIT binary patch
literal 6508
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
M3MZlIfdBtZ0A_pz5C8xG

literal 0
HcmV?d00001

diff --git a/templates/course.html b/templates/course.html
index c95340e..b667734 100644
--- a/templates/course.html
+++ b/templates/course.html
@@ -160,12 +160,21 @@
 							<option value="{{ source.id }}">{{ source.name }}</option>
 							{% endfor %}
 						</select>
-						<img src="{{ config.VIDEOPREFIX }}/thumbnail/s_none.jpg" style="width: 100%; margin-bottom: 0.5em; margin-top: 0.5em"/>
-						<select name="source{{ snum }}_audiomode" class="form-control">
-							<option selected value="unchanged">Audio unverändert</option>
-							<option value="left">Nur linke Tonspur</option>
-							<option value="right">Nur rechte Tonspur</option>
-							<option value="mix">Monomix aller Tonspuren</option>
+						<img src="{{ url_for('static', filename='smptebars.jpg') }}" style="width: 100%; margin-bottom: 0.5em; margin-top: 0.5em"/>
+						<label>Lautstärke</label>
+						<div class="row">
+							<div class="col-xs-6">
+								<input type="range" name="source{{ snum }}_leftvolume" value="100">
+							</div>
+							<div class="col-xs-6">
+								<input type="range" name="source{{ snum }}_rightvolume" value="100">
+							</div>
+						</div>
+						<select name="source{{ snum }}_audiomode" class="form-control" style="margin-top: 0.5em">
+							<option value="mono" selected>Mono-Mix</option>
+							<option value="stereo">Stereo</option>
+							<option value="unchanged">Audio unverändert</option>
+							<option value="off">Kein Audio</option>
 						</select>
 					</div>
 					{% endfor %}
@@ -174,36 +183,27 @@
 						<select name="videomode" class="form-control">
 							<option value="1" selected>Nur Quelle 1</option>
 							<option value="2">Nur Quelle 2</option>
-							<option value="sidebyside">Side-by-Side (Quelle 1 groß, 1/3 von 2 daneben)</option>
+							<option value="lecture4:3">Quelle 1 (4:3) links, Ausschnitt von 2 rechts</option>
+							<option value="lecture16:9">Quelle 1 (16:9) links, Ausschnitt von 2 rechts</option>
+							<option value="sidebyside">Side-by-Side (Quelle 1 links, 2 rechts)</option>
 						</select>
 						<div class="checkbox"><label><input name="video_showlogo" type="checkbox" checked>Video AG-Logo einblenden</label></div>
 					</div>
-					<div class="col-xs-12" style="margin-top: 1em">
-						<label>Audio</label>
-						<select name="audiomode" class="form-control">
-							<option value="1" selected>Quelle 1</option>
-							<option value="2">Quelle 2</option>
-							<option value="splitstereo">Quelle 1 links, 2 rechts</option>
-							<option value="mix">Mix beider Quellen</option>
-						</select>
-						<div class="checkbox"><label><input name="audio_normalize" type="checkbox">Lautstärke normalisieren</label></div>
-					</div>
-					<div class="col-xs-12" style="margin-top: 1em">
-						<label>Ausgabeformat</label>
-						<select name="outputmode" class="form-control">
-							<option selected value="multi">1080p/720p/360p (Standard)</option>
-							<option value="720p">Nur 720p</option>
-						</select>
-					</div>
 					<div class="col-xs-12" style="margin-top: 1em">
 						<label>Weitere Einstellungen</label>
-						<div class="checkbox"><label><input name="autostart" type="checkbox" checked>Automatisch starten</label></div>
+						<div style="margin-top: -1em;">
+							<div class="checkbox"><label><input name="audio_normalize" type="checkbox">Lautstärke normalisieren</label></div>
+						</div>
 					</div>
 				</div>
 				</form>
 			</div>
 			<div class="modal-footer">
+				<form class="form-inline" method="post" action="{{ url_for('start_stream') }}">
+				<input type="hidden" id="editstream-lectureid" name="lecture_id" value="">
+				<button type="submit" id="editstream-start" class="btn btn-danger">Speichern und starten</button>
 				<button type="button" id="editstream-submit" class="btn btn-primary">Speichern</button>
+				</form>
 			</div>
 		</div>
 	</div>
@@ -212,7 +212,10 @@
 <script>
 function editstream_update() {
 	$('#editstream .source-select').each(function () {
-		$(this).siblings('img').prop('src', '{{ config.VIDEOPREFIX }}/thumbnail/s_'+$(this).val()+'.jpg');
+		if ($(this).val())
+			$(this).siblings('img').prop('src', '{{ config.VIDEOPREFIX }}/thumbnail/s_'+$(this).val()+'.jpg');
+		else
+			$(this).siblings('img').prop('src', {{ url_for('static', filename='smptebars.jpg')|tojson }});
 	});
 };
 function editstream_dump() {
@@ -223,6 +226,9 @@ function editstream_dump() {
 	$("#editstream select[name!='']").each(function () {
 		res[$(this).attr('name')] = $(this).val();
 	});
+	$("#editstream input[type='range'][name!='']").each(function () {
+		res[$(this).attr('name')] = $(this).val();
+	});
 	return res;
 };
 function editstream_load(obj) {
@@ -234,15 +240,24 @@ function editstream_load(obj) {
 		if ($(this).attr('name') in obj)
 			$(this).val(obj[$(this).attr('name')]);
 	});
+	$("#editstream input[type='range'][name!='']").each(function () {
+		if ($(this).attr('name') in obj)
+			$(this).val(obj[$(this).attr('name')]);
+	});
 };
 $('#editstream .source-select').on('change', editstream_update);
 $('#editstream-submit').on('click', function () {
 	moderator.api.set($('#editstream').data('currentpath'), JSON.stringify(editstream_dump()), true);
 	$('#editstream').modal('hide');
 });
+$('#editstream-start').on('click', function () {
+	moderator.api.set($('#editstream').data('currentpath'), JSON.stringify(editstream_dump()), true);
+	return true;
+});
 $('#editstream').on('show.bs.modal', function (event) {
 	var button = $(event.relatedTarget);
 	$('#editstream').data('currentpath', button.data('path'));
+	$('#editstream-lectureid').val(button.data('lectureid'));
 	$("#editstream-form")[0].reset();
 	if (button.data('value'))
 		editstream_load(button.data('value'));
diff --git a/templates/macros.html b/templates/macros.html
index 107cef4..f0de863 100644
--- a/templates/macros.html
+++ b/templates/macros.html
@@ -262,7 +262,7 @@ $('#embedcodebtn').popover(
 						</li>
 						{% if ismod() %}
 						<li class="pull-right">
-							<button class="btn btn-default" data-toggle="modal" data-target="#editstream" data-path="{{ 'lectures.%i.stream_settings'%lecture.id }}" data-value='{{ lecture.stream_settings|e }}'>
+							<button class="btn btn-default" data-toggle="modal" data-target="#editstream" data-lectureid="{{ lecture.id }}" data-path="{{ 'lectures.%i.stream_settings'%lecture.id }}" data-value='{{ lecture.stream_settings|e }}'>
 								<span class="fas fa-broadcast-tower"></span>
 							</button>
 						</li>
-- 
GitLab