Skip to content

Instantly share code, notes, and snippets.

@brouberol
Last active December 2, 2019 13:33
Show Gist options
  • Save brouberol/1a8c50dd63029813f4f1bd77adb7d45b to your computer and use it in GitHub Desktop.
Save brouberol/1a8c50dd63029813f4f1bd77adb7d45b to your computer and use it in GitHub Desktop.
Apply a time offset to each subtitle in an srt file
#!/usr/bin/env python3
"""
Script mutating a subtitles (.srt) file by applying a time offset to each subtitle.
Examples:
% srtoffset movie.srt '00:00:31,500'
% srtoffset movie.srt '00:00:03,125' --rewind
"""
import argparse
import os
import sys
import re
TIMING_PATTERN = re.compile(
r"(?P<hours>\d{2}):(?P<minutes>\d{2}):(?P<seconds>\d{2}),(?P<ms>\d{3})")
TIME_SEPARATOR = ' --> '
def parse_args():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('subtitles', help='The subtitles file to manipulate')
parser.add_argument('offset', help='The offset to apply to the subtitles file')
parser.add_argument(
'--rewind',
action='store_true', default=False,
help='Apply a negative time offset to the subtitle timing')
parser.add_argument(
'-i', '--inplace',
action='store_true', default=False,
help='Perform the translation in-place')
return parser.parse_args()
class Subtitle:
"""A subtitle line"""
@classmethod
def from_group(cls, group):
start_time, end_time = map(Time.from_str, group[1].split(TIME_SEPARATOR))
return cls(
index=group[0],
start_time=start_time,
end_time=end_time,
text='.\n'.join(group[2:]))
def __init__(self, index, start_time, end_time, text):
self.index = index
self.start_time = start_time
self.end_time = end_time
self.text = text
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __add__(self, offset):
self.start_time += offset
self.end_time += offset
return self
def __str__(self):
return f"{self.index}\n{self.start_time}{TIME_SEPARATOR}{self.end_time}\n{self.text}"
class SubtitlesFile:
def __init__(self, filepath):
self.filepath = filepath
with open(self.filepath) as f:
self.subtitles = self._parse(f)
def __str__(self):
str_subs = []
for sub in self.subtitles:
str_subs.append(str(sub))
return '\n\n'.join(str_subs)
def __add__(self, offset):
"""Mutate each subtitle line in the file by applying the argument time offset"""
for i, _ in enumerate(self.subtitles):
self.subtitles[i] += offset
return self
def _parse(self, f):
subtitles, group = [], []
for line in f:
line = line.strip()
if line:
group.append(line)
else:
subtitles.append(Subtitle.from_group(group))
group = []
if group:
subtitles.append(Subtitle.from_group(group))
return subtitles
class Time:
@classmethod
def from_str(cls, t_str, rewind=False):
m = re.match(TIMING_PATTERN, t_str)
if not m:
raise ValueError
d = m.groupdict()
factor = -1 if rewind else 1
return cls(
hours=factor * int(d['hours']),
minutes=factor * int(d['minutes']),
seconds=factor * int(d['seconds']),
milliseconds=factor * int(d['ms']))
def __init__(self, hours, minutes, seconds, milliseconds):
self.hours = hours
self.minutes = minutes
self.seconds = seconds
self.milliseconds = milliseconds
def __repr__(self):
return f"<{self.__class__.__name__} {str(self)}>"
def __str__(self):
h = str(self.hours).zfill(2)
m = str(self.minutes).zfill(2)
s = str(self.seconds).zfill(2)
ms = str(self.milliseconds).zfill(3)
return f"{h}:{m}:{s},{ms}"
def __eq__(self, other):
return self.__dict__ == other.__dict__
def __add__(self, other):
extra_seconds, ms = divmod(self.milliseconds + other.milliseconds, 1000)
extra_minutes, seconds = divmod(self.seconds + other.seconds + extra_seconds, 60)
extra_hours, minutes = divmod(self.minutes + other.minutes + extra_minutes, 60)
hours = self.hours + other.hours + extra_hours
return self.__class__(hours, minutes, seconds, ms)
def main():
args = parse_args()
if not os.path.exists(args.subtitles):
print(f"{args.subtitles} file does not exists. Exiting.")
sys.exit(1)
try:
time_offset = Time.from_str(args.offset, rewind=args.rewind)
except ValueError:
print("Could not parse argument offset. Must be of form {hh}:{mm}:{ss},{ms}")
sys.exit(1)
subfile = SubtitlesFile(args.subtitles)
subfile += time_offset
if args.inplace:
with open(args.filepath, 'w') as subfile:
subfile.write(str(subtitles))
else:
print(str(subfile))
if __name__ == '__main__':
assert Time(0, 0, 0, 0) + Time(0, 0, 1, 0) == Time(0, 0, 1, 0)
assert Time(0, 0, 0, 1) + Time(0, 0, 1, 0) == Time(0, 0, 1, 1)
assert Time(0, 0, 1, 1) + Time(0, 0, 1, 0) == Time(0, 0, 2, 1)
assert Time(0, 30, 0, 0) + Time(0, 31, 0, 0) == Time(1, 1, 0, 0)
assert Time(0, 0, 2, 0) + Time(0, 0, -1, 0) == Time(0, 0, 1, 0)
assert Time(0, 1, 0, 0) + Time(0, 0, -1, 0) == Time(0, 0, 59, 0)
assert Time(1, 0, 0, 0) + Time(0, -1, 0, 0) == Time(0, 59, 0, 0)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment