You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
113 lines
3.4 KiB
113 lines
3.4 KiB
#!/usr/bin/env python
|
|
|
|
"""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()
|