@@ -38,14 +38,22 @@ class StreamMetadata:
3838 stream_index : int
3939 """Index of the stream that this metadata refers to (int)."""
4040
41+ # Computed fields (computed in C++ with fallback logic)
42+ duration_seconds : Optional [float ]
43+ """Duration of the stream in seconds. Computed in C++ with fallback logic:
44+ tries to calculate from content if scan was performed, otherwise falls back
45+ to header values."""
46+ begin_stream_seconds : Optional [float ]
47+ """Beginning of the stream, in seconds. Computed in C++ with fallback logic."""
48+
4149 def __repr__ (self ):
4250 s = self .__class__ .__name__ + ":\n "
4351 for field in dataclasses .fields (self ):
4452 s += f"{ SPACES } { field .name } : { getattr (self , field .name )} \n "
4553 return s
4654
4755
48- @dataclass
56+ @dataclass ( repr = False )
4957class VideoStreamMetadata (StreamMetadata ):
5058 """Metadata of a single video stream."""
5159
@@ -87,103 +95,19 @@ class VideoStreamMetadata(StreamMetadata):
8795 is the ratio between the width and height of each pixel
8896 (``fractions.Fraction`` or None)."""
8997
90- @property
91- def duration_seconds (self ) -> Optional [float ]:
92- """Duration of the stream in seconds. We try to calculate the duration
93- from the actual frames if a :term:`scan` was performed. Otherwise we
94- fall back to ``duration_seconds_from_header``. If that value is also None,
95- we instead calculate the duration from ``num_frames_from_header`` and
96- ``average_fps_from_header``.
97- """
98- if (
99- self .end_stream_seconds_from_content is not None
100- and self .begin_stream_seconds_from_content is not None
101- ):
102- return (
103- self .end_stream_seconds_from_content
104- - self .begin_stream_seconds_from_content
105- )
106- elif self .duration_seconds_from_header is not None :
107- return self .duration_seconds_from_header
108- elif (
109- self .num_frames_from_header is not None
110- and self .average_fps_from_header is not None
111- ):
112- return self .num_frames_from_header / self .average_fps_from_header
113- else :
114- return None
115-
116- @property
117- def begin_stream_seconds (self ) -> float :
118- """Beginning of the stream, in seconds (float). Conceptually, this
119- corresponds to the first frame's :term:`pts`. If
120- ``begin_stream_seconds_from_content`` is not None, then it is returned.
121- Otherwise, this value is 0.
122- """
123- if self .begin_stream_seconds_from_content is None :
124- return 0
125- else :
126- return self .begin_stream_seconds_from_content
127-
128- @property
129- def end_stream_seconds (self ) -> Optional [float ]:
130- """End of the stream, in seconds (float or None).
131- Conceptually, this corresponds to last_frame.pts + last_frame.duration.
132- If ``end_stream_seconds_from_content`` is not None, then that value is
133- returned. Otherwise, returns ``duration_seconds``.
134- """
135- if self .end_stream_seconds_from_content is None :
136- return self .duration_seconds
137- else :
138- return self .end_stream_seconds_from_content
139-
140- @property
141- def num_frames (self ) -> Optional [int ]:
142- """Number of frames in the stream (int or None).
143- This corresponds to ``num_frames_from_content`` if a :term:`scan` was made,
144- otherwise it corresponds to ``num_frames_from_header``. If that value is also
145- None, the number of frames is calculated from the duration and the average fps.
146- """
147- if self .num_frames_from_content is not None :
148- return self .num_frames_from_content
149- elif self .num_frames_from_header is not None :
150- return self .num_frames_from_header
151- elif (
152- self .average_fps_from_header is not None
153- and self .duration_seconds_from_header is not None
154- ):
155- return int (self .average_fps_from_header * self .duration_seconds_from_header )
156- else :
157- return None
158-
159- @property
160- def average_fps (self ) -> Optional [float ]:
161- """Average fps of the stream. If a :term:`scan` was perfomed, this is
162- computed from the number of frames and the duration of the stream.
163- Otherwise we fall back to ``average_fps_from_header``.
164- """
165- if (
166- self .end_stream_seconds_from_content is None
167- or self .begin_stream_seconds_from_content is None
168- or self .num_frames is None
169- # Should never happen, but prevents ZeroDivisionError:
170- or self .end_stream_seconds_from_content
171- == self .begin_stream_seconds_from_content
172- ):
173- return self .average_fps_from_header
174- return self .num_frames / (
175- self .end_stream_seconds_from_content
176- - self .begin_stream_seconds_from_content
177- )
178-
179- def __repr__ (self ):
180- s = super ().__repr__ ()
181- s += f"{ SPACES } duration_seconds: { self .duration_seconds } \n "
182- s += f"{ SPACES } begin_stream_seconds: { self .begin_stream_seconds } \n "
183- s += f"{ SPACES } end_stream_seconds: { self .end_stream_seconds } \n "
184- s += f"{ SPACES } num_frames: { self .num_frames } \n "
185- s += f"{ SPACES } average_fps: { self .average_fps } \n "
186- return s
98+ # Computed fields (computed in C++ with fallback logic)
99+ end_stream_seconds : Optional [float ]
100+ """End of the stream, in seconds (float or None).
101+ Conceptually, this corresponds to last_frame.pts + last_frame.duration.
102+ Computed in C++ with fallback logic."""
103+ num_frames : Optional [int ]
104+ """Number of frames in the stream (int or None).
105+ Computed in C++ with fallback logic: uses content if scan was performed,
106+ otherwise falls back to header values or calculates from duration and fps."""
107+ average_fps : Optional [float ]
108+ """Average fps of the stream (float or None).
109+ Computed in C++ with fallback logic: if scan was performed, computes from
110+ num_frames and duration, otherwise uses header value."""
187111
188112
189113@dataclass
@@ -260,10 +184,12 @@ def get_container_metadata(decoder: torch.Tensor) -> ContainerMetadata:
260184 stream_dict = json .loads (_get_stream_json_metadata (decoder , stream_index ))
261185 common_meta = dict (
262186 duration_seconds_from_header = stream_dict .get ("durationSecondsFromHeader" ),
187+ duration_seconds = stream_dict .get ("durationSeconds" ),
263188 bit_rate = stream_dict .get ("bitRate" ),
264189 begin_stream_seconds_from_header = stream_dict .get (
265190 "beginStreamSecondsFromHeader"
266191 ),
192+ begin_stream_seconds = stream_dict .get ("beginStreamSeconds" ),
267193 codec = stream_dict .get ("codec" ),
268194 stream_index = stream_index ,
269195 )
@@ -276,6 +202,9 @@ def get_container_metadata(decoder: torch.Tensor) -> ContainerMetadata:
276202 end_stream_seconds_from_content = stream_dict .get (
277203 "endStreamSecondsFromContent"
278204 ),
205+ end_stream_seconds = stream_dict .get ("endStreamSeconds" ),
206+ num_frames = stream_dict .get ("numFrames" ),
207+ average_fps = stream_dict .get ("averageFps" ),
279208 width = stream_dict .get ("width" ),
280209 height = stream_dict .get ("height" ),
281210 num_frames_from_header = stream_dict .get ("numFramesFromHeader" ),
0 commit comments