encoding.py 7.86 KB
Newer Older
1
from server import *
2
from sorter import insert_video
Julian Rother's avatar
Julian Rother committed
3
import os.path
4
import json
5

Julian Rother's avatar
Julian Rother committed
6
def set_metadata(dest, course, lecture):
7
	chapters = query('SELECT text, time FROM chapters WHERE lecture_id = ? AND visible ORDER BY time', lecture['id'])
Julian Rother's avatar
Julian Rother committed
8
9
10
11
	metadata = {'title': lecture['title'], 'album': course['title'],
		'description': lecture['comment'],
		'date': lecture['time'].strftime('%m/%d/%Y'),
		'artist': lecture['speaker'] if lecture['speaker'] else course['organizer']}
Julian Rother's avatar
Julian Rother committed
12
13
14
	dest['metadata'] = metadata
	dest['chapters'] = chapters

15
def schedule_remux(lectureid, videoid=None):
Julian Rother's avatar
Julian Rother committed
16
17
18
19
20
21
22
	lecture = query('SELECT * FROM lectures WHERE id = ?', lectureid)[0]
	course = query('SELECT * FROM courses WHERE id = ?', lecture['course_id'])[0]
	videos = query('''SELECT videos.*, sources.path AS srcpath, sources.hash AS srchash, formats.options AS fmtopts
			FROM videos
			JOIN sources ON videos.source = sources.id
			JOIN formats ON videos.video_format = formats.id
			WHERE videos.lecture_id = ?''', lectureid)
Julian Rother's avatar
Julian Rother committed
23
24
25
	for video in videos:
		if not video['source']:
			continue
26
27
		if videoid and video['id'] != videoid:
			continue
Julian Rother's avatar
Julian Rother committed
28
29
		data = {'path': video['path'], 'srcpath': video['srcpath'],
			'srchash': video['srchash'], 'video_id': video['id']}
Julian Rother's avatar
Julian Rother committed
30
31
32
33
34
		fmt = json.loads(video['fmtopts'])
		if 'format' in fmt:
			data['format'] = fmt['format']
		data['options'] = fmt.get('options', {})
		set_metadata(data, course, lecture)
Julian Rother's avatar
Julian Rother committed
35
		schedule_job('remux', data)
36
37
38
39
40
41
42
43
44
45
46

@app.route('/internal/jobs/add/remux', methods=['GET', 'POST'])
@mod_required
@csrf_protect
def add_remux_job():
	lectureid = request.values.get('lectureid')
	videoid = int(request.values.get('videoid', 0))
	if not lectureid:
		lectureid = query('SELECT lecture_id FROM videos WHERE id = ?', videoid)[0]['lecture_id']
	schedule_remux(lectureid, videoid)
	return redirect(request.values.get('ref', url_for('jobs_overview')))
Julian Rother's avatar
Julian Rother committed
47

48
def schedule_transcode(source, fmt_id=None, video=None, intro_path=None):
Julian Rother's avatar
Julian Rother committed
49
50
51
52
53
54
55
56
57
	if video:
		fmt_id = video['video_format']
		assert(video['lecture_id'] == source['lecture_id'])
	assert(fmt_id != None)
	fmt = query('SELECT * FROM formats WHERE id = ?', fmt_id)[0]
	lecture = query('SELECT * FROM lectures WHERE id = ?', source['lecture_id'])[0]
	course = query('SELECT * FROM courses WHERE id = ?', lecture['course_id'])[0]
	data = {'input': {'path': source['path'], 'streams': []}, 'output': json.loads(fmt['options']), 'filters': []}
	if source['type'] == 'plain':
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
		data['input']['streams'].append({'name': 'video', 'type': 'video'})
		data['input']['streams'].append({'name': 'audio', 'type': 'audio'})
	elif source['type'] == 'autointro':
		if not intro_path:
			ctx={'source_id': source['id'], 'fmt_id': fmt['id']}
			if video:
				ctx['video_id'] = video['id']
			schedule_intro(lecture['lecture_id'], transcode_ctx=ctx)
			return
		data['input']['streams'].append({'name': 'src_video', 'type': 'video'})
		data['input']['streams'].append({'name': 'src_audio', 'type': 'audio'})
		data['filters'].append('movie=$WORKER_RAW/%s, loop=loop=-1:size=1:start=0, fade=out:st=7:d=1, trim=duration=8[intro_video]'%intro_path)
		data['filters'].append('anullsrc,atrim=duration=8[intro_audio]')
		data['filters'].append('movie=$WORKER_RAW/outro_slide.png, loop=loop=-1:size=1:start=0, fade=in:st=0:d=1, trim=duration=5[outro_video]')
		data['filters'].append('anullsrc,atrim=duration=5[outro_audio]')
		data['filters'].append('[src_video]fade=in:st=0:d=1[tmp_video]')
		data['filters'].append('[intro_video] [intro_audio] [tmp_video] [src_audio] [outro_video] [outro_audio] concat=n=3:a=1 [video] [audio]')
Julian Rother's avatar
Julian Rother committed
75
76
77
78
79
80
	else:
		assert(False)
	set_metadata(data['output'], course, lecture)
	basename = os.path.basename(source['path']).rsplit('.', 1)[0]
	data['output']['path'] = 'pub/'+course['handle']+'/'+basename+fmt['suffix']
	if video:
81
		old_source = query('SELECT * FROM sources WHERE id = ?', video['source'])[0]
Julian Rother's avatar
Julian Rother committed
82
83
84
85
86
87
88
89
		data['output']['path'] = video['path']
		data['video_id'] = video['id']
		data['srcpath'] = old_source['path']
		data['srchash'] = old_source['hash']
	else:
		data['lecture_id'] = lecture['id']
	data['format_id'] = fmt['id']
	data['source_id'] = source['id']
90
91
92
93
94
95
96
97
98
99
	return schedule_job('transcode', data, queue="background")

@job_handler('transcode')
def insert_transcoded_video(jobid, jobtype, data, state, status):
	if 'lecture_id' not in data or 'source_id' not in data or 'format_id' not in data:
		return
	if 'video_id' in data:
		return
	video_id = insert_video(data['lecture_id'], data['output']['path'], data['format_id'], status['hash'], status['filesize'], status['duration'], data['source_id'])
	schedule_remux(data['lecture_id'], video_id)
Julian Rother's avatar
Julian Rother committed
100

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def schedule_intro(lecture_id, **data):
	lecture = query('SELECT * FROM lectures where id = ?', lecture_id)
	course = query('SELECT * FROM course where id = ?', lecture['course_id'])
	data['path'] = 'intros/l_%i.png'%lecture['id']
	data['lecture_id'] = lecture_id
	set_metadata(data, course, lecture)
	schedule_job('intro', data)

@job_handler('intro')
def transcode_after_intro(jobid, jobtype, data, state, status):
	if 'transcode_ctx' not in data:
		return 
	source = query('SELECT * FROM sources WHERE id = ?', data['transcode_ctx']['source_id'])[0]
	video = None
	if data['transcode_ctx'].get('video_id'):
		video = query('SELECT * FROM videos WHERE id = ?', data['transcode_ctx']['video_id'])
	schedule_transcode(source, data['transcode_ctx']['fmt_id'], video=video, intro_path=data['path'])

119
120
121
122
@app.route('/internal/jobs/add/reencode', methods=['GET', 'POST'])
@mod_required
@csrf_protect
@handle_errors('jobs_overview', 'Video nicht gefunden!', 404, IndexError)
123
def add_reencode_job():
124
125
126
127
128
129
130
131
	video = query('SELECT * FROM videos WHERE id = ?', request.values['videoid'])[0]
	if not video['source']:
		flash('Manuell erstellte Videos können nicht neukodiert werden!')
	else:
		source = query('''SELECT sources.* FROM sources WHERE sources.id = ? ORDER BY time_created''', video['source'])[-1]
		schedule_transcode(source, video=video)
	return redirect(request.values.get('ref', url_for('jobs_overview')))

Andreas Valder's avatar
Andreas Valder committed
132
@job_handler('probe-raw', 'intro')
Julian Rother's avatar
Julian Rother committed
133
134
135
def update_lecture_videos(jobid, jobtype, data, state, status):
	if 'lecture_id' not in data:
		return
Andreas Valder's avatar
Andreas Valder committed
136
137
138
139
	if jobtype == 'probe-raw':
		if 'source_id' not in data:
			modify('INSERT INTO sources (lecture_id, path, type, hash, time_created) VALUES (?, ?, ?, ?, ?)',
					data['lecture_id'], data['path'], 'plain', status['hash'], datetime.now())
Julian Rother's avatar
Julian Rother committed
140
141
142
143
	sources = query('SELECT * FROM sources WHERE sources.lecture_id = ? ORDER BY time_created', data['lecture_id'])
	if not sources:
		return
	latest = sources[-1]
Andreas Valder's avatar
Andreas Valder committed
144
	lecture = query('SELECT * FROM lectures where id = ?', data['lecture_id'])
145
	if False and jobtype == 'probe-raw':
Andreas Valder's avatar
Andreas Valder committed
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
		schedule_intro(data['lecture_id'])
	else:
		videos = query('SELECT * FROM videos WHERE videos.lecture_id = ?', data['lecture_id'])
		current_fmts = [v['video_format'] for v in videos]
		formats = query('''SELECT formats.* FROM formats
				JOIN profiles ON formats.id = profiles.format
				JOIN courses ON profiles.name = courses.profile
				JOIN lectures ON courses.id = lectures.course_id
				WHERE lectures.id = ?''', data['lecture_id'])
		for fmt in formats:
			if fmt['id'] not in current_fmts:
				schedule_transcode(latest, fmt_id=fmt['id'])
		for video in videos:
			if video['source'] != latest['id']:
				schedule_transcode(latest, video=video)
Julian Rother's avatar
Julian Rother committed
161

Julian Rother's avatar
Julian Rother committed
162
@edit_handler('chapters')
163
def chapter_changed(table, column, value, id, user):
Julian Rother's avatar
Julian Rother committed
164
165
166
167
168
169
170
171
	chapters = query('SELECT * FROM chapters WHERE id = ?', id)
	if not chapters:
		return
	chapter = chapters[0]
	if column in ['visible', 'deleted'] or (chapter['visible'] and not chapter['deleted']):
		schedule_remux(chapter['lecture_id'])

@edit_handler('courses')
172
def course_changed(table, column, value, id, user):
Julian Rother's avatar
Julian Rother committed
173
174
175
176
177
178
179
	if column not in ['title', 'organizer']:
		return
	lectures = query('SELECT * FROM lectures WHERE course_id = ?', id)
	for lecture in lectures:
		schedule_remux(lecture['id'])

@edit_handler('lectures')
180
def lecture_changed(table, column, value, id, user):
Julian Rother's avatar
Julian Rother committed
181
182
183
	if column in ['title', 'comment', 'time', 'speaker']:
		schedule_remux(id)