- 
                Notifications
    You must be signed in to change notification settings 
- Fork 67
Calculate num_frames if missing from metadata #732
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Calculate num_frames if missing from metadata #732
Conversation
| """Number of frames in the stream. This corresponds to | ||
| ``num_frames_from_content`` if a :term:`scan` was made, otherwise it | ||
| corresponds to ``num_frames_from_header``. | ||
| """ | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the doc string, we should also say what we do if num_frames is missing from all of the metadata.
| elif ( | ||
| self.average_fps_from_header is not None | ||
| and self.duration_seconds_from_header is not None | ||
| ): | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just went down an interesting rabbit-hole of asking "Should we instead use the average_fps property?" And the answer is no! :) If self.average_fps_from_header is None, then self.average_fps actually calls this property, self.num_frames and we'd (I think) hit infinite recursion. I do wonder if there is a way for us to be more systematic about this, where we could more centrally define the logic of under what circumstances we use which metadata.
        
          
                test/test_decoders.py
              
                Outdated
          
        
      | all_frames = decoder.get_frames_played_in_range( | ||
| decoder.metadata.begin_stream_seconds, decoder.metadata.end_stream_seconds | ||
| ) | ||
| assert_frames_equal(all_frames.data, decoder[:]) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also add a simple assert len(decoder) == CORRECT_VALUE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had initially approved, but it looks like the tests are failing on Mac - let's make sure we're using the right tolerances there.
| Thank you! Also, this is only half of #727. We also want to be able to handle if  | 
8d60d07    to
    3075975      
    Compare
  
            
          
                test/test_decoders.py
              
                Outdated
          
        
      | ) | ||
| assert len(decoder) == 390 | ||
|  | ||
| # Test get_frames_in_range | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a comment here that we're really just testing the logic in the Python VideoDecoder class when we do the decoding. We do checks that involve num_frames at the Python layer, and that logic will use the mocked metadata. But the C++ layer does not use that mocked metadata, and will instead use what it read from the video file itself.
We should also create an issue about adding a video file that has the number of frames missing in its metadata so we can actually test the C++ logic as well. This test would then use that video file, and not need any mocking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I'll remove some unnecessary python calls here, and add a comment indicating that the python logic is the only aspect being tested.
It looks like issue #710 covers this subject.
| Thanks, @Dan-Flores! I looked into the failures, and they're all known (#740, #734, #724). | 
| I've updated this PR to complete the other half of #727, and updated the description as well. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR, and great work on the tests/mocking!
        
          
                test/test_metadata.py
              
                Outdated
          
        
      |  | ||
| @pytest.mark.parametrize( | ||
| "duration_seconds_from_header, begin_stream_seconds_from_content, end_stream_seconds_from_content, expected_duration_seconds", | ||
| [(60, 5, 20, 15), (60, 1, None, 60), (None, 0, 10, 10)], | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit for completeness, I think we can also test the case when only begin_stream_seconds_from_content is None?
| [(60, 5, 20, 15), (60, 1, None, 60), (None, 0, 10, 10)], | |
| [(60, 5, 20, 15), (60, 1, None, 60), (60, None, 1, 60), (None, 0, 10, 10)], | 
        
          
                test/test_metadata.py
              
                Outdated
          
        
      |  | ||
| @pytest.mark.parametrize( | ||
| "num_frames_from_header, average_fps_from_header, expected_duration_seconds", | ||
| [(100, 10, 10), (100, None, None), (None, None, None)], | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly here we may want to test when only num_frames_from_header is missing:
| [(100, 10, 10), (100, None, None), (None, None, None)], | |
| [(100, 10, 10), (100, None, None), (None, 10, None), (None, None, None)], | 
        
          
                src/torchcodec/_core/_metadata.py
              
                Outdated
          
        
      | def duration_seconds(self) -> Optional[float]: | ||
| """Duration of the stream in seconds. We try to calculate the duration | ||
| from the actual frames if a :term:`scan` was performed. Otherwise we | ||
| fall back to ``duration_seconds_from_header``. | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly to what was done for the num_frames properties, we can slightly update the docstring above to describe the new fall-back
63613c7    to
    e94fc04      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the new stuff looks good too, good to merge!
The new tests introduced in ffac96c (meta-pytorch#732) are failing since d6ce570 (meta-pytorch#737) because d6ce570 was merged by rebase.
This PR enables the instantiation of a VideoDecoder without certain metadata to resolve issue #727.
num_frames_from_contentornum_frames_from_headerin the metadata, the VideoDecoder will calculate the number of frames usingaverage_fps_from_headerandduration_seconds_from_header.duration_seconds_from_header, orend_stream_seconds_from_contentandbegin_stream_seconds_from_contentfrom a scan, the VideoDecoder will calculate the duration usingnum_frames_from_headerandaverage_fps_from_header.Tests in test_decoders.py
_get_stream_json_metadatais mocked to ensure VideoDecoder is instantiated withoutnum_frames_from_contentornum_frames_from_header.get_frames_in_rangeare tested.Tests in test_metadata.py
test_calculate_num_frames_using_fps_and_durationinstantiatesVideoStreamMetadatawithoutnum_frames_from_contentornum_frames_from_header, and checks that various values ofaverage_fpsandduration_secondsresult in the correct number of frames.In
test_num_frames_fallback, the test where bothnum_frames_from_contentandnum_frames_from_headerareNoneis no longer valid. A similar test case is added intest_calculate_num_frames_using_fps_and_duration.test_duration_seconds_fallbackvalidates that calculating metadata using a scan is prioritized over header metadata.test_calculate_duration_seconds_using_fps_and_num_framestests the fallback implemented in this PR.