Skip to content

Commit

Permalink
Add shortcuts to tracks (audio_tracks, video_tracks…), fixes #95
Browse files Browse the repository at this point in the history
Also add the following to improve tests:
* Chapters to sample.mkv.
* An image test file.
* A MediaInfo XML output for a file containing tracks of type "Other".
  • Loading branch information
sbraz committed Nov 19, 2020
1 parent 821273f commit 12ad13e
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 7 deletions.
13 changes: 9 additions & 4 deletions docs/index.rst
Expand Up @@ -40,15 +40,20 @@ Getting information from an image
from pymediainfo import MediaInfo
media_info = MediaInfo.parse("/home/user/image.jpg")
for track in media_info.tracks:
if track.track_type == "Image":
print(f"{track.format} of {track.width}×{track.height} pixels.")
# Tracks can be accessed via the 'tracks' attribute or through shortcuts
# such as 'image_tracks', 'audio_tracks', 'video_tracks', etc.
general_track = media_info.general_tracks[0]
image_track = media_info.image_tracks[0]
print(
f"{image_track.format} of {image_track.width}×{image_track.height} pixels"
f" and {general_track.file_size} bytes."
)
Will return something like:

.. code-block:: none
JPEG of 828×828 pixels.
JPEG of 828×828 pixels and 19098 bytes.
Getting information from a video
--------------------------------
Expand Down
67 changes: 64 additions & 3 deletions pymediainfo/__init__.py
Expand Up @@ -10,7 +10,7 @@
import sys
import warnings
import xml.etree.ElementTree as ET
from typing import Any, Dict, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Tuple, Union

from pkg_resources import DistributionNotFound, get_distribution

Expand All @@ -25,7 +25,7 @@ class Track:
An object associated with a media file track.
Each :class:`Track` attribute corresponds to attributes parsed from MediaInfo's output.
All attributes are lower case. Attributes that are present several times such as Duration
All attributes are lower case. Attributes that are present several times such as `Duration`
yield a second attribute starting with `other_` which is a list of all alternative
attribute values.
Expand All @@ -38,7 +38,7 @@ class Track:
<Track track_id='None', track_type='General'>
>>> t.duration
3000
>>> t.to_data()["other_duration"]
>>> t.other_duration
['3 s 0 ms', '3 s 0 ms', '3 s 0 ms',
'00:00:03.000', '00:00:03.000']
>>> type(t.non_existing)
Expand Down Expand Up @@ -166,6 +166,65 @@ def __init__(self, xml: str, encoding_errors: str = "strict"):
for xml_track in xml_dom.iterfind(xpath):
self.tracks.append(Track(xml_track))

def _tracks(self, track_type: str) -> List[Track]:
return [track for track in self.tracks if track.track_type == track_type]

@property
def general_tracks(self) -> List[Track]:
"""
:return: All :class:`Track`\\s of type ``General``.
:rtype: list of :class:`Track`\\s
"""
return self._tracks("General")

@property
def video_tracks(self) -> List[Track]:
"""
:return: All :class:`Track`\\s of type ``Video``.
:rtype: list of :class:`Track`\\s
"""
return self._tracks("Video")

@property
def audio_tracks(self) -> List[Track]:
"""
:return: All :class:`Track`\\s of type ``Audio``.
:rtype: list of :class:`Track`\\s
"""
return self._tracks("Audio")

@property
def text_tracks(self) -> List[Track]:
"""
:return: All :class:`Track`\\s of type ``Text``.
:rtype: list of :class:`Track`\\s
"""
return self._tracks("Text")

@property
def other_tracks(self) -> List[Track]:
"""
:return: All :class:`Track`\\s of type ``Other``.
:rtype: list of :class:`Track`\\s
"""
return self._tracks("Other")

@property
def image_tracks(self) -> List[Track]:
"""
:return: All :class:`Track`\\s of type ``Image``.
:rtype: list of :class:`Track`\\s
"""
return self._tracks("Image")

@property
def menu_tracks(self) -> List[Track]:
"""
:return: All :class:`Track`\\s of type ``Menu``.
:rtype: list of :class:`Track`\\s
"""
return self._tracks("Menu")

@staticmethod
def _normalize_filename(filename: Any) -> Any:
# TODO: wait for https://github.com/python/typeshed/pull/4582 pylint: disable=fixme
Expand Down Expand Up @@ -264,6 +323,8 @@ def can_parse(cls, library_file: Optional[str] = None) -> bool:
"""
Checks whether media files can be analyzed using libmediainfo.
:param str library_file: path to the libmediainfo library, this should only be used if
the library cannot be auto-detected.
:rtype: bool
"""
try:
Expand Down
Binary file added tests/data/empty.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions tests/data/other_track.xml
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<Mediainfo version="20.09">
<File>
<track type="General">
<Complete_name>test.mxf</Complete_name>
</track>
<track type="Video">
<ID>2</ID>
<Format>MPEG Video</Format>
<Format_version>Version 2</Format_version>
</track>
<track type="Audio">
<ID>3</ID>
<Format>PCM</Format>
</track>
<track type="Other" typeorder="1">
<ID>1-Material</ID>
<Type>Time code</Type>
<Format>MXF TC</Format>
<Frame_rate>25.000 FPS</Frame_rate>
<Time_code_of_first_frame>00:00:00:00</Time_code_of_first_frame>
<Time_code_settings>Material Package</Time_code_settings>
<Time_code__striped>Yes</Time_code__striped>
</track>
<track type="Other" typeorder="2">
<ID>1-Source</ID>
<Type>Time code</Type>
<Format>MXF TC</Format>
<Frame_rate>25.000 FPS</Frame_rate>
<Time_code_of_first_frame>00:00:00:00</Time_code_of_first_frame>
<Time_code_settings>Source Package</Time_code_settings>
<Time_code__striped>Yes</Time_code__striped>
</track>
</File>
</Mediainfo>
Binary file modified tests/data/sample.mkv
Binary file not shown.
40 changes: 40 additions & 0 deletions tests/test_pymediainfo.py
Expand Up @@ -368,3 +368,43 @@ def test_parameter_output(self):
os.path.join(data_dir, "sample.mp4"), output="General;%FileSize%"
)
self.assertEqual(media_info, "404567")


class MediaInfoTrackShortcutsTests(unittest.TestCase):
def setUp(self):
self.mi_audio = MediaInfo.parse(os.path.join(data_dir, "sample.mp4"))
self.mi_text = MediaInfo.parse(os.path.join(data_dir, "sample.mkv"))
self.mi_image = MediaInfo.parse(os.path.join(data_dir, "empty.gif"))
with open(os.path.join(data_dir, "other_track.xml")) as f:
self.mi_other = MediaInfo(f.read())

def test_empty_list(self):
self.assertEqual(self.mi_audio.text_tracks, [])

def test_general_tracks(self):
self.assertEqual(len(self.mi_audio.general_tracks), 1)
self.assertIsNotNone(self.mi_audio.general_tracks[0].file_name)

def test_video_tracks(self):
self.assertEqual(len(self.mi_audio.video_tracks), 1)
self.assertIsNotNone(self.mi_audio.video_tracks[0].display_aspect_ratio)

def test_audio_tracks(self):
self.assertEqual(len(self.mi_audio.audio_tracks), 1)
self.assertIsNotNone(self.mi_audio.audio_tracks[0].sampling_rate)

def test_text_tracks(self):
self.assertEqual(len(self.mi_text.text_tracks), 1)
self.assertEqual(self.mi_text.text_tracks[0].kind_of_stream, "Text")

def test_other_tracks(self):
self.assertEqual(len(self.mi_other.other_tracks), 2)
self.assertEqual(self.mi_other.other_tracks[0].type, "Time code")

def test_image_tracks(self):
self.assertEqual(len(self.mi_image.image_tracks), 1)
self.assertEqual(self.mi_image.image_tracks[0].width, 1)

def test_menu_tracks(self):
self.assertEqual(len(self.mi_text.menu_tracks), 1)
self.assertEqual(self.mi_text.menu_tracks[0].kind_of_stream, "Menu")

0 comments on commit 12ad13e

Please sign in to comment.