Skip to content

Instantly share code, notes, and snippets.

@manneohrstrom
Last active January 4, 2024 18:43
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save manneohrstrom/8033e178cd38589b0226b45cef1dfe30 to your computer and use it in GitHub Desktop.
Save manneohrstrom/8033e178cd38589b0226b45cef1dfe30 to your computer and use it in GitHub Desktop.
Python method for converting from frames to SMPTE time code.
# Copyright (c) 2016 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See
# https://github.com/shotgunsoftware/tk-core/blob/master/LICENSE
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
def frames_to_timecode(total_frames, frame_rate, drop):
"""
Method that converts frames to SMPTE timecode.
:param total_frames: Number of frames
:param frame_rate: frames per second
:param drop: true if time code should drop frames, false if not
:returns: SMPTE timecode as string, e.g. '01:02:12:32' or '01:02:12;32'
"""
if drop and frame_rate not in [29.97, 59.94]:
raise NotImplementedError("Time code calculation logic only supports drop frame "
"calculations for 29.97 and 59.94 fps.")
# for a good discussion around time codes and sample code, see
# http://andrewduncan.net/timecodes/
# round fps to the nearest integer
# note that for frame rates such as 29.97 or 59.94,
# we treat them as 30 and 60 when converting to time code
# then, in some cases we 'compensate' by adding 'drop frames',
# e.g. jump in the time code at certain points to make sure that
# the time code calculations are roughly right.
#
# for a good explanation, see
# https://documentation.apple.com/en/finalcutpro/usermanual/index.html#chapter=D%26section=6
fps_int = int(round(frame_rate))
if drop:
# drop-frame-mode
# add two 'fake' frames every minute but not every 10 minutes
#
# example at the one minute mark:
#
# frame: 1795 non-drop: 00:00:59:25 drop: 00:00:59;25
# frame: 1796 non-drop: 00:00:59:26 drop: 00:00:59;26
# frame: 1797 non-drop: 00:00:59:27 drop: 00:00:59;27
# frame: 1798 non-drop: 00:00:59:28 drop: 00:00:59;28
# frame: 1799 non-drop: 00:00:59:29 drop: 00:00:59;29
# frame: 1800 non-drop: 00:01:00:00 drop: 00:01:00;02
# frame: 1801 non-drop: 00:01:00:01 drop: 00:01:00;03
# frame: 1802 non-drop: 00:01:00:02 drop: 00:01:00;04
# frame: 1803 non-drop: 00:01:00:03 drop: 00:01:00;05
# frame: 1804 non-drop: 00:01:00:04 drop: 00:01:00;06
# frame: 1805 non-drop: 00:01:00:05 drop: 00:01:00;07
#
# example at the ten minute mark:
#
# frame: 17977 non-drop: 00:09:59:07 drop: 00:09:59;25
# frame: 17978 non-drop: 00:09:59:08 drop: 00:09:59;26
# frame: 17979 non-drop: 00:09:59:09 drop: 00:09:59;27
# frame: 17980 non-drop: 00:09:59:10 drop: 00:09:59;28
# frame: 17981 non-drop: 00:09:59:11 drop: 00:09:59;29
# frame: 17982 non-drop: 00:09:59:12 drop: 00:10:00;00
# frame: 17983 non-drop: 00:09:59:13 drop: 00:10:00;01
# frame: 17984 non-drop: 00:09:59:14 drop: 00:10:00;02
# frame: 17985 non-drop: 00:09:59:15 drop: 00:10:00;03
# frame: 17986 non-drop: 00:09:59:16 drop: 00:10:00;04
# frame: 17987 non-drop: 00:09:59:17 drop: 00:10:00;05
# calculate number of drop frames for a 29.97 std NTSC
# workflow. Here there are 30*60 = 1800 frames in one
# minute
FRAMES_IN_ONE_MINUTE = 1800 - 2
FRAMES_IN_TEN_MINUTES = (FRAMES_IN_ONE_MINUTE * 10) - 2
ten_minute_chunks = total_frames / FRAMES_IN_TEN_MINUTES
one_minute_chunks = total_frames % FRAMES_IN_TEN_MINUTES
ten_minute_part = 18 * ten_minute_chunks
one_minute_part = 2 * ((one_minute_chunks - 2) / FRAMES_IN_ONE_MINUTE)
if one_minute_part < 0:
one_minute_part = 0
# add extra frames
total_frames += ten_minute_part + one_minute_part
# for 60 fps drop frame calculations, we add twice the number of frames
if fps_int == 60:
total_frames = total_frames * 2
# time codes are on the form 12:12:12;12
smpte_token = ";"
else:
# time codes are on the form 12:12:12:12
smpte_token = ":"
# now split our frames into time code
hours = int(total_frames / (3600 * fps_int))
minutes = int(total_frames / (60 * fps_int) % 60)
seconds = int(total_frames / fps_int % 60)
frames = int(total_frames % fps_int)
return "%02d:%02d:%02d%s%02d" % (hours, minutes, seconds, smpte_token, frames)
# usage example
print frames_to_timecode(123214, 24, False)
@deadsound
Copy link

deadsound commented Mar 18, 2019

Thanks! Works Gr8!
Change the last line to
print (frames_to_timecode (123214, 24, False))
for python3

@jeanmichel-nwsb
Copy link

Hello
Thanks but I don't get the expected results
I get the following on the 1st minute-mark

1794	00:00:59;27
1795	00:00:59;28
1796	00:00:59;29
1797	00:01:00;00
1798	00:01:00;01
1799	00:01:00;02
1800	00:01:00;03

and same at every minute-mark

10 minute mark is as expected

17979	00:09:59;27
17980	00:09:59;28
17981	00:09:59;29
17982	00:10:00;00
17983	00:10:00;01
17984	00:10:00;02

Test code is as below, using Python 3.10

with open("ntsc_tc.csv", "wt") as fntsc:
    fntsc.write("frame;timestamp;timecode\n")
    frame = 0
    while frame < 12*60*30:
        ts = 1001.0 * frame / 30000.0
        tc = frames_to_timecode(frame, 29.97, True)
        fntsc.write(f"{frame};{ts};\"{tc}\"\n")
        frame += 1

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment