This repository was archived by the owner on Nov 28, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtranscode-h264-v3.py
More file actions
692 lines (631 loc) · 30.6 KB
/
transcode-h264-v3.py
File metadata and controls
692 lines (631 loc) · 30.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 2015 Michael Stucky
#
# This script is based on Raymond Wagner's transcode wrapper stub.
# Designed to be a USERJOB of the form </path to script/transcode-h264.py %JOBID%>
#
# Modifications - Drew 7/5/2015
#
# - chanid/starttime arguments needed a couple fixes to get it working
# - added ability to cut out commercials before transcode via generate_commcutlist=True
# - added ability to implement a compression ratio
# the compression ratio estimates the input streams encoding bitrate (all streams as a one bitrate)
# then computes an output bitrate as a factor of this bitrate, i.e., if compressionRatio=0.75
# then the output video will be encoded at 75% of the input video bitrate. Usually one sets
# compressionRatio < 1, resulting in a smaller output file.
# Note the estimated bitrate is derived from the video duration
# and file size hence it will over estimate the true video bitrate as it does not account for the
# encapsulation overhead of the input encoding scheme nor the bitrate of any included audio streams.
# To enable, set estimateBitrate = True and set compressionRatio to your desired value (I use 0.7).
# - added ability to change h264 encoding preset and constant rate factor (crf) settings for HD video
# - added loads of debug statements, I've kept them in to facilitate hacking -- sorry to the purists in advance
# - added status output from ffmpeg to the myth backend giving % complete, ETA and fps encode statistics.
# - added "smart" commercial detection - if needed it is run and on completion cancels any other mythcommflag jobs
# for the transcoded recording
# Modifications - Drew 1/25/2016
# - added fix for the markup data which is inaccurate, especially when commercials are removed
#
from MythTV import Job, Recorded, System, MythDB, findfile, MythError, MythLog, datetime
from optparse import OptionParser
from glob import glob
from shutil import copyfile
import sys
import os
import errno
import threading, time
from datetime import timedelta
from dateutil.parser import parse
import re, tempfile
import queue # thread-safe
########## IMPORTANT #####################
#
# YOU WILL NEED TO EDIT THE SETTINGS BELOW
#
########## IMPORTANT #####################
transcoder = '/usr/bin/ffmpeg'
# flush_commskip
# True => (Default) the script will delete all commercial skip indices from the old file
# False => the transcode will leave the commercial skip indices from the old file "as is"
flush_commskip = True
# require_commflagged
# True => the script will ensure mythcommflag has run on the file before encoding it
# False => (Default) the transcode will process the video file "as is"
require_commflagged = False
# generate_commcutlist
# True => (Default) flagged commercials are removed from the output video file
# False => flagged commercials are NOT removed from the output video file
generate_commcutlist = True
# estimateBitrate
# True => (Default) the bitrate of the input file is estimated via size & duration
# ** Required True for "compressionRatio" option to work.
# False => The bitrate of the input file is unknown
estimateBitrate = True
# compressionRatio
# 0.0 - 1.0 => Set the approximate bitrate of the output relative to the
# detected bitrate of the input.
# One can think of this as the target compression rate, i.e., the
# compressionRatio = (output filesize)/(input filesize)
# h264 video quality is approximately equal to mpeg2 video quality
# at a compression ratio of 0.65-0.75
# * Note: When enabled, this value will determine the approximate
# relative size of the output file and input file
# (output filesize) = compressionRatio * (input filesize)
compressionRatio = 0.65
# enforce a max (do not exceed) bitrate for encoded HD video
# to disable set hd_max_bitrate=0
hdvideo_max_bitrate = 5500 # 0 = disable or (kBits_per_sec,kbps)
hdvideo_min_bitrate = 0 # 0 = disable or (kBits_per_sec,kbps)
# number of seconds of video that can be held in playing device video buffers (typically 2-5 secs)
NUM_SECS_VIDEO_BUF=3 # secs
device_bufsize = NUM_SECS_VIDEO_BUF*hdvideo_max_bitrate # (kBits_per_sec,kbps)
# enforce a target bitrate for the encoder to achieve approximately
#hdvideo_tgt_bitrate = 5000 # 0 = disable or (kBits_per_sec,kbps)
hdvideo_tgt_bitrate = 0 # 0 = disable or (kBits_per_sec,kbps)
# build_seektable
# True => Rebuild myth seek table.
# It allows accurate ffwd,rew / seeking on the transcoded output video
# False => (Default) Do not rebuild the myth seek table. Not working for mythtv on h264 content.
build_seektable = False
# Making this true enables a bunch of debug information to be printed as the script runs.
debug = False
# TODO - override buffer size (kB), only use when necessary for a specific target device
# bufsize_override=0 # 0 = disable or (kBits_per_sec,kbps)
# h264 encode preset
# ultrafast,superfast, veryfast, faster, fast, medium, slow, slower, veryslow
preset_HD = 'fast'
preset_nonHD = 'slow'
# h264 encode constant rate factor (used for non-HD) valid/sane values 18-28
# lower values -> higher quality, larger output files,
# higher values -> lower quality, smaller output files
crf = '21'
# if HD, copy input audio streams to the output audio streams
abitrate_param_HD='-c:a copy'
# if non-HD, encode audio to AAC with libfdk_aac at a bitrate of 128kbps
abitrate_param_nonHD = '-c:a libfdk_aac -b:a 128k'
# to convert non-HD audio to AAC using ffmpeg's aac encoder
#abitrate_param_nonHD='-strict -2'
# TODO use -crf 20 -maxrate 400k -bufsize 1835k
# effectively "target" crf 20, but if the output exceeds 400kb/s, it will degrade to something more than crf 20
# TODO detect and preserve ac3 5.1 streams typically found in HD content
# TODO detect and preserve audio streams by language
# TODO detect and preserve subtitle streams by language
# TODO is mp4 or mkv better for subtitle support in playback
# subtitle codecs for MKV containers: copy, ass, srt, ssa
# subtitle codecs for MP4 containers: copy, mov_text
# Languages for audio stream and subtitle selection
# eng - English
# fre - French
# ger - German
# ita - Italian
# spa - Spanish
language = 'eng'
# interval between reads from the ffmpeg status file
# also defines the interval when waiting for a mythcommflag job to finish
POLL_INTERVAL=10 # secs
# mythtv automatically launched user jobs with nice level of 17
# this will add to that level (only positive values allowed unless run as root)
# e.g., NICELEVEL=1 will run with a nice level of 18. The max nicelevel is 19.
#NICELEVEL=5
NICELEVEL=0
class CleanExit:
pass
def runjob(jobid=None, chanid=None, starttime=None, tzoffset=None):
global estimateBitrate
db = MythDB()
if jobid:
job = Job(jobid, db=db)
chanid = job.chanid
utcstarttime = job.starttime
else:
job=None;
#utcstarttime = datetime.strptime(starttime, "%Y%m%d%H%M%S%z")
utcstarttime = parse(starttime)
utcstarttime = utcstarttime + timedelta(hours=tzoffset)
if debug:
print('chanid "%s"' % chanid)
print('utcstarttime "%s"' % utcstarttime)
rec = Recorded((chanid, utcstarttime), db=db);
utcstarttime = rec.starttime;
starttime_datetime = utcstarttime
# reformat 'starttime' for use with mythtranscode/ffmpeg/mythcommflag
starttime = str(utcstarttime.utcisoformat().replace(':', '').replace(' ', '').replace('T', '').replace('-', ''))
if debug:
print('mythtv format starttime "%s"' % starttime)
input_filesize = rec.filesize
if rec.commflagged:
if debug:
print('Recording has been scanned to detect commerical breaks.')
waititer=1
keepWaiting = True
while keepWaiting == True:
keepWaiting=False;
for index,jobitem in reversed(list(enumerate(db.searchJobs(chanid=chanid, starttime=starttime_datetime)))):
if jobitem.type == jobitem.COMMFLAG: # Commercial flagging job
if debug:
print('Commercial flagging job detected with status %s' % jobitem.status)
if jobitem.status == jobitem.RUNNING: # status = RUNNING?
job.update({'status':job.PAUSED,
'comment':'Waited %d secs for the commercial flagging job' % (waititer*POLL_INTERVAL) \
+ ' currently running on this recording to complete.'})
if debug:
print('Waited %d secs for the commercial flagging job' % (waititer*POLL_INTERVAL) \
+ ' currently running on this recording to complete.')
time.sleep(POLL_INTERVAL);
keepWaiting=True
waititer = waititer + 1
break
else:
if debug:
print('Recording has not been scanned to detect/remove commercial breaks.')
if require_commflagged:
if jobid:
job.update({'status':job.RUNNING, 'comment':'Required commercial flagging for this file is not found.'
+ 'Flagging commercials and cancelling any queued commercial flagging.'})
# cancel any queued job to flag commercials for this recording and run commercial flagging in this script
for index,jobitem in reversed(list(enumerate(db.searchJobs(chanid=chanid,starttime=starttime_datetime)))):
if debug:
if index==0:
print(list(jobitem.keys()))
print(index,jobitem.id,jobitem.chanid)
if jobitem.type == jobitem.COMMFLAG: # Commercial flagging job
if jobitem.status == jobitem.RUNNING: # status = RUNNING?
jobitem.cmds = jobitem.STOP # stop command from the frontend to stop the commercial flagging job
#jobitem.setStatus(jobitem.CANCELLED)
#jobitem.setComment('Cancelled: Transcode command ran commercial flagging for this recording.')
jobitem.update({'status':jobitem.CANCELLED,
'comment':'A user transcode job ran commercial flagging for'
+ ' this recording and cancelled this job.'})
if debug:
print('Flagging Commercials...')
# Call "mythcommflag --chanid $CHANID --starttime $STARTTIME"
task = System(path='mythcommflag', db=db)
try:
output = task('--chanid "%s"' % chanid,
'--starttime "%s"' % starttime,
'2> /dev/null')
except MythError as e:
# it seems mythcommflag always exits with an decoding error "eno: Unknown error 541478725 (541478725)"
pass
#print 'Command failed with output:\n%s' % e.stderr
#if jobid:
# job.update({'status':304, 'comment':'Flagging commercials failed'})
#sys.exit(e.retcode)
sg = findfile(rec.basename, rec.storagegroup, db=db)
if sg is None:
print('Local access to recording not found.')
sys.exit(1)
infile = os.path.join(sg.dirname, rec.basename)
tmpfile = '%s.tmp' % infile.rsplit('.',1)[0]
# tmpfile = infile
outfile = '%s.mp4' % infile.rsplit('.',1)[0]
if debug:
print('tmpfile "%s"' % tmpfile)
clipped_bytes=0;
# If selected, create a cutlist to remove commercials via mythtranscode by running:
# mythutil --gencutlist --chanid $CHANID --starttime $STARTTIME
if generate_commcutlist:
if jobid:
job.update({'status':job.RUNNING, 'comment':'Generating Cutlist for commercial removal'})
task = System(path='mythutil', db=db)
try:
output = task('--gencutlist',
'--chanid "%s"' % chanid,
'--starttime "%s"' % starttime)
# '--loglevel debug',
# '2> /dev/null')
except MythError as e:
print('Command "mythutil --gencutlist" failed with output:\n%s' % e.stderr)
if jobid:
job.update({'status':job.ERRORED, 'comment':'Generation of commercial Cutlist failed'})
sys.exit(e.retcode)
# Lossless transcode to strip cutlist
if generate_commcutlist or rec.cutlist==1:
if jobid:
job.update({'status':job.RUNNING, 'comment':'Removing Cutlist'})
task = System(path='mythtranscode', db=db)
try:
output = task('--chanid "%s"' % chanid,
'--starttime "%s"' % starttime,
'--mpeg2',
'--honorcutlist',
'-o "%s"' % tmpfile,
'1>&2')
# '2> /dev/null')
clipped_filesize = os.path.getsize(tmpfile)
clipped_bytes = input_filesize - clipped_filesize
clipped_compress_pct = float(clipped_bytes)/input_filesize
rec.commflagged = 0
except MythError as e:
print('Command "mythtranscode --honorcutlist" failed with output:\n%s' % e.stderr)
if jobid:
job.update({'status':job.ERRORED, 'comment':'Removing Cutlist failed. Copying file instead.'})
# sys.exit(e.retcode)
copyfile('%s' % infile, '%s' % tmpfile)
clipped_filesize = input_filesize
clipped_bytes = 0
clipped_compress_pct = 0
pass
else:
if jobid:
job.update({'status':job.RUNNING, 'comment':'Creating temporary file for transcoding.'})
copyfile('%s' % infile, '%s' % tmpfile)
clipped_filesize = input_filesize
clipped_bytes = 0
clipped_compress_pct = 0
duration_secs = 0
# Estimate bitrate, and detect duration and number of frames
if estimateBitrate:
if jobid:
job.update({'status':job.RUNNING, 'comment':'Estimating bitrate; detecting frames per second, and resolution.'})
duration_secs, e = get_duration(db, rec, transcoder, tmpfile);
if duration_secs>0:
bitrate = int(clipped_filesize*8/(1024*duration_secs))
else:
print('Estimate bitrate failed falling back to constant rate factor encoding.\n')
estimateBitrate = False
duration_secs = 0
print(e.stderr.decode('utf-8'))
# get framerate of mpeg2 video stream and detect if stream is HD
r = re.compile('mpeg2video (.*?) fps,')
m = r.search(e.stderr.decode('utf-8'))
strval = m.group(1)
if debug:
print(strval)
isHD = False
if "1920x1080" in strval or "1280x720" in strval or "2560x1440" in strval:
if debug:
print('Stream is HD')
isHD = True
else:
if debug:
print('Stream is not HD')
framerate = float(m.group(1).split(' ')[-1])
if debug:
print('Framerate %s' % framerate)
# Setup transcode video bitrate and quality parameters
# if estimateBitrate is true and the input content is HD:
# encode 'medium' preset and vbitrate = inputfile_bitrate*compressionRatio
# else:
# encode at user default preset and constant rate factor ('slow' and 20)
preset = preset_nonHD
if estimateBitrate:
if isHD:
h264_bitrate = int(bitrate*compressionRatio)
# HD coding with specified target bitrate (CRB encoding)
if hdvideo_tgt_bitrate > 0 and h264_bitrate > hdvideo_tgt_bitrate:
h264_bitrate = hdvideo_tgt_bitrate;
vbitrate_param = '-b:v %dk' % h264_bitrate
else: # HD coding with disabled or acceptable target bitrate (CRF encoding)
vbitrate_param = '-crf:v %s' % crf
preset = preset_HD
else: # non-HD encoding (CRF encoding)
vbitrate_param = '-crf:v %s' % crf
else:
vbitrate_param = '-crf:v %s' % crf
if hdvideo_min_bitrate > 0:
vbitrate_param = vbitrate_param + ' -minrate %sk' % hdvideo_min_bitrate
if hdvideo_max_bitrate > 0:
vbitrate_param = vbitrate_param + ' -maxrate %sk' % hdvideo_max_bitrate
if hdvideo_max_bitrate > 0 or hdvideo_min_bitrate > 0:
vbitrate_param = vbitrate_param + ' -bufsize %sk' % device_bufsize
if debug:
print('Video bitrate parameter "%s"' % vbitrate_param)
print('Video h264 preset parameter "%s"' % preset)
# Setup transcode audio bitrate and quality parameters
# Right now, the setup is as follows:
# if input is HD:
# copy audio streams to output, i.e., input=output audio
# else:
# output is libfdk_aac encoded at 128kbps
if isHD:
abitrate_param = abitrate_param_HD # preserve 5.1 audio
else:
abitrate_param = abitrate_param_nonHD
if debug:
print('Audio bitrate parameter "%s"' % abitrate_param)
# Transcode to mp4
# if jobid:
# job.update({'status':4, 'comment':'Transcoding to mp4'})
# ffmpeg output is redirected to the temporary file tmpstatusfile and
# a second thread continuously reads this file while
# the transcode is in-process. see while loop below for the monitoring thread
tf = tempfile.NamedTemporaryFile()
tmpstatusfile = tf.name
# tmpstatusfile = '/tmp/ffmpeg-transcode.txt'
if debug:
print('Using temporary file "%s" for ffmpeg status updates.' % tmpstatusfile)
res = []
# create a thread to perform the encode
ipq = queue.Queue()
t = threading.Thread(target=wrapper, args=(encode,
(jobid, db, job, ipq, preset, vbitrate_param, abitrate_param,
tmpfile, outfile, tmpstatusfile,), res))
t.start()
# wait for ffmpeg to open the file and emit its initialization information
# before we start the monitoring process
time.sleep(1)
# open the temporary file having the ffmeg output text and process it to generate status updates
hangiter=0;
with open(tmpstatusfile) as f:
# read all the opening ffmpeg status/analysis lines
lines = f.readlines()
# set initial progress to -1
prev_progress=-1
framenum=0
fps=1.0
while t.is_alive():
# read all output since last readline() call
lines = f.readlines()
if len(lines) > 0:
# every ffmpeg output status line ends with a carriage return '\r'
# split the last read line at these locations
lines=lines[-1].split('\r')
# if debug:
# print lines;
hangiter=0
if len(lines) > 1 and lines[-2].startswith('frame'):
# since typical reads will have the last line ending with \r the last status
# message is at index=[-2] start processing this line
# replace multiple spaces with one space
lines[-2] = re.sub(' +',' ',lines[-2])
# remove any spaces after equals signs
lines[-2] = re.sub('= +','=',lines[-2])
# split the fields at the spaces the first two fields for typical
# status lines will be framenum=XXXX and fps=YYYY parse the values
values = lines[-2].split(' ')
if len(values) > 1:
if debug:
print('values %s' % values)
prev_framenum = framenum
prev_fps = fps
try:
# framenum = current frame number being encoded
framenum = int(values[0].split('=')[1])
# fps = frames per second for the encoder
fps = float(values[1].split('=')[1])
except ValueError as e:
print('ffmpeg status parse exception: "%s"' % e)
framenum = prev_framenum
fps = prev_fps
pass
# progress = 0-100 represent percent complete for the transcode
progress = int((100*framenum)/(duration_secs*framerate))
# eta_secs = estimated number of seconds until transcoding is complete
eta_secs = int((float(duration_secs*framerate)-framenum)/fps)
# pct_realtime = how many real seconds it takes to encode 1 second of video
pct_realtime = float(fps/framerate)
if debug:
print('framenum = %d fps = %.2f' % (framenum, fps))
if progress != prev_progress:
if debug:
print('Progress %d%% encoding %.1f frames per second ETA %d mins' \
% ( progress, fps, float(eta_secs)/60))
if jobid:
progress_str = 'Transcoding to mp4 %d%% complete ETA %d mins fps=%.1f.' \
% ( progress, float(eta_secs)/60, fps)
job.update({'status':job.RUNNING, 'comment': progress_str})
prev_progress = progress
elif len(lines) > 1:
if debug:
print('Read pathological output %s' % lines[-2])
else:
if debug:
print('Read no lines of ffmpeg output for %s secs. Possible hang?' % (POLL_INTERVAL*hangiter))
hangiter = hangiter + 1
if jobid:
progress_str = 'Read no lines of ffmpeg output for %s secs. Possible hang?' % (POLL_INTERVAL*hangiter)
job.update({'status':job.RUNNING, 'comment': progress_str})
time.sleep(POLL_INTERVAL)
if debug:
print('res = "%s"' % res)
t.join(1)
try:
if ipq.get_nowait() == CleanExit:
sys.exit()
except queue.Empty:
pass
if flush_commskip:
task = System(path='mythutil')
task.command('--chanid %s' % chanid,
'--starttime %s' % starttime,
'--clearcutlist',
'2> /dev/null')
task = System(path='mythutil')
task.command('--chanid %s' % chanid,
'--starttime %s' % starttime,
'--clearskiplist',
'2> /dev/null')
if flush_commskip:
for index,mark in reversed(list(enumerate(rec.markup))):
if mark.type in (rec.markup.MARK_COMM_START, rec.markup.MARK_COMM_END):
del rec.markup[index]
rec.bookmark = 0
rec.cutlist = 0
rec.markup.commit()
# tf.close();
# os.remove(tmpstatusfile);
rec.basename = os.path.basename(outfile)
rec.filesize = os.path.getsize(outfile)
# rec.commflagged = 0
rec.transcoded = 1
rec.seek.clean()
rec.update()
os.remove(infile)
# Cleanup the old *.png files
for filename in glob('%s*.png' % infile):
os.remove(filename)
os.remove(tmpfile)
try:
os.remove('%s.map' % tmpfile)
except OSError:
pass
output_filesize = rec.filesize
if duration_secs > 0:
output_bitrate = int(output_filesize*8/(1024*duration_secs)) # kbps
actual_compression_ratio = 1 - float(output_filesize)/clipped_filesize
compressed_pct = 1 - float(output_filesize)/input_filesize
if build_seektable:
if jobid:
job.update({'status':job.RUNNING, 'comment':'Rebuilding seektable'})
task = System(path='mythcommflag')
task.command('--chanid %s' % chanid,
'--starttime %s' % starttime,
'--rebuild',
'2> /dev/null')
# fix during in the recorded markup table this will be off if commercials are removed
duration_msecs, e = get_duration(db, rec, transcoder, outfile)
duration_msecs = 1000*duration_msecs
for index,mark in reversed(list(enumerate(rec.markup))):
# find the duration markup entry and correct any error in the video duration that might be there
if mark.type == 33:
if debug:
print('Markup Duration in milliseconds "%s"' % mark.data)
error = mark.data - duration_msecs
if error != 0:
if debug:
print('Markup Duration error is "%s"msecs' % error)
mark.data = duration_msecs
#rec.bookmark = 0
#rec.cutlist = 0
rec.markup.commit()
if jobid:
if output_bitrate:
job.update({'status':job.FINISHED, 'comment':'Transcode Completed @ %dkbps, compressed file by %d%% (clipped %d%%, transcoder compressed %d%%)' % (output_bitrate,int(compressed_pct*100),int(clipped_compress_pct*100),int(actual_compression_ratio*100))})
else:
job.update({'status':job.FINISHED, 'comment':'Transcode Completed'})
def get_duration(db=None, rec=None, transcoder='/usr/bin/ffmpeg', filename=None):
task = System(path=transcoder, db=db)
if filename is None:
return -1
try:
output = task('-i "%s"' % filename, '1>&2')
except MythError as e:
err = e
pass
r = re.compile('Duration: (.*?), start')
m = r.search(err.stderr.decode('utf-8'))
if m:
duration = m.group(1).split(':')
duration_secs = float((int(duration[0])*60+int(duration[1]))*60+float(duration[2]))
duration_msecs = int(1000*duration_secs)
if debug:
print('Duration %s' % m.group(1))
print('Duration %s' % duration)
print('Duration in seconds "%s"' % duration_secs)
print('Duration in milliseconds "%s"' % duration_msecs)
return duration_secs, err
return -1, err
def encode(jobid=None, db=None, job=None,
procqueue=None, preset='slow',
vbitrate_param='-crf:v 18',
abitrate_param='-c:a libfdk_aac -b:a 128k',
tmpfile=None, outfile=None, statusfile=None):
# task = System(path=transcoder, db=db)
task = System(path='nice', db=db)
try:
output = task(
'-n %s' % NICELEVEL,
'%s' % transcoder,
'-i "%s"' % tmpfile,
# parameter to overwrite output file if present without prompt
'-y',
# parameter de-interlacing filter
'-filter:v yadif=0:-1:1',
# parameter to allow streaming content
'-movflags faststart',
# parameter needed when hdhomerun prime mpeg2 files sometime repeat timestamps
'-vsync passthrough',
# h264 video codec
'-c:v libx264',
# presets for h264 encode that effect encode speed/output filesize
'-preset:v %s' % preset,
# ########## IMPORTANT ############
# ffmpeg versions after 08-18-2015 include a change to force explicit IDR frames,
# setting this flag helps/corrects myth seektable indexing h264-encoded files
# uncomment the line below if you have a recent version of ffmpeg that supports this option
# '-forced-idr 1',
# parameters to determine video encode target bitrate
vbitrate_param,
# parameters to determine audio encode target bitrate
abitrate_param,
# parameter to encode all input audio streams into the output
# '-map 0:a',
# parameters to set the first output audio stream
# to be an audio stream having the specified language (default=eng -> English)
# '-metadata:s:a:0',
# 'language=%s' % language,
# parameter to copy input subtitle streams into the output
'-c:s copy',
# '-c:s mov_text',
# parameters to set the first output subtitle stream
# to be an english subtitle stream
# '-metadata:s:s:0',
# 'language=%s' % language,
# we can control the number of encode threads (disabled)
'-threads 4',
# output file parameter
'"%s"' % outfile,
# redirection of output to temporaryfile
'> %s 2>&1 < /dev/null' % statusfile)
except MythError as e:
print('Command failed with output:\n%s' % e.stderr)
if jobid:
job.update({'status':job.ERRORED, 'comment':'Transcoding to mp4 failed'})
procqueue.put(CleanExit)
os.remove(tmpfile)
try:
os.remove('%s.map' % tmpfile)
except OSError:
pass
sys.exit(e.retcode)
def wrapper(func, args, res):
res.append(func(*args))
def main():
parser = OptionParser(usage="usage: %prog [options] [jobid]")
parser.add_option('--chanid', action='store', type='int', dest='chanid',
help='Use chanid with both starttime and tzoffset for manual operation')
parser.add_option('--starttime', action='store', type='string', dest='starttime',
help='Use starttime with both chanid and tzoffset for manual operation')
parser.add_option('--tzoffset', action='store', type='int', dest='tzoffset',
help='Use tzoffset with both chanid and starttime for manual operation')
parser.add_option('-v', '--verbose', action='store', type='string', dest='verbose',
help='Verbosity level')
opts, args = parser.parse_args()
if opts.verbose:
if opts.verbose == 'help':
print(MythLog.helptext)
sys.exit(0)
MythLog._setlevel(opts.verbose)
if len(args) == 1:
runjob(jobid=args[0])
elif opts.chanid and opts.starttime and opts.tzoffset is not None:
runjob(chanid=opts.chanid, starttime=opts.starttime, tzoffset=opts.tzoffset)
else:
print('Script must be provided jobid, or chanid, starttime and timezone offset.')
sys.exit(1)
if __name__ == '__main__':
debug = True
# sys.stdout = open('/home/mythtv/logfile','a')
# print('Starting ', str(sys.argv))
main()