SubRip (.srt) subtitle parser and writer for Python
—
Time representation and manipulation through the SubRipTime class. Supports the standard SubRip time format (HH:MM:SS,mmm), arithmetic operations, format conversion, and coercion from various input types.
Create time objects from various sources and formats.
class SubRipTime:
def __init__(self, hours=0, minutes=0, seconds=0, milliseconds=0):
"""
Create a new SubRipTime instance.
Args:
hours (int): Hour component (default: 0)
minutes (int): Minute component (default: 0)
seconds (int): Second component (default: 0)
milliseconds (int): Millisecond component (default: 0)
"""
@classmethod
def coerce(cls, other):
"""
Convert various types to SubRipTime instance.
Args:
other: Input to convert (str, int, datetime.time, dict, tuple, or SubRipTime)
Returns:
SubRipTime: Converted time object
Supported types:
- str/unicode: "HH:MM:SS,mmm" format
- int: Total milliseconds
- datetime.time: Standard time object
- dict: {'hours': h, 'minutes': m, 'seconds': s, 'milliseconds': ms}
- tuple/list: (hours, minutes, seconds, milliseconds)
"""
@classmethod
def from_string(cls, source):
"""
Parse time from SubRip format string.
Args:
source (str): Time string in "HH:MM:SS,mmm" format
Returns:
SubRipTime: Parsed time object
Raises:
InvalidTimeString: If format is invalid
"""
@classmethod
def from_ordinal(cls, ordinal):
"""
Create time from total millisecond count.
Args:
ordinal (int): Total milliseconds
Returns:
SubRipTime: Time object representing the duration
"""
@classmethod
def from_time(cls, source):
"""
Create time from datetime.time object.
Args:
source (datetime.time): Standard time object
Returns:
SubRipTime: Converted time object
"""
@classmethod
def parse_int(cls, digits):
"""
Parse integer from string with error tolerance.
Args:
digits (str): String containing digits
Returns:
int: Parsed integer value (0 if parsing fails)
"""Access and modify individual time components.
@property
def hours(self):
"""
Hour component (0-23).
Returns:
int: Hours
"""
@property
def minutes(self):
"""
Minute component (0-59).
Returns:
int: Minutes
"""
@property
def seconds(self):
"""
Second component (0-59).
Returns:
int: Seconds
"""
@property
def milliseconds(self):
"""
Millisecond component (0-999).
Returns:
int: Milliseconds
"""
@property
def ordinal(self):
"""
Total time in milliseconds.
Returns:
int: Total milliseconds since 00:00:00,000
"""Perform arithmetic and timing operations.
def shift(self, *args, **kwargs):
"""
Add time offset or apply ratio transformation.
Args:
*args: Positional time arguments (hours, minutes, seconds, milliseconds)
**kwargs: Named time arguments or ratio for scaling
Supported kwargs:
hours (int): Hours to add
minutes (int): Minutes to add
seconds (int): Seconds to add
milliseconds (int): Milliseconds to add
ratio (float): Multiply time by this ratio
"""
def to_time(self):
"""
Convert to standard datetime.time object.
Returns:
datetime.time: Standard Python time object
"""SubRipTime supports arithmetic operations with other time objects.
# Addition
time1 + time2 # Add two times
time += other_time # In-place addition
# Subtraction
time1 - time2 # Subtract times
time -= other_time # In-place subtraction
# Multiplication (scaling)
time * ratio # Scale time by ratio
time *= ratio # In-place scalingCompare times using standard comparison operators.
time1 < time2 # Earlier than
time1 <= time2 # Earlier than or equal
time1 == time2 # Equal times
time1 >= time2 # Later than or equal
time1 > time2 # Later than
time1 != time2 # Not equalConvert times to various string formats.
def __str__(self):
"""
Convert to SubRip format string.
Returns:
str: Time in "HH:MM:SS,mmm" format (negative times shown as 00:00:00,000)
"""
def __repr__(self):
"""
Convert to Python representation.
Returns:
str: "SubRipTime(hours, minutes, seconds, milliseconds)" format
"""Iterate over time components.
def __iter__(self):
"""
Iterate over time components.
Yields:
int: Hours, minutes, seconds, milliseconds in order
"""import pysrt
from datetime import time
# Create from components
t1 = pysrt.SubRipTime(1, 30, 45, 500) # 01:30:45,500
print(t1) # "01:30:45,500"
# Create from string
t2 = pysrt.SubRipTime.from_string("00:02:15,750")
# Create from total milliseconds
t3 = pysrt.SubRipTime.from_ordinal(135500) # 00:02:15,500
# Create from datetime.time
py_time = time(14, 30, 15, 250000) # 250000 microseconds = 250 ms
t4 = pysrt.SubRipTime.from_time(py_time)
# Coerce from various formats
t5 = pysrt.SubRipTime.coerce("01:23:45,678")
t6 = pysrt.SubRipTime.coerce(90000) # 90 seconds in milliseconds
t7 = pysrt.SubRipTime.coerce({'minutes': 5, 'seconds': 30})
t8 = pysrt.SubRipTime.coerce((0, 1, 30, 0)) # tuple format# Basic arithmetic
start = pysrt.SubRipTime(0, 1, 0, 0) # 00:01:00,000
duration = pysrt.SubRipTime(0, 0, 3, 500) # 00:00:03,500
end = start + duration # 00:01:03,500
# Time differences
total_time = pysrt.SubRipTime(1, 30, 0, 0)
elapsed = pysrt.SubRipTime(0, 45, 30, 0)
remaining = total_time - elapsed # 00:44:30,000
# Scaling operations
original = pysrt.SubRipTime(0, 2, 0, 0) # 2 minutes
double_speed = original * 0.5 # 1 minute (half duration)
slow_motion = original * 2.0 # 4 minutes (double duration)time_obj = pysrt.SubRipTime(2, 15, 30, 750) # 02:15:30,750
# Access components
print(f"Hours: {time_obj.hours}") # 2
print(f"Minutes: {time_obj.minutes}") # 15
print(f"Seconds: {time_obj.seconds}") # 30
print(f"Milliseconds: {time_obj.milliseconds}") # 750
# Total milliseconds
print(f"Total ms: {time_obj.ordinal}") # 8130750
# Component modification
time_obj.hours = 3
time_obj.minutes = 0
print(time_obj) # "03:00:30,750"
# Iteration over components
hours, minutes, seconds, ms = time_obj
components = list(time_obj) # [3, 0, 30, 750]# Offset operations
time_obj = pysrt.SubRipTime(1, 0, 0, 0) # 01:00:00,000
# Add components
time_obj.shift(minutes=30, seconds=15) # 01:30:15,000
time_obj.shift(hours=-1, milliseconds=500) # 00:30:15,500
# Ratio scaling for framerate conversion
# Convert from 23.976 fps to 25 fps
time_obj.shift(ratio=25/23.976)
# In-place operations
time_obj += pysrt.SubRipTime(0, 0, 5, 0) # Add 5 seconds
time_obj -= pysrt.SubRipTime(0, 0, 2, 500) # Subtract 2.5 seconds
time_obj *= 1.1 # Scale by 10%# Convert to different formats
srt_time = pysrt.SubRipTime(14, 30, 15, 250)
# Standard Python time object
py_time = srt_time.to_time()
print(py_time) # 14:30:15.250000
# String representations
print(str(srt_time)) # "14:30:15,250"
print(repr(srt_time)) # "SubRipTime(14, 30, 15, 250)"
# Total seconds (with decimal)
total_seconds = srt_time.ordinal / 1000.0 # 52215.25
# Format for different systems
ffmpeg_format = str(srt_time).replace(',', '.') # "14:30:15.250"try:
# Invalid format
bad_time = pysrt.SubRipTime.from_string("25:70:90,1500")
except pysrt.InvalidTimeString as e:
print(f"Invalid time format: {e}")
# Negative time handling
negative = pysrt.SubRipTime(0, 0, 5, 0) - pysrt.SubRipTime(0, 0, 10, 0)
print(negative.ordinal) # -5000 (internal representation)
print(str(negative)) # "00:00:00,000" (displayed as zero)
# Very large times
large_time = pysrt.SubRipTime(99, 59, 59, 999) # Maximum reasonable time
print(large_time) # "99:59:59,999"# Time-based subtitle operations
def adjust_subtitle_timing(subs, delay_ms):
"""Add delay to all subtitles"""
delay = pysrt.SubRipTime.from_ordinal(delay_ms)
for sub in subs:
sub.start += delay
sub.end += delay
def synchronize_subtitles(subs, reference_time, actual_time):
"""Sync subtitles based on a reference point"""
ratio = actual_time.ordinal / reference_time.ordinal
for sub in subs:
sub.shift(ratio=ratio)
def find_gaps(subs, min_gap_ms=100):
"""Find gaps between subtitles"""
gaps = []
min_gap = pysrt.SubRipTime.from_ordinal(min_gap_ms)
for i in range(len(subs) - 1):
current_end = subs[i].end
next_start = subs[i + 1].start
gap = next_start - current_end
if gap > min_gap:
gaps.append((i, gap))
return gaps
# Usage
subs = pysrt.open('movie.srt')
adjust_subtitle_timing(subs, 2500) # 2.5 second delay
gaps = find_gaps(subs)
print(f"Found {len(gaps)} significant gaps")TIME_PATTERN: str = '%02d:%02d:%02d,%03d' # String format pattern
SECONDS_RATIO: int = 1000 # Milliseconds per second
MINUTES_RATIO: int = 60000 # Milliseconds per minute
HOURS_RATIO: int = 3600000 # Milliseconds per hourInstall with Tessl CLI
npx tessl i tessl/pypi-pysrt