From 1eb80e450345964fa1a1fb3bcc1616bd5eb8b3b0 Mon Sep 17 00:00:00 2001 From: Bastiaan Terhorst Date: Thu, 12 Jun 2025 11:11:46 +0200 Subject: [PATCH 1/5] add support for fetching loudness levels of audio streams --- plexapi/media.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plexapi/media.py b/plexapi/media.py index c1530959..8cb158e1 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -433,10 +433,34 @@ def setSelected(self): """ return self._parent().setSelectedAudioStream(self) + def levels(self, subSample=128): + """ Load time series loudness levels. + + Attributes: + subSample: (int): the number of loudness segments to load + """ + + key = f'/library/streams/{self.id}/levels' + params = {'subsample': subSample} + + levels = self.fetchItems(ekey=key, cls=Level, params=params) + return levels + @deprecated('Use "setSelected" instead.') def setDefault(self): return self.setSelected() +@utils.registerPlexObject +class Level(PlexObject): + """ Represents an instance of loudness for an audio stream. + + Attributes: + loudness (float): loudness level in db + """ + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self.loudness = data.attrib.get('v') + @utils.registerPlexObject class SubtitleStream(MediaPartStream): From 2e9fdff071e816cd5ba6feed9e8fefa9aab5cef1 Mon Sep 17 00:00:00 2001 From: Bastiaan Terhorst Date: Thu, 12 Jun 2025 17:05:46 +0200 Subject: [PATCH 2/5] Add test for levels --- tests/test_audio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_audio.py b/tests/test_audio.py index b73f2ba1..a89bb1b8 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -397,6 +397,7 @@ def test_audio_Track_attrs(album): assert stream.lra is None assert stream.peak is None assert stream.startRamp is None + assert len(stream.levels(subSample=32)) == 32 def test_audio_Track_album(album): From d09e0ca4540040b9bbd8dd20d588f7e608623d61 Mon Sep 17 00:00:00 2001 From: Bastiaan Terhorst Date: Mon, 30 Jun 2025 09:07:37 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> --- plexapi/media.py | 17 +++++++---------- tests/test_audio.py | 3 ++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/plexapi/media.py b/plexapi/media.py index 8cb158e1..7ce0c9f9 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -434,17 +434,15 @@ def setSelected(self): return self._parent().setSelectedAudioStream(self) def levels(self, subSample=128): - """ Load time series loudness levels. + """ Returns a list of :class:`~plexapi.media.Level` objects for this AudioStream. + Only available for Tracks which have been analyzed for loudness. Attributes: - subSample: (int): the number of loudness segments to load + subSample (int): The number of loudness samples to return. Default 128. """ - key = f'/library/streams/{self.id}/levels' params = {'subsample': subSample} - - levels = self.fetchItems(ekey=key, cls=Level, params=params) - return levels + return self.fetchItems(key, params=params) @deprecated('Use "setSelected" instead.') def setDefault(self): @@ -452,15 +450,14 @@ def setDefault(self): @utils.registerPlexObject class Level(PlexObject): - """ Represents an instance of loudness for an audio stream. + """ Represents a single loudness Level sample for an AudioStream. Attributes: - loudness (float): loudness level in db + loudness (float): Loudness level value """ def _loadData(self, data): """ Load attribute values from Plex XML response. """ - self.loudness = data.attrib.get('v') - + self.loudness = utils.cast(float, data.attrib.get('v')) @utils.registerPlexObject class SubtitleStream(MediaPartStream): diff --git a/tests/test_audio.py b/tests/test_audio.py index a89bb1b8..43f941d3 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -397,7 +397,8 @@ def test_audio_Track_attrs(album): assert stream.lra is None assert stream.peak is None assert stream.startRamp is None - assert len(stream.levels(subSample=32)) == 32 + if stream.loudness is not None: + assert len(stream.levels(subSample=32)) == 32 def test_audio_Track_album(album): From c5213374117dad2f5adc752dcfb3b039601cdd98 Mon Sep 17 00:00:00 2001 From: Bastiaan Terhorst Date: Mon, 30 Jun 2025 15:48:16 +0200 Subject: [PATCH 4/5] move Level object to bottom of media.py --- plexapi/media.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/plexapi/media.py b/plexapi/media.py index 7ce0c9f9..895c14eb 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -447,17 +447,7 @@ def levels(self, subSample=128): @deprecated('Use "setSelected" instead.') def setDefault(self): return self.setSelected() - -@utils.registerPlexObject -class Level(PlexObject): - """ Represents a single loudness Level sample for an AudioStream. - - Attributes: - loudness (float): Loudness level value - """ - def _loadData(self, data): - """ Load attribute values from Plex XML response. """ - self.loudness = utils.cast(float, data.attrib.get('v')) + @utils.registerPlexObject class SubtitleStream(MediaPartStream): @@ -1364,3 +1354,15 @@ def _loadData(self, data): self.quality = data.attrib.get('quality') self.title = data.attrib.get('title') self.url = data.attrib.get('url') + + +@utils.registerPlexObject +class Level(PlexObject): + """ Represents a single loudness Level sample for an AudioStream. + + Attributes: + loudness (float): Loudness level value + """ + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self.loudness = utils.cast(float, data.attrib.get('v')) \ No newline at end of file From 7257ad3d5921e1c57c173aeb3a34f776b1466262 Mon Sep 17 00:00:00 2001 From: Bastiaan Terhorst Date: Tue, 1 Jul 2025 13:57:43 +0200 Subject: [PATCH 5/5] clean up whitespace issues --- plexapi/media.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plexapi/media.py b/plexapi/media.py index 895c14eb..718b5b7c 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -447,7 +447,7 @@ def levels(self, subSample=128): @deprecated('Use "setSelected" instead.') def setDefault(self): return self.setSelected() - + @utils.registerPlexObject class SubtitleStream(MediaPartStream): @@ -1363,6 +1363,6 @@ class Level(PlexObject): Attributes: loudness (float): Loudness level value """ - def _loadData(self, data): + def _loadData(self, data): """ Load attribute values from Plex XML response. """ - self.loudness = utils.cast(float, data.attrib.get('v')) \ No newline at end of file + self.loudness = utils.cast(float, data.attrib.get('v'))