-
Notifications
You must be signed in to change notification settings - Fork 1
/
lossless_cut.py
executable file
·1877 lines (1826 loc) · 79 KB
/
lossless_cut.py
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
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# ----------------------
"""
# Name: lossless_cut.py A Loss Less video file cut for MythTV recordings
#
# Python Script
# Author: R.D. Vaughan (original developer)
# Angela Schmid (maintainer)
# Purpose: This python script performs loss less video file cut
# and merge processing on MythTV recordings.
#
# This utility is based on the process found in the shell script
# "h264cut.sh" from:
#
# Ian Thiele at: [email protected]
# Wiki page for H264 commercial remover and remuxer:
# http://www.mythtv.org/wiki/H264cut
#
# Copyright (C) 2012 R.D. Vaughan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# License:Creative Commons GNU GPL v2
# (https://www.gnu.org/licenses/gpl-2.0.html)
#-------------------------------------
#
"""
## System imports
import os
import sys
from glob import glob
from optparse import OptionParser
from datetime import datetime
from copy import deepcopy
## Mythtv loss less cut specific imports
import importcode.common as common
from importcode.utilities import create_cachedir, \
set_language, get_config, exec_commandline, \
check_dependancies, create_logger, commandline_call, \
get_iso_language_code, read_iso_language_codes, make_timestamp, \
display_recorded_info, get_mediainfo, cleanup_working_dir, \
create_config_file, log_traceback
#
from importcode.mythtvinterface import Mythtvinterface
#
try:
from lxml import etree as etree
except Exception as errmsg:
sys.stderr.write('''
Importing the "lxml" python libraries failed on
Error: (%s)\n''' % errmsg)
sys.exit(int(common.JOBSTATUS().ABORTED))
#
# Check that the lxml library is current enough
# From the lxml documents it states:
# (http://codespeak.net/lxml/installation.html)
# "If you want to use XPath, do not use libxml2 2.6.27. We recommend
# libxml2 2.7.2 or later"
#
VERSION = ''
for digit in etree.LIBXML_VERSION:
VERSION += str(digit)+'.'
VERSION = VERSION[:-1]
if VERSION < '2.7.2':
sys.stderr.write('''
Error: The installed version of the "lxml" python library "libxml" version
is too old. At least "libxml" version 2.7.2 must be installed.
Your version is (%s).
''' % VERSION)
sys.exit(int(common.JOBSTATUS().ABORTED))
#
## Initialize local variables
__title__ = common.APPNAME
__version__ = common.VERSION
__author__ = common.__author__
#
# Language translation specific to this desktop
_ = set_language()
#
__purpose__ = _('''
This python script performs loss less video file cut
and merge processing on MythTV recordings.
''')
#
## Local variables
## Help text
MANDITORY = _(
'''Mandatory command line parameter:
-f "/path/filename.mpg" MythTV recorded video path and file name
to be converted to a mkv file
When this script is used as a MythTV user job always specify the
file as: "%%DIR%%/%%FILE%%"
One and only one of these three options MUST be selected:
-e Export the final mkv video file to MythVideo,
including building any required sub directories.
-m "/mkv directory path" Specify directory path to move the final
mkv video file
-r Replace the Recorded video file with the loss
less cut mkv file. Do not use this option unless
you are confident that the results of the cutting
process. Unlike the option -e and -m the original
recorded video is replaced.
''')
#
USAGE_TEXT = _('''
This script uses a MythTV cut list to create a loss less mkv video file from
MythTV recordings this includes:
Video encoded as with mpeg2 and h.264 (SD, 720p or 1080i HD)
Audio encoded as mp3 and AC3 (2ch and 5.1)
Subtitle encoded srt format or can be exported and converted into an srt file.
Recording devices such as HDPVR, HDHomeRun and MPEG2 from a DVB-T FreeView
transport.
For a complete list of supported recording devices check the wiki at:
%s
If a cutlist or skiplist has not been created then the whole mpg file is
copied into a mkv file container. In most cases a skip or cut list would
have been created before running this script as a userjob.
MythTV Userjob examples: (modify the path to the script so that it matches
your installation)
Export a loss less mkv file to a specific directory and leave the
original mpg file unchanged:
/path to/script file/lossless_cut.py -f "%%DIR%%/%%FILE%%" -e
OR
Move a loss less mkv file to a specific local directory and leave the
original mpg file unchanged:
/path to/script file/lossless_cut.py -f "%%DIR%%/%%FILE%%" -m "/move path"
OR
To create a loss less mkv file and replace the mpg with the mkv in the
MythTV database:
/path to/script file/lossless_cut.py -f "%%DIR%%/%%FILE%%" -r
%s
Optional command line parameters:
Most of these parameters can be set in the automatically
generated "~/.mythtv/lossless_cut.cfg" configuration file.
-a Do NOT add metadata with the MythTV grabbers
-C Create individual files from each cut segment.
This is referred to as Concert Cuts.
-D Delay option to change when the video track starts
by a positive (start sooner than other tracks)
or negative number (start later than other tracks)
in milliseconds. One second = 1000
This option should only be chosen by experienced users.
-g Generate a cut list if one does not exist but there is
a skip list.
-h or -u Display this help/usage text
-j JobQueue ID "%%JOBID%%" of this UserJob.
-k Keep the log file after the userjob
has completed successfully
-l "/log directory path" Specify a directory path to save the jobs
log file
-s Display a summary of the options that would
be used during processing
but exit before processing begins. Used this
option for debugging. No changes to the mpg
file occurs.
-P Passthrough, no subtitle processing
No need for: mkvtoolnix and ccextractor
Cannot be used with "-S" Strip option.
-S Strip away any subtitle or secondary audio tracks
-T Identifies a specific Audio track to copy in
conjunction with the "-S" Strip option.
Find the audio track numbers using a
"> mkvmerge -i file.mkv" command. If there is only
one audio track then this option is meaningless.
-t Perform a test of the environment, display
success or failure then exit
-v Display script's version, author's name and
purpose.
-X Force use of mythccextractor as the primary
utility to extract and convert subtitles tracks
into srt format.
-w "/working directory path" Specify a working directory path to
manipulate the video file
This script requires the following to be installed and accessible:
1) The "lossless_cut" directory must be installed on a MythTV backend
2) The utilities: mythutil or mythcommflag
3) When --passthrough is not used:
The mkv utility suite "MKVToolNix" is installed. At least versions
"v5.7.0" or higher. NOTE: Most Linux distros distribute older versions.
Instructions to upgrading to the latest versions are at:
Downloads: %s
Source: %s
4) The utility mediainfo is installed.
Use version "MediaInfoLib - v0.7.5" or higher.
PPA: %s
Downloads: %s
5) A properly configured: config.xml or mysql.txt file.
''') % (common.__url__, MANDITORY,
common.MKVTOOLNIX_DOWNLOADS_URL,
common.MKVTOOLNIX_SOURCE_URL,
common.MEDIAINFO_PPA_URL, common.MEDIAINFO_URL)
#
MANDITORY = (MANDITORY + "%s") % ''
#
## Command line options and arguments
PARSER = OptionParser(
usage="%prog usage: lossless_cut.py -aCDefghujklmrsPStTvXw [parameters]\n")
PARSER.add_option( "-a", "--addmetadata", action="store_true",
default=False, dest="addmetadata",
help=_("Do NOT add metadata to the mkv video container."))
PARSER.add_option( "-C", "--concertcuts", metavar="concertcuts",
action="store_const", default="N/A", dest="concertcuts",
help= '''Create individual files from each cut segment and an optional
track naming configuration file. This option is referred to as "Concert Cuts".''')
PARSER.add_option( "-D", "--delayvideo", type="int",
metavar="delayvideo", dest="delayvideo",
help= _(
'''Delay option to change when the video track starts by a
positive (start sooner than other tracks) or
negative number (start later than other tracks) in milliseconds.
One second = 1000
This option should only be chosen by experienced users.'''))
PARSER.add_option( "-e", "--mythvideo_export", action="store_true",
default=False, dest="mythvideo_export",
help=_(
"Export the final mkv video into MythVideo, this includes subdirectory creation when necessary."))
PARSER.add_option( "-f", "--recordedfile", metavar="recordedfile",
default="", dest="recordedfile",
help=_(
'The absolute path and file name of the MythTV recording.'))
PARSER.add_option( "-g", "--gencutlist", action="store_true",
default=False, dest="gencutlist",
help=_(
"Generate a cut list if one does not exist but there is a skip list."))
PARSER.add_option( "-j", "--jobid", metavar="JOBID",
default="", dest="jobid",
help= '''This is the MythTV JobQueue ID from the "%JOBID%"
variable. This is a mandatory command line option if the
configuration file "error_detection" variables are used.''')
PARSER.add_option( "-k", "--keeplog", action="store_true",
default=False, dest="keeplog",
help=_(
"Keep the log file after the userjob has completed successfully."))
PARSER.add_option( "-l", "--logpath", metavar="logpath",
default="", dest="logpath",
help=_(
'Specify a directory path to save the jobs log file'))
PARSER.add_option( "-m", "--movepath", metavar="movepath",
default="", dest="movepath",
help=_(
"Specify a local directory path to move/save the final mkv video file."))
PARSER.add_option( "-r", "--replace_recorded", action="store_true",
default=False, dest="replace_recorded",
help=_(
"Replace the recorded video file with the loss less cut version. Use with caution!"))
PARSER.add_option( "-s", "--summary", action="store_true",
default=False, dest="summary",
help=_(
'''Display a summary of the options that would be used during processing
but exit before processing begins. Used this for debugging. No changes
to the mpg file occur.'''))
PARSER.add_option( "-P", "--passthrough", action="store_true",
default=False, dest="passthrough",
help=_(
"Passthrough, no subtitle processing."))
PARSER.add_option( "-S", "--noextratracks", action="store_true",
default=False, dest="noextratracks",
help=_(
"Do not include any subtitle or secondary audio tracks."))
PARSER.add_option( "-t", "--test", action="store_true",
default=False, dest="test",
help=_(
"Test that the environment meets all the scripts dependencies."))
PARSER.add_option( "-T", "--tracknumber", type="int",
metavar="tracknumber", dest="tracknumber",
help= _(
'''Identifies a specific Audio track to copy in
conjunction with the "-S" Strip option.
Find the audio track numbers using a
"> mkvmerge -i file.mkv" command. If there is only
one audio track then this option is meaningless.'''))
PARSER.add_option( "-u", "--usage", action="store_true",
default=False, dest="usage",
help=_("Display this help/usage text and exit."))
PARSER.add_option( "-v", "--version", action="store_true",
default=False, dest="version",
help=_("Display version and author information"))
PARSER.add_option( "-X", "--forcemythxxextractor", action="store_true",
default=False, dest="forcemythxxextractor",
help=_(
'''Force use of mythccextractor as the primary
utility to extract and convert subtitles tracks into SRT format.'''))
PARSER.add_option( "-w", "--workingpath", metavar="WORKINGPATH",
default="", dest="workingpath",
help=_(
'Specify a working directory path to manipulate the video file'))
#
OPTS, ARGS = PARSER.parse_args()
#
## The Concert Cuts option "-C" can optionally include a configuration
## file path. Handle three different situations.
if OPTS.concertcuts == 'N/A':
OPTS.concertcuts = False
elif OPTS.concertcuts == None and len(ARGS):
OPTS.concertcuts = ARGS[0]
else:
OPTS.concertcuts = True
#
#
## Deal with utf8 string in stdout and stderr
class OutStreamEncoder(object):
"""Wraps a stream with an encoder"""
def __init__(self, outstream, encoding=None):
self.out = outstream
if not encoding:
self.encoding = sys.getfilesystemencoding()
else:
self.encoding = encoding
def write(self, obj):
"""Wraps the output stream, encoding Unicode strings with the
specified encoding"""
# if isinstance(obj, unicode):
# try:
# self.out.write(obj.encode(self.encoding))
# except IOError:
# pass
# else:
try:
self.out.write(obj)
except IOError:
pass
def __getattr__(self, attr):
"""Delegate everything but write to the stream"""
return getattr(self.out, attr)
sys.stdout = OutStreamEncoder(sys.stdout, 'utf-8')
sys.stderr = OutStreamEncoder(sys.stderr, 'utf-8')
#
#
class Mythtvlosslesscut(object):
"""Perform loss less cut processing on a MythTV recorded video file
"""
def __init__(self,
opts, # Command line options
):
#
self.jobstatus = common.JOBSTATUS()
self.return_code = int(self.jobstatus.UNKNOWN)
#
try:
self.configuration = get_config(opts)
except Exception as errmsg:
sys.stderr.write(
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
_('''Processing the configuration file failed. Error(%s)\n''')
% errmsg)
raise errmsg
# sys.exit(int(self.jobstatus.ABORTED))
#
self.logger = create_logger(self.configuration['logfile'], filename=True)
#
## Make verbose output to the log standard
self.configuration['verbose'] = True
self.configuration['version'] = __version__
#
try:
self.mythtvinterface = Mythtvinterface(self.logger,
self.configuration)
except Exception as errmsg:
sys.stderr.write(
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
_('''Acquiring access to MythTV failed, aborting script.
Error(%s)\n''') % errmsg)
raise errmsg
# sys.exit(int(self.jobstatus.ABORTED))
#
## Set if mythtv v0.24 is installed
if self.mythtvinterface.OWN_VERSION[1] == 24:
self.configuration['v024'] = True
else:
self.configuration['v024'] = False
#
# Determine if the system is 64 or 32 bit architecture
if (sys.maxsize > 2**32):
bit_arch = common.BIT_ARCH_64
else:
bit_arch = common.BIT_ARCH_32
# Set the subtitle extraction utilities based on the system
# architecture
self.configuration['ccextractor'] = bit_arch % (
common.APPDIR, common.CCEXTRACTOR)
# Add location of the ProjectX java app
self.configuration['projectx_jar_path'] = \
'%ssubtitle/ProjectX.jar' % common.APPDIR
#
try:
self.configuration['mythutil'] = check_dependancies(
self.configuration)
except Exception as errmsg:
sys.stderr.write(
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
_('''
One or more script dependencies could not be satisfied, aborting script.
Error(%s)\n''')
% errmsg)
raise errmsg
#sys.exit(int(self.jobstatus.ABORTED))
#
self.mythtvinterface.stdout = sys.stdout
self.mythtvinterface.stderr = sys.stderr
self.mythtvinterface.etree = etree
#
## Xpath filters used to parse mediainfo XML output
# Find specific track types
self.tracks_filter = \
etree.XPath(common.TRACKS_XPATH)
self.element_filter = \
etree.XPath(common.ELEMENTS_XPATH)
#
## Remove this recording's files from the working directory
cleanup_working_dir(self.configuration['workpath'],
self.configuration['recorded_name'])
#
self.processing_started = datetime.now()
self.subtitles = None
#
## Check if the user wants to automatically generate a cut list when
## it is empty but there is a skip list
self.configuration['gencutlist'] = False
if opts.gencutlist:
self.configuration['gencutlist'] = True
#
# end __init__()
#
def cut_video_file(self):
'''
1) Check if display options was selected.
2) Test the environment and the script dependancies
was selected.
3) Process the MythTV recording.
return nothing
'''
if self.configuration['summary']:
self._display_variables(summary=True)
sys.exit(int(self.jobstatus.UNKNOWN))
#
if self.configuration['test']:
self._display_variables(summary=True)
sys.stdout.write(
_('''Congratulations! All script dependencies have been satisfied.
You are ready to perform loss less cuts on MythTV recorded videos.
'''))
sys.exit(int(self.jobstatus.UNKNOWN))
#
# Verify that one and only one of these options have been selected
count = 0
for key in ['replace', 'mythvideo_export', 'movepath']:
if self.configuration[key]:
count += 1
if count == 0:
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
sys.stderr.write(
_('''You must select one of these three options "-e", "-m", "r". See:
%s\n''') % (MANDITORY))
sys.exit(int(self.jobstatus.ABORTED))
elif count > 1:
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
sys.stderr.write(
_('''You must ONLY select ONE of these three options "-e", "-m", "r". See:
%s\n''') % (MANDITORY))
sys.exit(int(self.jobstatus.ABORTED))
#
self.logger.info(
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
_('''Start loss less commercial cut of "%s" at: %s''') % (
self.configuration['recordedfile'],
self.processing_started.strftime(common.LL_START_END_FORMAT)))
#
self._display_variables()
#
self._collect_metadate()
#
# Only process subtitles if they need to be included
if not self.configuration['passthrough'] and (not self.configuration['strip'] and self.subtitles):
self._process_subtitles()
#
self._cut_preprocessing()
#
if self.configuration['concertcuts']:
self._process_concert_cuts()
else:
self._lossless_cut()
#
self._cleanup()
#
return
#
def _collect_metadate(self,):
''' Read the recorded record and collect specific data including
metadata. If there is no metadata then use the grabbers to get
missing details.
Read the recordedseek record for the first and last frame numbers
return nothing
'''
#
self.subtitles = False
# Get the xml track info using mediainfo
self.configuration['trackinfo'] = get_mediainfo(
self.configuration['recordedfile'],
self.element_filter, self.tracks_filter,
etree, self.logger, sys)
#
## Get this recordings metadata and other data from the MyhTV DB
self.mythtvinterface.get_recorded_data()
#
display_recorded_info(self.configuration, self.logger)
#
# Check if any subtitles processing is required
if self.configuration['trackinfo']['total_subtitle']:
self.subtitles = True
#
## Set the mkv file metadata formatted title
if self.configuration['subtitle']:
if self.configuration['inetref']:
self.configuration['video_title'] = \
self.configuration['series'] % self.configuration
else:
self.configuration['video_title'] = \
'%(title)s: %(subtitle)s' % self.configuration
else:
if self.configuration['releasedate']:
self.configuration['video_title'] = \
self.configuration['movie_format'] % \
self.configuration
else:
self.configuration['video_title'] = \
'%(title)s' % self.configuration
#
self.configuration['iso639_2_lang_codes'] = \
read_iso_language_codes(logger=self.logger)
#
## Replace mythvidexport format variables with config key equivalents
for find_replace in self.configuration['mythvidexport_rep']:
self.configuration['export_format'] = self.configuration[
'export_format'].replace(find_replace[0], find_replace[1])
#
## Replace Concert Cuts format variables with config key equivalents
if self.configuration['concertcuts']:
for find_replace in self.configuration['mythvidexport_rep']:
self.configuration['concertcuts'] = self.configuration[
'concertcuts'].replace(find_replace[0], find_replace[1])
for seg_key in list(self.configuration['segment_names'].keys()):
self.configuration['segment_names'][seg_key] = \
self.configuration['segment_names'][seg_key].replace(
find_replace[0], find_replace[1])
#
self.logger.info(_('''
Recorded file:
Channel ID: %(chanid)s
Start time: %(starttime)s
''') % self.configuration)
#
return
#
def _process_subtitles(self,):
'''
If there are subtitle track(s) and they are not in srt format then
extract the track and convert to an srt subtitle file.
Copy the original recorded file and new srt file(s). During the
copy add the new srt file as a subtitle track. Then change the
recorded file name that gets cut to this new recorded mkv file.
return nothing
'''
#
self.configuration['srt_files'] = []
#
## Attempt to extract non-SRT subtitle tracks and convert
## into srt format
subtrack_num = 0
unknown_lang = 'Unknown %s'
unknown_lang_count = 1
first_subtitle_track = True
first_dvb_subtitle_track = True
for subetree in self.configuration['trackinfo']['subtitle']:
# Skip subtitle tracks that are already in SRT format
# as they will be automatically copied by mkvmerge
code_id_elem = subetree.xpath('./Codec_ID')
if code_id_elem:
if code_id_elem[0].text.startswith('S_TEXT'):
subtrack_num += 1
continue
#
try:
subid = subetree.xpath('./ID')[0]
subformat = subetree.xpath('./Format')[0].text
try:
sublanguage = subetree.xpath('./Language')[0].text
except IndexError:
# Some recording devices do not declare
# the language of a subtitle track
sublanguage = unknown_lang % unknown_lang_count
unknown_lang_count += 1
self.configuration['subid'] = ''
index = (subid.text).find(' ')
if not index == -1:
self.configuration['subid'] = (subid.text)[:index]
else:
verbage = _(
'''Subtitle streamid %s does not have a recognizable subid in the
ID sudid text "%s", skipping this subtitle track.''') % \
(self.configuration['streamid'], subid)
self.logger.info(verbage)
sys.stdout.write(verbage + '\n\n')
subtrack_num += 1
continue
self.configuration['subpage'] = ''
index = (subid.text).find(')-')
if not index == -1:
self.configuration['subpage'] = (
subid.text)[index+2:].strip()
except Exception as errmsg:
# Deal with problems with the subtitle data
verbage = _(
'''Getting Subtitle streamid %s extraction information failed
this subtitle track will be skipped.
Error: "%s"''') % (self.configuration['streamid'], errmsg)
self.logger.info(verbage)
sys.stdout.write(verbage + '\n\n')
subtrack_num += 1
continue
#
## Process DVB Subtitles
if (not self.configuration['include_dvb_subtitles'] and \
subformat.find('DVB Subtitle') != -1) or \
not first_dvb_subtitle_track:
subtrack_num += 1
continue
elif self.configuration['include_dvb_subtitles'] and \
subformat.find('DVB Subtitle') != -1 and \
first_dvb_subtitle_track:
#
self.configuration['projectx_ini_path'] = \
common.PROJECTX_INI_PATH % self.configuration
try:
fileh = open(
self.configuration['projectx_ini_path'], 'w')
except IOError as errmsg:
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
directory, basefilename = os.path.split(
self.configuration['projectx_ini_path'])
sys.stderr.write(_(
'''Could not create the ProjectX "%s" file in directory "%s".
This is likely a directory Read/Write permission issue but
check the error message below to confirm, aborting script.
Error: %s''') % (basefilename, directory, errmsg))
exit(int(self.jobstatus.ABORTED))
#
fileh.write(self.configuration['projectx_ini'])
fileh.close()
#
arguments = (common.EXTRACT_DVB_SUBTITLES %
self.configuration)
result = commandline_call(
self.configuration['java'], arguments)
#
stdout = ''
if self.configuration['verbose']:
stdout = result[1]
#
if result[0]:
self.logger.info(_('''ProjectX command:
> %s %s
%s
''' % (self.configuration['java'], arguments, stdout)))
#
for filename in glob(
('%(workpath)s/%(recorded_name)s*' %
self.configuration)):
if not filename.endswith('.idx'):
continue
# Unfortunitely even if all the subtitle
# tracks have been properly identified the fact that
# ProjectX extracts all DVB subtitles in one pass
# means that only the first subtitle track's
# langauge can be identified. Default to "Unknown"
# for any additional subtitle track languages
if first_subtitle_track:
self.configuration['srt_files'].append(
[sublanguage, filename,
self.configuration['trackinfo'][
'subtitle_details'][subtrack_num][
'Delay_relative_to_video']])
else:
sublanguage = unknown_lang % unknown_lang_count
unknown_lang_count += 1
self.configuration['srt_files'].append(
[sublanguage, filename,
self.configuration['trackinfo'][
'subtitle_details'][subtrack_num][
'Delay_relative_to_video']])
first_dvb_subtitle_track = False
subtrack_num += 1
continue
else:
verbage = \
_('''ProjectX could not extract a DVB Subtitle track.
ID %s, language "%s", Format "%s"
Commandline options: "%s"
%s''') % (subid.text, sublanguage, subformat, arguments, result[1])
self.logger.info(verbage)
#
# ProjectX extracts and converts all DVB Subtitle
# tracks in one pass therefore only execute this
# code once
first_dvb_subtitle_track = False
subtrack_num += 1
continue
#
# If this is MythTV v0.25+ try the built-in subtitle extractor
###### See ticket: http://code.mythtv.org/trac/ticket/11081
## Added a special command line option "-X" just until the ticket is resolved
## mythccextractor may work for some recording devices
if not self.configuration['v024'] and \
self.configuration['forcemythxxextractor'] and \
not first_subtitle_track:
subtrack_num += 1
continue
if not self.configuration['v024'] and \
self.configuration['forcemythxxextractor'] and \
first_subtitle_track:
#
arguments = (self.configuration['mythccextractor_args']) % (
self.configuration)
result = commandline_call('mythccextractor', arguments)
#
stdout = ''
if self.configuration['verbose']:
stdout = result[1]
#
if result[0]:
self.logger.info(_('''Mythccextractor command:
> %s %s
%s
''' % ('mythccextractor', arguments, stdout)))
for filename in glob(
('%(recorded_dir)s/%(recorded_name)s*.srt' %
self.configuration)):
# Unfortunitely even if all the subtitle
# tracks being properly identified the fact that
# mythccextractor extracts all subtitles in one pass
# means that only the first subtitle track's
# langauge can be identified. Default to "Unknown"
# for any additional subtitle track languages
if first_subtitle_track:
self.configuration['srt_files'].append(
[sublanguage, filename,
self.configuration['trackinfo'][
'subtitle_details'][subtrack_num][
'Delay_relative_to_video']])
else:
sublanguage = unknown_lang % unknown_lang_count
unknown_lang_count += 1
self.configuration['srt_files'].append(
[sublanguage, filename,
self.configuration['trackinfo'][
'subtitle_details'][subtrack_num][
'Delay_relative_to_video']])
first_subtitle_track = False
subtrack_num += 1
continue
else:
verbage = \
_('''mythccextractor could not extract a subtitle track.
ID %s, language "%s", Format "%s"
Commandline options: "%s"
%s''') % (
subid.text, sublanguage, subformat,
arguments, result[1])
self.logger.info(verbage)
#
# mythccextractor extracts and converts all subtitle
# tracks in one pass therefore only execute this
# code once
first_subtitle_track = False
#
self.configuration['sub_filename'] = os.path.join(
self.configuration['workpath'],
'%s_%03d.srt' %
(self.configuration['recorded_name'], subtrack_num))
#
# Clean up any zero sized srt file
try:
os.remove(self.configuration['sub_filename'])
except:
pass
#
# <ID>5603 (0x15E3)-888</ID>
self.configuration['streamid'] = '1'
try:
self.configuration['streamid'] = subetree.attrib['streamid']
except KeyError:
# There appears to be only one "Text" subtitle track
# that means there is no streamid attribute in the XML
# so assume this is streamid "1"
pass
#
# Attempt to extract the non-SRT subtitle tracks
## using CCExtractor and convert into SRT format
arguments = (self.configuration['ccextractor_args']) % (
self.configuration)
if not self.configuration['subpage']:
arguments = arguments.replace('-tpage ', '')
#
## Use option '-noteletext' ONLY when subformat.startswith('EIA-')
## Currently only HDHomerun recordings seems to need this
## option but other devices may have the same issue
if subformat.startswith('EIA-'):
arguments += ' -noteletext'
result = commandline_call(self.configuration['ccextractor'],
arguments)
stdout = ''
if self.configuration['verbose']:
stdout = result[1]
self.logger.info(_('''CCExtractor command:
> %s %s
%s
''' % (self.configuration['ccextractor'], arguments, stdout)))
#
if not result[0]:
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
verbage = \
_('''CCExtractor could not extract a subtitle track.
ID %s, language "%s", Format "%s"
Commandline options: "%s"
%s''') % (subid.text, sublanguage, subformat, arguments, result[1])
self.logger.info(verbage)
#
# Clean up any zero sized srt file
try:
os.remove(self.configuration['sub_filename'])
except:
pass
#
subtrack_num += 1
continue
#
self.configuration['srt_files'].append([
sublanguage, self.configuration['sub_filename'],
self.configuration['trackinfo'][
'subtitle_details'][subtrack_num][
'Delay_relative_to_video']])
subtrack_num += 1
#
# Remove any SRT files less than 20 bytes in size
srt_files = deepcopy(self.configuration['srt_files'])
for filename in srt_files:
if os.path.getsize(filename[1]) < 20:
os.remove(filename[1])
self.configuration['srt_files'].remove(filename)
continue
#
## If there are no srt files to process return
if not self.configuration['srt_files']:
return
#
## Add the srt file(s)
cmd_line_args = common.ADD_SRT_CMD % self.configuration
for srtfile in self.configuration['srt_files']:
lang_code = ''
#
## The TID "0" in "0:%s" is hardcoded for the language and
## vobsub delay.
if srtfile[0]:
lang_code = get_iso_language_code(
self.configuration['iso639_2_lang_codes'],
sublanguage,
logger=self.logger)
if lang_code:
lang_code = '--language 0:%s' % \
(lang_code)
#
## Subitle track delay
if srtfile[2] != None or (self.configuration[
'vobsub_delay'] != '0' and \
srtfile[1].find('.idx') != -1):
delay_value = 0
if srtfile[2] == None:
delay_value = self.configuration['vobsub_delay'].strip()
elif self.configuration['vobsub_delay'] == '0':
delay_value = srtfile[2]
else:
delay_value = int(self.configuration[
'vobsub_delay'].strip()) + srtfile[2]
#
lang_code += ' --sync 0:%s' % delay_value
#
cmd_line_args += ' %s "%s"' % (lang_code, srtfile[1])
#
result = commandline_call(common.MKVMERGE, cmd_line_args %
self.configuration)
stdout = ''
if self.configuration['verbose']:
stdout = result[1]
self.logger.info(_('''mkvmerge add subtitle track(s) command:
> mkvmerge %s
%s
''' % (cmd_line_args % self.configuration, stdout)))
if not result[0]:
# TRANSLATORS: Please leave %s as it is,
# because it is needed by the program.
# Thank you for contributing to this project.
verbage = \
_('''mkvmerge could not add the subtitle track(s), aborting script.
Error: %s''') % (result[1])
self.logger.critical(verbage)
sys.stderr.write(verbage + '\n')
#
## Remove this recording's files from the working directory
cleanup_working_dir(self.configuration['workpath'],
self.configuration['recorded_name'])
exit(int(self.jobstatus.ABORTED))
#
## Clean up the created srt files
for srtfile in self.configuration['srt_files']:
try:
os.remove(srtfile[1])
except OSError:
pass
#
## Change the source video file that will be cut then merged
## into a final mkv video including srt subtitle track(s).
self.configuration['sourcefile'] = \
"%(workpath)s/%(recorded_name)s_tmp.mkv" % self.configuration
#
# Refresh the xml track info using mediainfo
self.configuration['trackinfo'] = get_mediainfo(
self.configuration['recordedfile'],
self.element_filter, self.tracks_filter,
etree, self.logger, sys)
#
return
#
def _cut_preprocessing(self,):
'''
Generate the get cutlist
Get cutlist and massage it to match keyframes
Create timecode split list using recordseek's fps value
Build the track appendto list
Build the merge string of segments
return nothing
'''
# If the video is being exported set the move directory
# and mkv title to the recorded dir and the filename.
# With export trick logic into processing the video
# as if it was a move.
if self.configuration['mythvideo_export']:
# replace PATH like variables from series/title/subtitle to construct valid export_path_file
self.configuration['series'] = self.configuration['series']\