GithubHelp home page GithubHelp logo

eoyilmaz / timecode Goto Github PK

View Code? Open in Web Editor NEW

This project forked from bantonj/pytimecode

147.0 18.0 35.0 177 KB

Python Module for SMPTE Time Code Manipulation

License: MIT License

Python 99.79% Batchfile 0.21%

timecode's Introduction

About

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).

The math behind the drop frame calculation is based on the blog post of David Heidelberger.

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:

from timecode import Timecode

tc1 = Timecode('29.97', '00:00:00;00')
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:

tc4 = Timecode('24', '00:00:00:00')
assert tc4.frames == 1

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.

The timecode library supports fractional frame rates passed as a string:

tc5 = Timecode('30000/1001', '00:00:00;00')
assert tc5.framerate == '29.97'

You may also pass a big "Binary Coded Decimal" integer as start timecode:

tc6 = Timecode('24', 421729315)
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:

tc8 = Timecode(25, '00:00:00.040')
assert tc8.frame_number == 1

You may set any timecode to be represented as fractions of seconds:

tc9 = Timecode(24, '19:23:14:23')
assert repr(tc9) == '19:23:14:23'

tc9.set_fractional(True)
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.

Please report any bugs to the GitHub page.

Copyright 2014 Joshua Banton and PyTimeCode developers.

timecode's People

Contributors

apetrynet avatar bantonj avatar cubicibo avatar eoyilmaz avatar foleyj2 avatar jeff-vincent avatar jonata avatar kingb avatar mike1158 avatar rafrafek avatar simonh10 avatar theodeus avatar thomasheritage avatar thomasparsley avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

timecode's Issues

Different timecodes occurring for same number of frames.

1.>>> tc = Timecode('25', '01:00:01:20')
2.>>> tc
01:00:01:20
3.>>> tc.frames
90046
4.>>> Timecode(25,frames=90446)
01:00:17:20

-> Why is different value of timecodes occurring when using 1 vs 4 while both have same no. of frames??

Python module seems not to convert SMPTE timecode to seconds.milliseconds format correctly

When using module to convert a SMPTE timecode 00:00:00:00 to seconds.milliseconds format, the behavior is incorrect e.g the result is 0.04 and not 0.00 as expected (which is corresponding to the real playhead position on a timeline) :

979e48e0-f552-4e88-a1eb-86ae13d77762

When using the module in a broadcast production environment, where the frame accuracy is mandatory, it smell like a real problem!

Buggy Timecode calculation when invoked with `frames`

When creating a Timecode object as such:

framerate = Fraction(24000, 1001) #23.976023976023978
frame_idx = 50000

Timecode(framerate, frames=frame_idx)

gives the output 00:36:13:20 which is wrong. I've manually verified this with the input video and also checked the result with 3 other Timecode calculators:

  1. Omnicalculator
  2. Zapstudio
  3. PySceneDetect's FrameTimecode module

All of these above output 00:34:45:09 (except PySceneDetect which outputs in milliseconds, but still accurate), which is the correct output

Read a timecode source

Hello,

I've looked around for a while to read the timecode data coming in off of an audio source, but there are no libraries capable of doing this it seems. Is there an example available somewhere/ is this something that is being looked into being added?

Thank you.

IndexError

Hi,

When trying to execute the following line of code:

subtitle["start"] = Timecode(self.framerate, str(datetime.timedelta(seconds=float(r["start_time"]))))

I got the following error:

Traceback (most recent call last):
  File "json_to_srt.py", line 333, in <module>
    JsonToSrt.create_subtitles()
  File "json_to_srt.py", line 106, in create_subtitles
    subtitle["start"] = Timecode(self.framerate, str(datetime.timedelta(seconds=float(r["start_time"]))))
  File "C:\Users\r.godement\AppData\Local\Programs\Python\Python38\lib\site-packages\timecode\__init__.py", line 77, in __init__
    self.frames = self.tc_to_frames(start_timecode)
  File "C:\Users\r.godement\AppData\Local\Programs\Python\Python38\lib\site-packages\timecode\__init__.py", line 209, in tc_to_frames
    hours, minutes, seconds, frames = map(int, self.parse_timecode(timecode))
  File "C:\Users\r.godement\AppData\Local\Programs\Python\Python38\lib\site-packages\timecode\__init__.py", line 337, in parse_timecode
    frs = int(bfr[3])
IndexError: list index out of range

Initializing with start_seconds does not create the correct Timecode object

When initializing a Timecode instance with the start_seconds field the object does not have an equivalent representation

framerate = "30000/1001"
seconds = 500
instance = Timecode(framerate, start_seconds=seconds)

This yields a representation where instance.float = 500.5005005005005 and instance.frames = 15000.

From using both omnicalculator and zapstudio the correct number of frames should be 14985

If using the corresponding timecode representation of 500 seconds it works

instance_timecode = Timecode(framerate, "00:08:20;00")
# instance_timecode.frames = 14985, instance_timecode.float = 500.0 

Please let me know if I'm misusing the module and if there's anymore info I can give. I am using version 1.2.4.

Unexpected TC change on instantiation.

I don't understand why this returns an altered timecode :

from timecode import Timecode
tc = Timecode("23.97", "00:00:01:23")
tc

>>> 00:00:02:00

Why is it rounding up to 2 seconds ?

29.97 Non-Drop

What's the supported way of creating an object that's 29.97 Non-Drop? Or 30 Drop? The __init__ seems to automatically infer drop-frame state from the frame rate.

I could do this:

tc1 = Timecode('29.97','01:03:18:00')
tc1.drop_frame = False

Just something to keep in mind. Most feature films in the US, if they're using 29.97 code on set-- which isn't too uncommon on the sound side-- it's going to be non-drop.

Float property inaccurate?

I could be wrong but I think the float property is not returning the correct value:

return float(self.frames) / float(self._int_framerate)

I'm working with 23.976 timecode and this property seems to be giving me incorrect results, I had to change it to:
return float(self.frame_number) / (float(24000) / 1001)

So frame_number instead of frames to use the 0-based number rather than 1-based. 24000/1001 is not really necessary in most cases, but at large timecode values you may lose some time when dividing by 23.976

Why is the frame_number for tc1 + tc2 = 1 and not 0 in this example?

Why is the frame_number for tc1 + tc2 = 1 and not 0 in this example?

from timecode import Timecode

tc1 = Timecode('25', '00:00:00:00')
print(tc1.frame_number)
print(tc1.frames)

tc2 = Timecode('25', '00:00:00:00')
print(tc2.frame_number)
print(tc2.frames)

tc3 = tc1 + tc2
print(tc3.frame_number). // Result are 1 frame and not 0
print(tc3.frames)
print(tc3)

[pip install timecode] UnicodeDecodeError: 'cp950' codec can't decode byte 0xc3 in position 9097: illegal multibyte sequence

I try pip install timecode, but I get encoding errors.

(.venv) D:\.....\.venv\Scripts>pip install timecode
Collecting timecode
  Using cached timecode-1.4.0.tar.gz (43 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  error: subprocess-exited-with-error

  × Getting requirements to build wheel did not run successfully.
  │ exit code: 1
  ╰─> [18 lines of output]
      Traceback (most recent call last):
        File "d:\.....\.venv\lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 353, in <module>
          main()
        File "d:\.....\.venv\lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
        File "d:\.....\.venv\lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 118, in get_requires_for_build_wheel
          return hook(config_settings)
        File "D:\TEMP\pip-build-env-m_q5666t\overlay\Lib\site-packages\setuptools\build_meta.py", line 325, in get_requires_for_build_wheel
          return self._get_build_requires(config_settings, requirements=['wheel'])
        File "D:\TEMP\pip-build-env-m_q5666t\overlay\Lib\site-packages\setuptools\build_meta.py", line 295, in _get_build_requires
          self.run_setup()
        File "D:\TEMP\pip-build-env-m_q5666t\overlay\Lib\site-packages\setuptools\build_meta.py", line 487, in run_setup
          super().run_setup(setup_script=setup_script)
        File "D:\TEMP\pip-build-env-m_q5666t\overlay\Lib\site-packages\setuptools\build_meta.py", line 311, in run_setup
          exec(code, locals())
        File "<string>", line 25, in <module>
        File "<string>", line 18, in read_file
      UnicodeDecodeError: 'cp950' codec can't decode byte 0xc3 in position 9097: illegal multibyte sequence
      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error

× Getting requirements to build wheel did not run successfully.
│ exit code: 1
╰─> See above for output.

note: This error originates from a subprocess, and is likely not a problem with pip.

I tried to modify the installation program to make it run normally, but I failed. I am not very good at python, sorry.

Error when used on Windows

Hi

Receiving the following error on windows when trying to use this (works fine on mac)

Traceback (most recent call last):
File "", line 1, in
File "C:\Program Files (x86)\Python36-32\lib\site-packages\timecode_init_.py", line 64, in init
self.framerate = framerate
File "C:\Program Files (x86)\Python36-32\lib\site-packages\timecode_init_.py", line 97, in framerate
if isinstance(framerate, basestring) and '/' in framerate:
NameError: name 'basestring' is not defined

Very simple example script that is being used in this instance

from timecode import Timecode

inPoint = Timecode('25', '00:00:10:00')
startTimecode = Timecode('25', '01:00:00:00')
print(startTimecode - inPoint)

Why do frames have to be larger than 0?

Hey there!

In the frames.setter, the code raises a ValueError if the number of frames frames <= 0:

However, I think for specific operations, for e.g. subtractions, even negative frame numbers might be useful as result.

In a somewhat similar manner, the __init__ code also throws a ValueError if start_seconds == 0. Here, I think it might be useful to simply let 0 seconds pass and use the default value self.frames = self.tc_to_frames('00:00:00:00')

I can, of course, deal with both issues before passing values to Timecode (or in my own fork).

But, is there a particular reason why the use-case scenarios are restricted to frames <= 0?

Thanks for keeping this repo alive!
Cheers!

start_seconds issue

print(Timecode('29.97', start_seconds=0))

yields

23:59:59;29

Should it yield

00:00:00:00?

Tests are failing

Test are failing for compairing two timecodes.

$ python test_timecode.py

F.........E..E..............
======================================================================
ERROR: test_gt_overload (__main__.TimecodeTester)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_timecode.py", line 1034, in test_gt_overload
    self.assertFalse(tc1 < tc2)
TypeError: '<' not supported between instances of 'Timecode' and 'Timecode'

======================================================================
ERROR: test_le_overload (__main__.TimecodeTester)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_timecode.py", line 1025, in test_le_overload
    self.assertTrue(tc5 > tc4)
TypeError: '>' not supported between instances of 'Timecode' and 'Timecode'

======================================================================
FAIL: test_2398_vs_23976 (__main__.TimecodeTester)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_timecode.py", line 103, in test_2398_vs_23976
    self.assertEqual(timeobj1.frames, timeobj2.frames)
AssertionError: 348144 != 333639

----------------------------------------------------------------------
Ran 28 tests in 0.007s

FAILED (failures=1, errors=2)

float doesn't use frame_number

As example:

seconds = 39.472766666666665
tc = Timecode(('30000', '1001'),seconds )
print(tc.float)
returns 39.50617283950618

if I change:

def float(self):
    """returns the seconds as float
    """
    return (self.frames - 1)  / float(self.framerate)

I get 39.47280613947281.

So the float function doesn't calculate the frame_number.

Incorrect comparison

I think there might be an error on the comparison code for sub-fractional second timecodes.
These are timecodes I was given from ffmpeg.

`#!/usr/bin/python
from timecode import Timecode

tc3 = Timecode(25, '00:20:25.92')
tc5 = Timecode(25, '00:20:25.72')
if tc3 > tc5:
print "tc3 is bigger than tc5"
if tc3 < tc5:
print "tc5 is bigger than tc3"`

prints out that tc5 is bigger on my machine.

Type Hints!

I love type hints and how they help dev tools (using mypy or pyright to catch errors for me!) I know that type hints are loved by everybody - so totally fair if you want to close this issue.

Now that python 2 is end of life, I think it would be great to add type hints to this package and update it so it's python 3.7+.

Currently I'm trying to use this in a project that we run mypy on, but mypy complains when you try to use dependencies that don't have type hints!

If you're on board with the idea, I'm happy to do the work and put in a PR. (I'm already doing the work in a stubs file for this on my project to appease mypy anyway)

ValueError exception

Timecode('24', 72931) is raising ValueError exception.

>>> Timecode('24', 72931)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\Python\pyenv-win\pyenv-win\versions\3.9.5\lib\site-packages\timecode\__init__.py", line 77, in __init__
    self.frames = self.tc_to_frames(start_timecode)
  File "D:\Python\pyenv-win\pyenv-win\versions\3.9.5\lib\site-packages\timecode\__init__.py", line 209, in tc_to_frames
    hours, minutes, seconds, frames = map(int, self.parse_timecode(timecode))
  File "D:\Python\pyenv-win\pyenv-win\versions\3.9.5\lib\site-packages\timecode\__init__.py", line 330, in parse_timecode
    hrs, mins, secs, frs = tuple(map(int, [hex_repr[i:i + 2] for i in range(2, 10, 2)]))
ValueError: invalid literal for int() with base 10: '1c'

I am on Windows and I am using python 3.9.5. I have the latest timecode package 1.3.1.

Timecode calculation

Timecode(30,frames=0) gives 23:59:59:29 as the resultant timecode value.
Can you please explain why this is being calculated instead of 00:00:00:00 as the result??
(I am a newbie in timecode calculations, so please let me know in case any timecode calculations are wrong)

Comparison operators throw an Error?

This is probably a real noob question but I cant get the comparison operators to work

        from timecode import Timecode

        tcI = Timecode('25', '03:12:49:02')
        tcChunkStart = Timecode('25', '03:12:51:04')
        if tcI < tcChunkStart:
            #do something

when run gets error on the if statement:
TypeError: '<' not supported between instances of 'Timecode' and 'Timecode'.

Seems weird as arithmetric functions work fine ie.

    tcTest=tcI + tcOut

Any help gratefully appreciated

Fails if a low binary coded decimal is passed to it

If a too low binary coded decimal is provided, the Timecode class will fail. I believe it's when it's under one hour:

import timecode
# This line will fail. Should output: 00:41:17:00
timecode.Timecode(24, 16663)

The reason it fails is because the created hex string is too short. This classmethod call returns wrong values:

>> timecode.Timecode.parse_timecode(16663)
('41', '17', '', '')`
>> # While the correct value for this operation should be:
('0', '41', '17', '0')

I couldn't find a clever solution for the bug so I'm just using a dirty if check for now and shift the values and making sure that any variable at least contain '0' if it's empty.

Incorrect frame_number for 29.97 NDF

import timecode
tt = timecode.Timecode('29.97')
tt.drop_frame=False
tt.set_timecode("00:10:00:00")
print(tt.frame_number)

>>> 18000

... Should this not be 17,982?

10 x 60 x 29.97 = 17,982

UPDATE:
In my example, it appears that this library is using the dropframe flag to trigger how the frames are being calculated. This is not correct. Dropframe is really just a 'mask' on top of how the timecodes are determined. This does make 'reversing out' frames<->timecodes tricky, but something here isn't quite right. The fact that I set the class as non-drop, THEN added the timecode should yield the proper frame count.

Why does self.frames = int(seconds * self._int_framerate) ?

Hi Erkan,
Thank you so much for maintaining this convenient timecode package on pip! I'm a Robotics PhD student at Columbia university. My current project requires syncing multiple GoPros using their timecode with a global clock.
After my discucsion with GoPro Lab's maintainer, and my experiments, I believe that the conversion of Timecode from and to float seconds is incorrect.
Let's take 29.97Hz video for example, each frame takes 1/29.97 seconds. Therefore, to convert seconds into frames, we should:

self.frames = int(seconds * float(self.framerate))

Same goes to converting number of frames into seconds:

float(self.frames) / float(self.framerate)

The symptom of this error is that the 29.97Hz drop frame timecode is lexically faster than 30Hz timecode, while in reality, the drop frame operations should make the two timecodes roughly align:

t = 5000
tc = Timecode(framerate='29.97', start_seconds=t)
print(tc, ' 29.97Hz')
# 01:23:24;29  29.97Hz

tc = Timecode(framerate='29.97', frames=round(t*29.97))
print(tc, ' 29.97Hz')
# 01:23:19;29  29.97Hz

tc = Timecode(framerate='30', frames=round(t*30))
print(tc, ' 30Hz')
# 01:23:19:29  30Hz

Failed unittest

Hi @eoyilmaz

I would like to use version 0.4.0 because of the support for semicolon syntax but I noticed some unit tests did not pass. See example below.

>python -m unittest discover tests/
...........F.......
======================================================================
FAIL: test_op_overloads_mult (test_timecode.TimecodeTester)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tip/volatile/greg/python/timecode-0.4.0/tests/test_timecode.py", line 723, in test_op_overloads_mult
    self.assertEqual("04:09:35:23", d.__str__())
AssertionError: '04:09:35:23' != '01:59:59:23'

----------------------------------------------------------------------
Ran 19 tests in 0.006s

FAILED (failures=1)

Construction using seconds is not accurate

The folloing code

    from timecode import Timecode
    seconds = 0
    for _ in range(12):
        seconds += 0.04
        print(Timecode(framerate=25, start_seconds=seconds).frames)

prints

1
2
3
4
5
6
7
8
9
10
10
11

This is because of the float_to_tc method:

    def float_to_tc(self, seconds):
        """set the frames by using the given seconds
        """
        return int(seconds * self._int_framerate)

If the float is not exactly represented, the result may not be accurate.
In the example :
int(0.43999999999999995*25)=int(10.999999999999998)=10

It would be safer to round the result before casting to integer:

    def float_to_tc(self, seconds):
        """set the frames by using the given seconds
        """
        return int(round(seconds * self._int_framerate))

Discarding '23.98' framerate in @framerate.setter?

Is there a reason that, on line 129, the framerate setter sets framerate and _init_framerate to 24, instead of keeping the original framerate?

elif framerate in ['23.976', '23.98']: framerate = '24' self._int_framerate = 24

While I appreciate that the frames_to_tc and tc_to_frames results are the same regardless, there are lots of circumstances where distinguishing between true 24 and pulldown timecodes is important.

Why does thsi library not support arbitrary FPS/Framerate?

I am impressed with the library, it appears to be a very thorough effort.

But I cant understand why you limited it to a few framerates tough. There is not even support for 12 fps.

I am a traditional animator. And I am looking for a means to calculate potential animations for planning reasons as well as for variety of other reasons, such as matching 2D animations against live footage/3D etc. This often means I am working with 12 or 24.

When I say arbitrary framerates, This site, Frames to Timecode Calculator, lets you input any framerate and frame count to get a Timecode.

Perhaps this is to do the with Drop-Frame format, if so then can we not just get support for NDF Timecodes to have arbitrary framerates?

Frame validation breaks timecode manipulation

Hi,

I've got a long standing application which uses the timecode library to manipulate timecode offsets. Essentially we do

a = Timecode('25', timecode1);
b = Timecode('25', timecode2);
offset = a - b;
c = Timecode('25', timecode3) + offset;

This usage breaks with 1.2.3 when 'a' and 'b' are identical - the validation which insists frames has to be more than 0 causes a type error on the offset computation.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.