diff --git a/docs/index.rst b/docs/index.rst index f6a77f8..a8e075f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 -------------------------------- diff --git a/pymediainfo/__init__.py b/pymediainfo/__init__.py index f95b9d5..694424d 100644 --- a/pymediainfo/__init__.py +++ b/pymediainfo/__init__.py @@ -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 @@ -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. @@ -38,7 +38,7 @@ class Track: >>> 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) @@ -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 @@ -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: diff --git a/tests/data/empty.gif b/tests/data/empty.gif new file mode 100644 index 0000000..3c51d74 Binary files /dev/null and b/tests/data/empty.gif differ diff --git a/tests/data/other_track.xml b/tests/data/other_track.xml new file mode 100644 index 0000000..96fff6c --- /dev/null +++ b/tests/data/other_track.xml @@ -0,0 +1,35 @@ + + + + + test.mxf + + + 2 + MPEG Video + Version 2 + + + 3 + PCM + + + 1-Material + Time code + MXF TC + 25.000 FPS + 00:00:00:00 + Material Package + Yes + + + 1-Source + Time code + MXF TC + 25.000 FPS + 00:00:00:00 + Source Package + Yes + + + diff --git a/tests/data/sample.mkv b/tests/data/sample.mkv index 71c8ac9..dbfe036 100644 Binary files a/tests/data/sample.mkv and b/tests/data/sample.mkv differ diff --git a/tests/test_pymediainfo.py b/tests/test_pymediainfo.py index 5cd2736..4678e54 100644 --- a/tests/test_pymediainfo.py +++ b/tests/test_pymediainfo.py @@ -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")