Files
gtav-src/tools_ng/techart/dcc/motionbuilder2014/python/external/pytimecode/pytimecode.py
T
2025-09-29 00:52:08 +02:00

225 lines
9.6 KiB
Python
Executable File

"""Module for manipulating SMPTE timecode. Supports 60, 59.94, 50, 30, 29.97, 25, 24, 23.98 frame rates in drop and non-drop where applicable, and milliseconds. It also supports
operator overloading for addition, subtraction, multiplication, and division.
iter_return sets the format that iterations return, the options are "tc" for a timecode string,
"frames" for a int total frames, and "tc_tuple" for a tuple of ints in the following format,
(hours, minutes, seconds, frames).
Notes: *There is a 24 hour SMPTE Timecode limit, so if your time exceeds that limit, it will roll over.
*2 PyTimeCode objects of the same frame rate is the only supported way to combine PyTimeCode objects,
for example adding them together.
Copyright Joshua Banton"""
class PyTimeCode(object):
def __init__(self, framerate, start_timecode = None, frames = None, drop_frame = False, iter_return="tc"):
"""frame rate can be string '60', '59.94', '50', '30', '29.97', '25', '24', '23.98', or 'ms'"""
self.framerate = framerate
self.int_framerate = self.set_int_framerate()
self.drop_frame = drop_frame
self.iter_return = iter_return
self.hrs = None
self.mins = None
self.secs = None
self.frs = None
self.frames = None
if start_timecode:
self.set_timecode(start_timecode)
self.tc_to_frames()
elif not frames==None:#because 0==False, and frames can be 0
self.frames = int(frames)
self.frames_to_tc(frame_only=True)
self.__check_drop_frame__()
def set_timecode(self, timecode):
"""sets timecode to argument 'timecode'"""
self.hrs, self.mins, self.secs, self.frs = self.parse_timecode(timecode)
def tc_to_frames(self):
"""converts corrent timecode to frames"""
frames = (((self.hrs * 3600) + (self.mins * 60) + self.secs) * self.int_framerate) + self.frs
if self.drop_frame:
del_frames = self.calc_drop_frames()
frames = frames - del_frames
self.frames = frames
def frames_to_tc(self, frame_only=False):
"""converts frames back to timecode, if frame_only==True, it needs to
calculate the drop frames without looking at self.hrs, etc."""
if self.drop_frame:
drop_frames = self.calc_drop_frames(frame_only)
frames = self.frames + drop_frames
else:
frames = self.frames
self.hrs = frames/(3600*self.int_framerate)
#check to see if hours => 24. SMPTE Timecode only goes to 24 hours
if self.hrs > 23:
self.hrs = self.hrs % 24
frames = frames - (24 * 3600 * self.int_framerate)
self.mins = (frames%(3600*self.int_framerate))/(60*self.int_framerate)
self.secs = ((frames%(3600*self.int_framerate))%(60*self.int_framerate))/self.int_framerate
self.frs = ((frames%(3600*self.int_framerate))%(60*self.int_framerate))%self.int_framerate
if self.drop_frame:
if self.frs == 0 and (self.mins % 10): #tests to see if frames is 0 and if minutes is not an even 10, 20, 30, etc
if self.framerate == '59.94':
self.frs = 4
elif self.framerate == '29.97':
self.frs = 2
self.tc_to_frames()
def calc_drop_frames(self, frame_only=False):
if frame_only:
hours = self.frames/(3600*self.int_framerate)
mins = (self.frames%(3600*self.int_framerate))/(60*self.int_framerate)
if mins%10: #if the minutes is not a multiple of 10, there needs to be one more drop frame unit, 2 or 4
extra = 1
else:
extra = 0
if self.framerate == '59.94':
return (hours * 6 * 36) + ((mins/10) *36) + (mins%10 * 4) + (extra * 4)
elif self.framerate == '29.97':
return (hours * 6 * 18) + ((mins/10) *18) + (mins%10 * 2) + (extra * 2)
elif self.framerate == '59.94':
return (self.hrs * 6 * 36) + ((self.mins/10) *36) + (self.mins%10 * 4)
elif self.framerate == '29.97':
return (self.hrs * 6 * 18) + ((self.mins/10) *18) + (self.mins%10 * 2)
else:
raise PyTimeCodeError('Drop frame with '+self.framerate+'fps not supported, only 29.97 & 59.94.')
def set_int_framerate(self):
if self.framerate == '29.97':
int_framerate = 30
elif self.framerate == '59.94':
int_framerate = 60
elif self.framerate == '23.98':
int_framerate = 24
elif self.framerate == 'ms':
int_framerate = 1000
elif self.framerate == 'frames':
int_framerate = 1
else:
int_framerate = int(self.framerate)
return int_framerate
def parse_timecode(self, timecode):
"""parses timecode string frames '00:00:00:00' or '00:00:00;00' or milliseconds '00:00:00:000'"""
if len(timecode) == 11:
frs = int(timecode[9:11])
elif len(timecode) == 12 and self.framerate == 'ms':
frs = int(timecode[9:12])
else:
raise PyTimeCodeError('Timecode string parsing error. ' + timecode)
hrs = int(timecode[0:2])
mins = int(timecode[3:5])
secs = int(timecode[6:8])
return hrs, mins, secs, frs
def make_timecode(self):
self.frames_to_tc()
hr_str = self.__set_time_str(self.hrs)
min_str = self.__set_time_str(self.mins)
sec_str = self.__set_time_str(self.secs)
frame_str = self.__set_time_str(self.frs)
timecode_str = "%s:%s:%s:%s" % (hr_str, min_str, sec_str, frame_str)
return timecode_str
def __set_time_str(self, time):
if len(str(time)) > 1:
time_str = str(time)
else:
time_str = "0%s" % time
return time_str
def __iter__(self):
return self
def next(self):
self.add_frames(1)
return self.__return_item__()
def back(self):
self.sub_frames(1)
return self.__return_item__()
def __check_drop_frame__(self):
if not self.drop_frame:
return True
elif self.framerate == "29.97" or self.framerate == "59.94":
return True
else:
raise PyTimeCodeError('Drop frame with '+self.framerate+'fps not supported, only 29.97 & 59.94.')
def __return_item__(self):
if self.iter_return == 'tc':
return self.make_timecode()
elif self.iter_return == 'frames':
return self.frames
elif self.iter_return == 'tc_tuple':
return (self.hrs, self.mins, self.secs, self.frs)
def add_frames(self, frames):
"""adds or subtracts frames number of frames"""
self.frames = self.frames + frames
def sub_frames(self, frames):
"""adds or subtracts frames number of frames"""
self.__add_timecode__(-frames)
def mult_frames(self, frames):
"""adds or subtracts frames number of frames"""
self.frames = self.frames * frames
def div_frames(self, frames):
"""adds or subtracts frames number of frames"""
self.frames = self.frames / frames
def __add__(self, other):
"""returns new pytimecode object with added timecodes"""
if type(other) == PyTimeCode:
added_frames = self.frames + other.frames
elif type(other) == int:
added_frames = self.frames + other
else:
raise PyTimeCodeError('Type '+str(type(other))+' not supported for arithmetic.')
newtc = PyTimeCode(self.framerate, start_timecode=None, frames=added_frames, drop_frame=self.drop_frame)
return newtc
def __sub__(self, other):
"""returns new pytimecode object with added timecodes"""
if type(other) == PyTimeCode:
subtracted_frames = self.frames - other.frames
elif type(other) == int:
subtracted_frames = self.frames - other
else:
raise PyTimeCodeError('Type '+str(type(other))+' not supported for arithmetic.')
newtc = PyTimeCode(self.framerate, start_timecode=None, frames=subtracted_frames, drop_frame=self.drop_frame)
return newtc
def __mul__(self, other):
"""returns new pytimecode object with added timecodes"""
if type(other) == PyTimeCode:
mult_frames = self.frames * other.frames
elif type(other) == int:
mult_frames = self.frames * other
else:
raise PyTimeCodeError('Type '+str(type(other))+' not supported for arithmetic.')
newtc = PyTimeCode(self.framerate, start_timecode=None, frames=mult_frames, drop_frame=self.drop_frame)
return newtc
def __div__(self, other):
"""returns new pytimecode object with added timecodes"""
if type(other) == PyTimeCode:
div_frames = self.frames / other.frames
elif type(other) == int:
div_frames = self.frames /other
else:
raise PyTimeCodeError('Type '+str(type(other))+' not supported for arithmetic.')
newtc = PyTimeCode(self.framerate, start_timecode=None, frames=div_frames, drop_frame=self.drop_frame)
return newtc
def __repr__(self):
return self.make_timecode()
class PyTimeCodeError(Exception):
pass