/usr/lib/python3/dist-packages/pysrt/commands.py is in python3-pysrt 1.0.1-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 | #!/usr/bin/env python
# -*- coding: utf-8 -*-
# pylint: disable-all
import os
import re
import sys
import codecs
import shutil
import argparse
from textwrap import dedent
from chardet import detect
from pysrt import SubRipFile, SubRipTime, VERSION_STRING
def underline(string):
return "\033[4m%s\033[0m" % string
class TimeAwareArgumentParser(argparse.ArgumentParser):
RE_TIME_REPRESENTATION = re.compile(r'^\-?(\d+[hms]{0,2}){1,4}$')
def parse_args(self, args=None, namespace=None):
time_index = -1
for index, arg in enumerate(args):
match = self.RE_TIME_REPRESENTATION.match(arg)
if match:
time_index = index
break
if time_index >= 0:
args.insert(time_index, '--')
return super(TimeAwareArgumentParser, self).parse_args(args, namespace)
class SubRipShifter(object):
BACKUP_EXTENSION = '.bak'
RE_TIME_STRING = re.compile(r'(\d+)([hms]{0,2})')
UNIT_RATIOS = {
'ms': 1,
'': SubRipTime.SECONDS_RATIO,
's': SubRipTime.SECONDS_RATIO,
'm': SubRipTime.MINUTES_RATIO,
'h': SubRipTime.HOURS_RATIO,
}
DESCRIPTION = dedent("""\
Srt subtitle editor
It can either shift, split or change the frame rate.
""")
TIMESTAMP_HELP = "A timestamp in the form: [-][Hh][Mm]S[s][MSms]"
SHIFT_EPILOG = dedent("""\
Examples:
1 minute and 12 seconds foreward (in place):
$ srt -i shift 1m12s movie.srt
half a second foreward:
$ srt shift 500ms movie.srt > othername.srt
1 second and half backward:
$ srt -i shift -1s500ms movie.srt
3 seconds backward:
$ srt -i shift -3 movie.srt
""")
RATE_EPILOG = dedent("""\
Examples:
Convert 23.9fps subtitles to 25fps:
$ srt -i rate 23.9 25 movie.srt
""")
LIMITS_HELP = "Each parts duration in the form: [Hh][Mm]S[s][MSms]"
SPLIT_EPILOG = dedent("""\
Examples:
For a movie in 2 parts with the first part 48 minutes and 18 seconds long:
$ srt split 48m18s movie.srt
=> creates movie.1.srt and movie.2.srt
For a movie in 3 parts of 20 minutes each:
$ srt split 20m 20m movie.srt
=> creates movie.1.srt, movie.2.srt and movie.3.srt
""")
FRAME_RATE_HELP = "A frame rate in fps (commonly 23.9 or 25)"
ENCODING_HELP = dedent("""\
Change file encoding. Useful for players accepting only latin1 subtitles.
List of supported encodings: http://docs.python.org/library/codecs.html#standard-encodings
""")
BREAK_EPILOG = dedent("""\
Break lines longer than defined length
""")
LENGTH_HELP = "Maximum number of characters per line"
def __init__(self):
self.output_file_path = None
def build_parser(self):
parser = TimeAwareArgumentParser(description=self.DESCRIPTION, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-i', '--in-place', action='store_true', dest='in_place',
help="Edit file in-place, saving a backup as file.bak (do not works for the split command)")
parser.add_argument('-e', '--output-encoding', metavar=underline('encoding'), action='store', dest='output_encoding',
type=self.parse_encoding, help=self.ENCODING_HELP)
parser.add_argument('-v', '--version', action='version', version='%%(prog)s %s' % VERSION_STRING)
subparsers = parser.add_subparsers(title='commands')
shift_parser = subparsers.add_parser('shift', help="Shift subtitles by specified time offset", epilog=self.SHIFT_EPILOG, formatter_class=argparse.RawTextHelpFormatter)
shift_parser.add_argument('time_offset', action='store', metavar=underline('offset'),
type=self.parse_time, help=self.TIMESTAMP_HELP)
shift_parser.set_defaults(action=self.shift)
rate_parser = subparsers.add_parser('rate', help="Convert subtitles from a frame rate to another", epilog=self.RATE_EPILOG, formatter_class=argparse.RawTextHelpFormatter)
rate_parser.add_argument('initial', action='store', type=float, help=self.FRAME_RATE_HELP)
rate_parser.add_argument('final', action='store', type=float, help=self.FRAME_RATE_HELP)
rate_parser.set_defaults(action=self.rate)
split_parser = subparsers.add_parser('split', help="Split a file in multiple parts", epilog=self.SPLIT_EPILOG, formatter_class=argparse.RawTextHelpFormatter)
split_parser.add_argument('limits', action='store', nargs='+', type=self.parse_time, help=self.LIMITS_HELP)
split_parser.set_defaults(action=self.split)
break_parser = subparsers.add_parser('break', help="Break long lines", epilog=self.BREAK_EPILOG, formatter_class=argparse.RawTextHelpFormatter)
break_parser.add_argument('length', action='store', type=int, help=self.LENGTH_HELP)
break_parser.set_defaults(action=self.break_lines)
parser.add_argument('file', action='store')
return parser
def run(self, args):
self.arguments = self.build_parser().parse_args(args)
if self.arguments.in_place:
self.create_backup()
self.arguments.action()
def parse_time(self, time_string):
negative = time_string.startswith('-')
if negative:
time_string = time_string[1:]
ordinal = sum(int(value) * self.UNIT_RATIOS[unit] for value, unit
in self.RE_TIME_STRING.findall(time_string))
return -ordinal if negative else ordinal
def parse_encoding(self, encoding_name):
try:
codecs.lookup(encoding_name)
except LookupError as error:
raise argparse.ArgumentTypeError(error.message)
return encoding_name
def shift(self):
self.input_file.shift(milliseconds=self.arguments.time_offset)
self.input_file.write_into(self.output_file)
def rate(self):
ratio = self.arguments.final / self.arguments.initial
self.input_file.shift(ratio=ratio)
self.input_file.write_into(self.output_file)
def split(self):
limits = [0] + self.arguments.limits + [self.input_file[-1].end.ordinal + 1]
base_name, extension = os.path.splitext(self.arguments.file)
for index, (start, end) in enumerate(zip(limits[:-1], limits[1:])):
file_name = '%s.%s%s' % (base_name, index + 1, extension)
part_file = self.input_file.slice(ends_after=start, starts_before=end)
part_file.shift(milliseconds=-start)
part_file.clean_indexes()
part_file.save(path=file_name, encoding=self.output_encoding)
def create_backup(self):
backup_file = self.arguments.file + self.BACKUP_EXTENSION
if not os.path.exists(backup_file):
shutil.copy2(self.arguments.file, backup_file)
self.output_file_path = self.arguments.file
self.arguments.file = backup_file
def break_lines(self):
split_re = re.compile(r'(.{,%i})(?:\s+|$)' % self.arguments.length)
for item in self.input_file:
item.text = '\n'.join(split_re.split(item.text)[1::2])
self.input_file.write_into(self.output_file)
@property
def output_encoding(self):
return self.arguments.output_encoding or self.input_file.encoding
@property
def input_file(self):
if not hasattr(self, '_source_file'):
with open(self.arguments.file, 'rb') as f:
content = f.read()
encoding = detect(content).get('encoding')
encoding = self.normalize_encoding(encoding)
self._source_file = SubRipFile.open(self.arguments.file,
encoding=encoding, error_handling=SubRipFile.ERROR_LOG)
return self._source_file
@property
def output_file(self):
if not hasattr(self, '_output_file'):
if self.output_file_path:
self._output_file = codecs.open(self.output_file_path, 'w+', encoding=self.output_encoding)
else:
self._output_file = sys.stdout
return self._output_file
def normalize_encoding(self, encoding):
return encoding.lower().replace('-', '_')
def main():
SubRipShifter().run(sys.argv[1:])
if __name__ == '__main__':
main()
|