diff options
Diffstat (limited to '.local/bin/srt-mux')
-rwxr-xr-x | .local/bin/srt-mux | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/.local/bin/srt-mux b/.local/bin/srt-mux new file mode 100755 index 0000000..3e26621 --- /dev/null +++ b/.local/bin/srt-mux @@ -0,0 +1,112 @@ +#!/usr/local/bin/python3 + +"""Merge multiple subtitles together into one.""" + +import datetime +import srt_tools.utils +import logging +import operator + +log = logging.getLogger(__name__) + +TOP = r"{\an8}" +BOTTOM = r"{\an2}" + + +def parse_args(): + examples = { + "Merge English and Chinese subtitles": "srt mux -i eng.srt -i chs.srt -o both.srt", + "Merge subtitles, with one on top and one at the bottom": "srt mux -t -i eng.srt -i chs.srt -o both.srt", + } + parser = srt_tools.utils.basic_parser( + description=__doc__, examples=examples, multi_input=True + ) + parser.add_argument( + "--ms", + metavar="MILLISECONDS", + default=datetime.timedelta(milliseconds=600), + type=lambda ms: datetime.timedelta(milliseconds=int(ms)), + help="if subs being muxed are within this number of milliseconds " + "of each other, they will have their times matched (default: 600)", + ) + parser.add_argument( + "-w", + "--width", + default=5, + type=int, + help="how many subs to consider for time matching at once (default: %(default)s)", + ) + parser.add_argument( + "-t", + "--top-and-bottom", + action="store_true", + help="use SSA-style tags to place files at the top and bottom, respectively. Turns off time matching", + ) + parser.add_argument( + "--no-time-matching", + action="store_true", + help="don't try to do time matching for close subtitles (see --ms)", + ) + return parser.parse_args() + + +def merge_subs(subs, acceptable_diff, attr, width): + """ + Merge subs with similar start/end times together. This prevents the + subtitles jumping around the screen. + + The merge is done in-place. + """ + sorted_subs = sorted(subs, key=operator.attrgetter(attr)) + + for subs in srt_tools.utils.sliding_window(sorted_subs, width=width): + current_sub = subs[0] + future_subs = subs[1:] + current_comp = getattr(current_sub, attr) + + for future_sub in future_subs: + future_comp = getattr(future_sub, attr) + if current_comp + acceptable_diff > future_comp: + log.debug( + "Merging %d's %s time into %d", + future_sub.index, + attr, + current_sub.index, + ) + setattr(future_sub, attr, current_comp) + else: + # Since these are sorted, and this one didn't match, we can be + # sure future ones won't match either. + break + + +def main(): + args = parse_args() + logging.basicConfig(level=args.log_level) + + srt_tools.utils.set_basic_args(args) + + muxed_subs = [] + for idx, subs in enumerate(args.input): + for sub in subs: + if args.top_and_bottom: + if idx % 2 == 0: + sub.content = TOP + sub.content + else: + sub.content = BOTTOM + sub.content + muxed_subs.append(sub) + + if args.no_time_matching or not args.top_and_bottom: + merge_subs(muxed_subs, args.ms, "start", args.width) + merge_subs(muxed_subs, args.ms, "end", args.width) + + output = srt_tools.utils.compose_suggest_on_fail(muxed_subs, strict=args.strict) + + try: + args.output.write(output) + except (UnicodeEncodeError, TypeError): # Python 2 fallback + args.output.write(output.encode(args.encoding)) + + +if __name__ == "__main__": # pragma: no cover + main() |