Last active
January 4, 2024 18:43
-
-
Save manneohrstrom/8033e178cd38589b0226b45cef1dfe30 to your computer and use it in GitHub Desktop.
Python method for converting from frames to SMPTE time code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello
Thanks but I don't get the expected results
I get the following on the 1st minute-mark
and same at every minute-mark
10 minute mark is as expected
Test code is as below, using Python 3.10
Thanks