diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 10baeaa..3da6402 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [2.7, 3.7, 3.8, 3.9, "3.10"] + python-version: [2.7, 3.7, 3.8, 3.9, "3.10", "3.11"] steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index 2d5aaef..478ec67 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,33 @@ About ----- -Python Module for manipulating SMPTE timecode. Supports 23.976, 23.98, 24, 25, -29.97, 30, 50, 59.94, 60 frame rates and milliseconds (1000 fps). +Python module for manipulating SMPTE timecode. Supports any arbitrary integer frame +rates and some default str values of 23.976, 23.98, 24, 25, 29.97, 30, 50, 59.94, 60 +frame rates and milliseconds (1000 fps) and fractional frame rates like "30001/1001". -This library is a fork of the original PyTimeCode python library. You should -not use the two library together (PyTimeCode is not maintained and has known -bugs). +This library is a fork of the original PyTimeCode python library. You should not use +the two library together (PyTimeCode is not maintained and has known bugs). The math behind the drop frame calculation is based on the [blog post of David Heidelberger](http://www.davidheidelberger.com/blog/?p=29). -Simple math operations like, addition, subtraction, multiplication or division -with an integer value or with a timecode is possible. Math operations between -timecodes with different frame rates are supported. So: +Simple math operations like, addition, subtraction, multiplication or division with an +integer value or with a timecode is possible. Math operations between timecodes with +different frame rates are supported. So: ```py from timecode import Timecode tc1 = Timecode('29.97', '00:00:00;00') -tc2 = Timecode('24', '00:00:00:10') +tc2 = Timecode(24, '00:00:00:10') tc3 = tc1 + tc2 assert tc3.framerate == '29.97' assert tc3.frames == 12 assert tc3 == '00:00:00:11' ``` -Creating a Timecode instance with a start timecode of '00:00:00:00' will -result a timecode object where the total number of frames is 1. So: +Creating a Timecode instance with a start timecode of '00:00:00:00' will result a +timecode object where the total number of frames is 1. So: ```py tc4 = Timecode('24', '00:00:00:00') @@ -40,10 +40,10 @@ Use the `frame_number` attribute if you want to get a 0 based frame number: assert tc4.frame_number == 0 ``` -Frame rates 29.97 and 59.94 are always drop frame, and all the others are non -drop frame. +Frame rates 29.97 and 59.94 are always drop frame, and all the others are non drop +frame. -The timecode library supports rational frame rates passed as a string: +The timecode library supports fractional frame rates passed as a string: ```py tc5 = Timecode('30000/1001', '00:00:00;00') @@ -60,8 +60,8 @@ assert repr(tc6) == '19:23:14:23' This is useful for parsing timecodes stored in OpenEXR's and extracted through OpenImageIO for instance. -Timecode also supports passing start timecodes formatted like HH:MM:SS.sss where -SS.sss is seconds and fractions of seconds: +Timecode also supports passing start timecodes formatted like HH:MM:SS.sss where SS.sss +is seconds and fractions of seconds: ```py tc8 = Timecode(25, '00:00:00.040') @@ -80,12 +80,10 @@ assert repr(tc9) == '19:23:14.958' Fraction of seconds is useful when working with tools like FFmpeg. +The SMPTE standard limits the timecode with 24 hours. Even though, Timecode instance +will show the current timecode inline with the SMPTE standard, it will keep counting the +total frames without clipping it. -The SMPTE standard limits the timecode with 24 hours. Even though, Timecode -instance will show the current timecode inline with the SMPTE standard, it will -keep counting the total frames without clipping it. - -Please report any bugs to the [GitHub](https://github.com/eoyilmaz/timecode) -page. +Please report any bugs to the [GitHub](https://github.com/eoyilmaz/timecode) page. Copyright 2014 Joshua Banton and PyTimeCode developers. diff --git a/tests/test_timecode.py b/tests/test_timecode.py index 749fc87..97689d3 100644 --- a/tests/test_timecode.py +++ b/tests/test_timecode.py @@ -6,6 +6,7 @@ @pytest.mark.parametrize( "args,kwargs", [ + [["12", "00:00:00:00"], {}], [["23.976", "00:00:00:00"], {}], [["23.98", "00:00:00:00"], {}], [["24", "00:00:00:00"], {}], @@ -49,6 +50,7 @@ [[(30000, 1001), "00:00:00;00"], {}], [[(60000, 1000), "00:00:00:00"], {}], [[(60000, 1001), "00:00:00;00"], {}], + [[12], {"frames": 12000}], [[24], {"frames": 12000}], [[23.976, "00:00:00:00"], {}], [[23.98, "00:00:00:00"], {}], @@ -416,7 +418,9 @@ def test_add_with_non_suitable_class_instance(args, kwargs, func, tc2): with pytest.raises(TimecodeError) as cm: _ = func(tc1, tc2) - assert "Type {} not supported for arithmetic.".format(tc2.__class__.__name__) == str(cm.value) + assert str(cm.value) == "Type {} not supported for arithmetic.".format( + tc2.__class__.__name__ + ) def test_div_method_working_properly_1(): diff --git a/timecode/__init__.py b/timecode/__init__.py index 9a77dda..6d76594 100644 --- a/timecode/__init__.py +++ b/timecode/__init__.py @@ -22,7 +22,7 @@ # THE SOFTWARE. __name__ = "timecode" -__version__ = "1.3.1" +__version__ = "1.3.2" __description__ = "SMPTE Time Code Manipulation Library" __author__ = "Erkan Ozgur Yilmaz" __author_email__ = "eoyilmaz@gmail.com" @@ -32,37 +32,40 @@ class Timecode(object): """The main timecode class. - Does all the calculation over frames, so the main data it holds is - frames, then when required it converts the frames to a timecode by - using the frame rate setting. - - :param framerate: The frame rate of the Timecode instance. It - should be one of ['23.976', '23.98', '24', '25', '29.97', '30', '50', - '59.94', '60', 'NUMERATOR/DENOMINATOR', ms'] where "ms" equals to - 1000 fps. - Can not be skipped. - Setting the framerate will automatically set the :attr:`.drop_frame` - attribute to correct value. - :param start_timecode: The start timecode. Use this to be able to - set the timecode of this Timecode instance. It can be skipped and - then the frames attribute will define the timecode, and if it is also - skipped then the start_second attribute will define the start - timecode, and if start_seconds is also skipped then the default value - of '00:00:00:00' will be used. - When using 'ms' frame rate, timecodes like '00:11:01.040' use '.040' - as frame number. When used with other frame rates, '.040' represents - a fraction of a second. So '00:00:00.040'@25fps is 1 frame. - :type framerate: str or int or float or tuple - :type start_timecode: str or None - :param start_seconds: A float or integer value showing the seconds. - :param int frames: Timecode objects can be initialized with an - integer number showing the total frames. - :param force_non_drop_frame: If True, uses Non-Dropframe calculation for - 29.97 or 59.94 only. Has no meaning for any other framerate. + Does all the calculation over frames, so the main data it holds is frames, then when + required it converts the frames to a timecode by using the frame rate setting. + + Args: + framerate (Union[str, int, float, Fraction]): The frame rate of the Timecode + instance. If a str is given it should be one of ['23.976', '23.98', '24', + '25', '29.97', '30', '50', '59.94', '60', 'NUMERATOR/DENOMINATOR', ms'] + where "ms" equals to 1000 fps. Otherwise, any integer or Fractional value is + accepted. Can not be skipped. Setting the framerate will automatically set + the :attr:`.drop_frame` attribute to correct value. + start_timecode (Union[None, str]): The start timecode. Use this to be able to + set the timecode of this Timecode instance. It can be skipped and then the + frames attribute will define the timecode, and if it is also skipped then + the start_second attribute will define the start timecode, and if + start_seconds is also skipped then the default value of '00:00:00:00' will + be used. When using 'ms' frame rate, timecodes like '00:11:01.040' use + '.040' as frame number. When used with other frame rates, '.040' represents + a fraction of a second. So '00:00:00.040' at 25fps is 1 frame. + start_seconds (Union[int, float]): A float or integer value showing the seconds. + frames (int): Timecode objects can be initialized with an integer number showing + the total frames. + force_non_drop_frame (bool): If True, uses Non-Dropframe calculation for 29.97 + or 59.94 only. Has no meaning for any other framerate. It is False by + default. """ - def __init__(self, framerate, start_timecode=None, start_seconds=None, - frames=None, force_non_drop_frame=False): + def __init__( + self, + framerate, + start_timecode=None, + start_seconds=None, + frames=None, + force_non_drop_frame=False, + ): self.force_non_drop_frame = force_non_drop_frame @@ -89,27 +92,31 @@ def __init__(self, framerate, start_timecode=None, start_seconds=None, self.frames = self.float_to_tc(start_seconds) else: # use default value of 00:00:00:00 - self.frames = self.tc_to_frames('00:00:00:00') + self.frames = self.tc_to_frames("00:00:00:00") @property def frames(self): - """getter for the _frames attribute + """Return the _frames attribute value. + + Returns: + int: The frames attribute value. """ return self._frames @frames.setter def frames(self, frames): - """setter for the _frames attribute + """Set the_frames attribute. - :param int frames: A positive int bigger than zero showing the number - of frames that this TimeCode represents. - :return: + Args: + frames (int): A positive int bigger than zero showing the number of frames + that this Timecode represents. """ # validate the frames value if not isinstance(frames, int): raise TypeError( "%s.frames should be a positive integer bigger " - "than zero, not a %s" % (self.__class__.__name__, frames.__class__.__name__) + "than zero, not a %s" + % (self.__class__.__name__, frames.__class__.__name__) ) if frames <= 0: @@ -121,23 +128,38 @@ def frames(self, frames): @property def framerate(self): - """getter for _framerate attribute + """Return the _framerate attribute. + + Returns: + str: The frame rate of this Timecode instance. """ return self._framerate @framerate.setter - def framerate(self, framerate): # lint:ok - """setter for the framerate attribute - :param framerate: - :return: + def framerate(self, framerate): + """Set the framerate attribute. + + Args: + framerate (Union[int, float, str, tuple, Fraction]): Several different type + is accepted for this argument: + + int, float: It is directly used. + str: Is used for setting DF Timecodes and possible values are + ["23.976", "23.98", "29.97", "59.94", "ms", "1000", "frames"] where + "ms" and "1000" results in to a milliseconds based Timecode and + "frames" will result a Timecode with 1 FPS. + tuple: The tuple should be in (nominator, denominator) format in which + the frame rate is kept as a fraction. + Fraction: If the current version of Python supports (which it should) + then Fraction is also accepted. """ # Convert rational frame rate to float numerator = None denominator = None try: - if '/' in framerate: - numerator, denominator = framerate.split('/') + if "/" in framerate: + numerator, denominator = framerate.split("/") except TypeError: # not a string pass @@ -147,6 +169,7 @@ def framerate(self, framerate): # lint:ok try: from fractions import Fraction + if isinstance(framerate, Fraction): numerator = framerate.numerator denominator = framerate.denominator @@ -163,25 +186,19 @@ def framerate(self, framerate): # lint:ok framerate = str(framerate) # set the int_frame_rate - if framerate == '29.97': + if framerate == "29.97": self._int_framerate = 30 - if self.force_non_drop_frame is True: - self.drop_frame = False - else: - self.drop_frame = True - elif framerate == '59.94': + self.drop_frame = not self.force_non_drop_frame + elif framerate == "59.94": self._int_framerate = 60 - if self.force_non_drop_frame is True: - self.drop_frame = False - else: - self.drop_frame = True - elif any(map(lambda x: framerate.startswith(x), ['23.976', '23.98'])): + self.drop_frame = not self.force_non_drop_frame + elif any(map(lambda x: framerate.startswith(x), ["23.976", "23.98"])): self._int_framerate = 24 - elif framerate in ['ms', '1000']: + elif framerate in ["ms", "1000"]: self._int_framerate = 1000 self.ms_frame = True framerate = 1000 - elif framerate == 'frames': + elif framerate == "frames": self._int_framerate = 1 else: self._int_framerate = int(float(framerate)) @@ -189,23 +206,45 @@ def framerate(self, framerate): # lint:ok self._framerate = framerate def set_fractional(self, state): - """Set or unset timecode to be represented with fractional seconds - :param bool state: + """Set if the Timecode is to be represented with fractional seconds. + + Args: + state (bool): If set to True the current Timecode instance will be + represented with a fractional seconds (will have a "." in the frame + separator). """ self.fraction_frame = state def set_timecode(self, timecode): - """Sets the frames by using the given timecode + """Set the frames by using the given timecode. + + Args: + timecode (Union[str, Timecode]): Either a str representation of a Timecode + or a Timecode instance. """ self.frames = self.tc_to_frames(timecode) def float_to_tc(self, seconds): - """set the frames by using the given seconds + """Return the number of frames in the given seconds using the current instance. + + Args: + seconds (float): The seconds to set hte timecode to. This uses the integer + frame rate for proper calculation. + + Returns: + int: The number of frames in the given seconds.ß """ return int(seconds * self._int_framerate) def tc_to_frames(self, timecode): - """Converts the given timecode to frames + """Convert the given Timecode to frames. + + Args: + timecode (Union[str, Timecode]): Either a str representing a Timecode or a + Timecode instance. + + Returns: + int: The number of frames in the given Timecode. """ # timecode could be a Timecode instance if isinstance(timecode, Timecode): @@ -215,12 +254,12 @@ def tc_to_frames(self, timecode): if isinstance(timecode, int): time_tokens = [hours, minutes, seconds, frames] - timecode = ':'.join(str(t) for t in time_tokens) + timecode = ":".join(str(t) for t in time_tokens) if self.drop_frame: - timecode = ';'.join(timecode.rsplit(':', 1)) + timecode = ";".join(timecode.rsplit(":", 1)) - if self.framerate != 'frames': + if self.framerate != "frames": ffps = float(self.framerate) else: ffps = float(self._int_framerate) @@ -228,7 +267,7 @@ def tc_to_frames(self, timecode): if self.drop_frame: # Number of drop frames is 6% of framerate rounded to nearest # integer - drop_frames = int(round(ffps * .066666)) + drop_frames = int(round(ffps * 0.066666)) else: drop_frames = 0 @@ -246,29 +285,35 @@ def tc_to_frames(self, timecode): total_minutes = (60 * hours) + minutes # Handle case where frames are fractions of a second - if len(timecode.split('.')) == 2 and not self.ms_frame: + if len(timecode.split(".")) == 2 and not self.ms_frame: self.fraction_frame = True - fraction = timecode.rsplit('.', 1)[1] + fraction = timecode.rsplit(".", 1)[1] - frames = int(round(float('.' + fraction) * ffps)) + frames = int(round(float("." + fraction) * ffps)) - frame_number = \ - ((hour_frames * hours) + (minute_frames * minutes) + - (ifps * seconds) + frames) - \ - (drop_frames * (total_minutes - (total_minutes // 10))) + frame_number = ( + (hour_frames * hours) + + (minute_frames * minutes) + + (ifps * seconds) + + frames + ) - (drop_frames * (total_minutes - (total_minutes // 10))) return frame_number + 1 # frames def frames_to_tc(self, frames): - """Converts frames back to timecode + """Convert frames back to timecode. + + Args: + frames (int): Number of frames. - :returns str: the string representation of the current time code + Returns: + str: The string representation of the current Timecode instance. """ if self.drop_frame: # Number of frames to drop on the minute marks is the nearest # integer to 6% of the framerate ffps = float(self.framerate) - drop_frames = int(round(ffps * .066666)) + drop_frames = int(round(ffps * 0.066666)) else: ffps = float(self._int_framerate) drop_frames = 0 @@ -293,7 +338,9 @@ def frames_to_tc(self, frames): d = frame_number // frames_per_10_minutes m = frame_number % frames_per_10_minutes if m > drop_frames: - frame_number += (drop_frames * 9 * d) + drop_frames * ((m - drop_frames) // frames_per_minute) + frame_number += (drop_frames * 9 * d) + drop_frames * ( + (m - drop_frames) // frames_per_minute + ) else: frame_number += drop_frames * 9 * d @@ -310,32 +357,59 @@ def frames_to_tc(self, frames): return hrs, mins, secs, frs def tc_to_string(self, hrs, mins, secs, frs): + """Return the string representation of a Timecode with given info. + + Args: + hrs (int): The hours portion of the Timecode. + mins (int): The minutes portion of the Timecode. + secs (int): The seconds portion of the Timecode. + frs (int): The frames portion of the Timecode. + + Returns: + str: The string representation of this Timecode.ßß + """ if self.fraction_frame: - return "{hh:02d}:{mm:02d}:{ss:06.3f}".format( - hh=hrs, mm=mins, ss=secs + frs - ) + return "{hh:02d}:{mm:02d}:{ss:06.3f}".format(hh=hrs, mm=mins, ss=secs + frs) - ff = "%02d" + ff = "{:02d}" if self.ms_frame: - ff = "%03d" + ff = "{:03d}" - return ("%02d:%02d:%02d%s" + ff) % ( + return ("{:02d}:{:02d}:{:02d}{}" + ff).format( hrs, mins, secs, self.frame_delimiter, frs ) @classmethod def parse_timecode(cls, timecode): - """parses timecode string NDF '00:00:00:00' or DF '00:00:00;00' or - milliseconds/fractionofseconds '00:00:00.000' + """Parse the given timecode string. + + This uses the frame separator do decide if this is a NDF, DF or a + or milliseconds/fraction_of_seconds based Timecode. + + '00:00:00:00' will result a NDF Timecode where, '00:00:00;00' will result a DF + Timecode or '00:00:00.000' will be a milliseconds/fraction_of_seconds based + Timecode. + + Args: + timecode (Union[int, str]): If an integer is given it is converted to hex + and the hours, minutes, seconds and frames are extracted from the hex + representation. If a str is given it should follow one of the SMPTE + timecode formats.ß + + Returns: + (int, int, int, int): A tuple containing the hours, minutes, seconds and + frames part of the Timecode. """ if isinstance(timecode, int): hex_repr = hex(timecode) # fix short string - hex_repr = '0x%s' % (hex_repr[2:].zfill(8)) - hrs, mins, secs, frs = tuple(map(int, [hex_repr[i:i + 2] for i in range(2, 10, 2)])) + hex_repr = "0x%s" % (hex_repr[2:].zfill(8)) + hrs, mins, secs, frs = tuple( + map(int, [hex_repr[i : i + 2] for i in range(2, 10, 2)]) + ) else: - bfr = timecode.replace(';', ':').replace('.', ':').split(':') + bfr = timecode.replace(";", ":").replace(".", ":").split(":") hrs = int(bfr[0]) mins = int(bfr[1]) secs = int(bfr[2]) @@ -345,48 +419,95 @@ def parse_timecode(cls, timecode): @property def frame_delimiter(self): - """Return correct symbol based on framerate.""" + """Return correct frame deliminator symbol based on the framerate. + + Returns: + str: The frame deliminator, ";" if this is a drop frame timecode, "." if + this is a millisecond based Timecode or ":" in any other case. + """ if self.drop_frame: - return ';' + return ";" elif self.ms_frame or self.fraction_frame: - return '.' + return "." else: - return ':' + return ":" def __iter__(self): + """Yield and iterator. + + Yields: + Timecode: Yields this Timecode instance. + """ yield self def next(self): + """Add one frame to this Timecode to go the next frame. + + Returns: + Timecode: Returns self. So, this is the same Timecode instance with this + one. + """ self.add_frames(1) return self def back(self): + """Subtract one frame from this Timecode to go back one frame. + + Returns: + Timecode: Returns self. So, this is the same Timecode instance with this + one. + """ self.sub_frames(1) return self def add_frames(self, frames): - """adds or subtracts frames number of frames + """Add or subtract frames from the number of frames of this Timecode. + + Args: + frames (int): The number to subtract from or add to the number of frames of + this Timecode instance. """ self.frames += frames def sub_frames(self, frames): - """adds or subtracts frames number of frames + """Add or subtract frames from the number of frames of this Timecode. + + Args: + frames (int): The number to subtract from or add to the number of frames of + this Timecode instance. """ self.add_frames(-frames) def mult_frames(self, frames): - """multiply frames + """Multiply frames. + + Args: + frames (int): Multiply the frames with this number. """ self.frames *= frames def div_frames(self, frames): - """adds or subtracts frames number of frames""" + """Divide the number of frames to the given number. + + Args: + frames (int): The other number to divide the number of frames of this + Timecode instance to. + """ self.frames = int(self.frames / frames) def __eq__(self, other): - """the overridden equality operator + """Override the equality operator. + + Args: + other (Union[int, str, Timecode]): Either and int representing the number of + frames, a str representing the start time of a Timecode with the same + frame rate of this one, or a Timecode to compare with the number of + frames. + + Returns: + bool: True if the other is equal to this Timecode instance. """ if isinstance(other, Timecode): return self.framerate == other.framerate and self.frames == other.frames @@ -397,7 +518,17 @@ def __eq__(self, other): return self.frames == other def __ge__(self, other): - """override greater or equal to operator""" + """Override greater than or equal to operator. + + Args: + other (Union[int, str, Timecode]): Either and int representing the number of + frames, a str representing the start time of a Timecode with the same + frame rate of this one, or a Timecode to compare with the number of + frames. + + Returns: + bool: True if the other is greater than or equal to this Timecode instance. + """ if isinstance(other, Timecode): return self.framerate == other.framerate and self.frames >= other.frames elif isinstance(other, str): @@ -407,7 +538,17 @@ def __ge__(self, other): return self.frames >= other def __gt__(self, other): - """override greater operator""" + """Override greater than operator. + + Args: + other (Union[int, str, Timecode]): Either and int representing the number of + frames, a str representing the start time of a Timecode with the same + frame rate of this one, or a Timecode to compare with the number of + frames. + + Returns: + bool: True if the other is greater than this Timecode instance. + """ if isinstance(other, Timecode): return self.framerate == other.framerate and self.frames > other.frames elif isinstance(other, str): @@ -417,7 +558,17 @@ def __gt__(self, other): return self.frames > other def __le__(self, other): - """override less or equal to operator""" + """Override less or equal to operator. + + Args: + other (Union[int, str, Timecode]): Either and int representing the number of + frames, a str representing the start time of a Timecode with the same + frame rate of this one, or a Timecode to compare with the number of + frames. + + Returns: + bool: True if the other is less than or equal to this Timecode instance. + """ if isinstance(other, Timecode): return self.framerate == other.framerate and self.frames <= other.frames elif isinstance(other, str): @@ -427,7 +578,17 @@ def __le__(self, other): return self.frames <= other def __lt__(self, other): - """override less operator""" + """Override less than operator. + + Args: + other (Union[int, str, Timecode]): Either and int representing the number of + frames, a str representing the start time of a Timecode with the same + frame rate of this one, or a Timecode to compare with the number of + frames. + + Returns: + bool: True if the other is less than this Timecode instance. + """ if isinstance(other, Timecode): return self.framerate == other.framerate and self.frames < other.frames elif isinstance(other, str): @@ -437,8 +598,17 @@ def __lt__(self, other): return self.frames < other def __add__(self, other): - """returns new Timecode instance with the given timecode or frames - added to this one + """Return a new Timecode with the given timecode or frames added to this one. + + Args: + other (Union[int, Timecode]): Either and int value or a Timecode in which + the frames are used for the calculation. + + Raises: + TimecodeError: If the other is not an int or Timecode. + + Returns: + Timecode: The resultant Timecode instance. """ # duplicate current one tc = Timecode(self.framerate, frames=self.frames) @@ -449,95 +619,161 @@ def __add__(self, other): tc.add_frames(other) else: raise TimecodeError( - 'Type %s not supported for arithmetic.' % - other.__class__.__name__ + "Type {} not supported for arithmetic.".format(other.__class__.__name__) ) return tc def __sub__(self, other): - """returns new Timecode instance with subtracted value""" + """Return a new Timecode instance with subtracted value. + + Args: + other (Union[int, Timecode]): The number to subtract, either an integer or + another Timecode in which the number of frames is subtracted. + + Raises: + TimecodeError: If the other is not an int or Timecode. + + Returns: + Timecode: The resultant Timecode instance. + """ if isinstance(other, Timecode): subtracted_frames = self.frames - other.frames elif isinstance(other, int): subtracted_frames = self.frames - other else: raise TimecodeError( - 'Type %s not supported for arithmetic.' % - other.__class__.__name__ + "Type {} not supported for arithmetic.".format(other.__class__.__name__) ) return Timecode(self.framerate, frames=abs(subtracted_frames)) def __mul__(self, other): - """returns new Timecode instance with multiplied value""" + """Return a new Timecode instance with multiplied value. + + Args: + other (Union[int, Timecode]): The multiplier either an integer or another + Timecode in which the number of frames is used as the multiplier. + + Raises: + TimecodeError: If the other is not an int or Timecode. + + Returns: + Timecode: The resultant Timecode instance. + """ if isinstance(other, Timecode): multiplied_frames = self.frames * other.frames elif isinstance(other, int): multiplied_frames = self.frames * other else: raise TimecodeError( - 'Type %s not supported for arithmetic.' % - other.__class__.__name__ + "Type {} not supported for arithmetic.".format(other.__class__.__name__) ) return Timecode(self.framerate, frames=multiplied_frames) def __div__(self, other): - """returns new Timecode instance with divided value""" + """Return a new Timecode instance with divided value. + + Args: + other (Union[int, Timecode]): The denominator either an integer or another + Timecode in which the number of frames is used as the denominator. + + Raises: + TimecodeError: If the other is not an int or Timecode. + + Returns: + Timecode: The resultant Timecode instance. + """ if isinstance(other, Timecode): div_frames = int(float(self.frames) / float(other.frames)) elif isinstance(other, int): div_frames = int(float(self.frames) / float(other)) else: raise TimecodeError( - 'Type %s not supported for arithmetic.' % - other.__class__.__name__ + "Type {} not supported for arithmetic.".format(other.__class__.__name__) ) return Timecode(self.framerate, frames=div_frames) def __truediv__(self, other): - """returns new Timecode instance with divided value""" + """Return a new Timecode instance with divided value. + + Args: + other (Union[int, Timecode]): The denominator either an integer or another + Timecode in which the number of frames is used as the denominator. + + Returns: + Timecode: The resultant Timecode instance. + """ return self.__div__(other) def __repr__(self): + """Return the string representation of this Timecode instance. + + Returns: + str: The string representation of this Timecode instance. + """ return self.tc_to_string(*self.frames_to_tc(self.frames)) @property def hrs(self): + """Return the hours part of the timecode. + + Returns: + int: The hours part of the timecode. + """ hrs, mins, secs, frs = self.frames_to_tc(self.frames) return hrs @property def mins(self): + """Return the minutes part of the timecode. + + Returns: + int: The minutes part of the timecode. + """ hrs, mins, secs, frs = self.frames_to_tc(self.frames) return mins @property def secs(self): + """Return the seconds part of the timecode. + + Returns: + int: The seconds part of the timecode. + """ hrs, mins, secs, frs = self.frames_to_tc(self.frames) return secs @property def frs(self): + """Return the frames part of the timecode. + + Returns: + int: The frames part of the timecode. + """ hrs, mins, secs, frs = self.frames_to_tc(self.frames) return frs @property def frame_number(self): - """returns the 0 based frame number of the current timecode instance + """Return the 0-based frame number of the current timecode instance. + + Returns: + int: 0-based frame number. """ return self.frames - 1 @property def float(self): - """returns the seconds as float + """Return the seconds as float. + + Returns: + float: The seconds as float. """ return float(self.frames) / float(self._int_framerate) class TimecodeError(Exception): - """Raised when an error occurred in timecode calculation - """ - pass + """Raised when an error occurred in timecode calculation."""