Skip to content

Commit 31c8940

Browse files
committed
audio: Move tag helpers to mopidy.audio.tags
1 parent f0c7d25 commit 31c8940

9 files changed

Lines changed: 408 additions & 394 deletions

File tree

mopidy/audio/actor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import pykka
1313

1414
from mopidy import exceptions
15-
from mopidy.audio import utils
15+
from mopidy.audio import tags as tags_lib, utils
1616
from mopidy.audio.constants import PlaybackState
1717
from mopidy.audio.listener import AudioListener
1818
from mopidy.internal import deprecation, process
@@ -325,7 +325,7 @@ def on_async_done(self):
325325
gst_logger.debug('Got ASYNC_DONE bus message.')
326326

327327
def on_tag(self, taglist):
328-
tags = utils.convert_taglist(taglist)
328+
tags = tags_lib.convert_taglist(taglist)
329329
gst_logger.debug('Got TAG bus message: tags=%r', dict(tags))
330330
self._audio._tags.update(tags)
331331
logger.debug('Audio event: tags_changed(tags=%r)', tags.keys())

mopidy/audio/scan.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
Gst.is_initialized() or Gst.init()
1111

1212
from mopidy import exceptions
13-
from mopidy.audio import utils
13+
from mopidy.audio import tags as tags_lib, utils
1414
from mopidy.internal import encoding
1515

1616
# GST_ELEMENT_FACTORY_LIST:
@@ -214,7 +214,7 @@ def _process(pipeline, timeout_ms):
214214
elif message.type == Gst.MessageType.TAG:
215215
taglist = message.parse_tag()
216216
# Note that this will only keep the last tag.
217-
tags.update(utils.convert_taglist(taglist))
217+
tags.update(tags_lib.convert_taglist(taglist))
218218

219219
now = int(time.time() * 1000)
220220
timeout -= now - previous

mopidy/audio/tags.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from __future__ import absolute_import, unicode_literals
2+
3+
import collections
4+
import logging
5+
import numbers
6+
7+
import gi
8+
gi.require_version('Gst', '1.0')
9+
from gi.repository import Gst
10+
Gst.is_initialized() or Gst.init()
11+
12+
from mopidy import compat
13+
from mopidy.models import Album, Artist, Track
14+
15+
16+
logger = logging.getLogger(__name__)
17+
TRACE = logging.getLevelName('TRACE')
18+
19+
20+
def convert_taglist(taglist):
21+
"""Convert a :class:`Gst.TagList` to plain Python types.
22+
23+
Knows how to convert:
24+
25+
- Dates
26+
- Buffers
27+
- Numbers
28+
- Strings
29+
- Booleans
30+
31+
Unknown types will be ignored and trace logged. Tag keys are all strings
32+
defined as part GStreamer under GstTagList_.
33+
34+
.. _GstTagList: https://developer.gnome.org/gstreamer/stable/\
35+
gstreamer-GstTagList.html
36+
37+
:param taglist: A GStreamer taglist to be converted.
38+
:type taglist: :class:`Gst.TagList`
39+
:rtype: dictionary of tag keys with a list of values.
40+
"""
41+
result = collections.defaultdict(list)
42+
43+
for n in range(taglist.n_tags()):
44+
tag = taglist.nth_tag_name(n)
45+
46+
for i in range(taglist.get_tag_size(tag)):
47+
value = taglist.get_value_index(tag, i)
48+
49+
if isinstance(value, Gst.DateTime):
50+
result[tag].append(value.to_iso8601_string())
51+
if isinstance(value, (compat.string_types, bool, numbers.Number)):
52+
result[tag].append(value)
53+
else:
54+
logger.log(
55+
TRACE, 'Ignoring unknown tag data: %r = %r', tag, value)
56+
57+
return result
58+
59+
60+
# TODO: split based on "stream" and "track" based conversion? i.e. handle data
61+
# from radios in it's own helper instead?
62+
def convert_tags_to_track(tags):
63+
"""Convert our normalized tags to a track.
64+
65+
:param tags: dictionary of tag keys with a list of values
66+
:type tags: :class:`dict`
67+
:rtype: :class:`mopidy.models.Track`
68+
"""
69+
album_kwargs = {}
70+
track_kwargs = {}
71+
72+
track_kwargs['composers'] = _artists(tags, Gst.TAG_COMPOSER)
73+
track_kwargs['performers'] = _artists(tags, Gst.TAG_PERFORMER)
74+
track_kwargs['artists'] = _artists(tags, Gst.TAG_ARTIST,
75+
'musicbrainz-artistid',
76+
'musicbrainz-sortname')
77+
album_kwargs['artists'] = _artists(
78+
tags, Gst.TAG_ALBUM_ARTIST, 'musicbrainz-albumartistid')
79+
80+
track_kwargs['genre'] = '; '.join(tags.get(Gst.TAG_GENRE, []))
81+
track_kwargs['name'] = '; '.join(tags.get(Gst.TAG_TITLE, []))
82+
if not track_kwargs['name']:
83+
track_kwargs['name'] = '; '.join(tags.get(Gst.TAG_ORGANIZATION, []))
84+
85+
track_kwargs['comment'] = '; '.join(tags.get('comment', []))
86+
if not track_kwargs['comment']:
87+
track_kwargs['comment'] = '; '.join(tags.get(Gst.TAG_LOCATION, []))
88+
if not track_kwargs['comment']:
89+
track_kwargs['comment'] = '; '.join(tags.get(Gst.TAG_COPYRIGHT, []))
90+
91+
track_kwargs['track_no'] = tags.get(Gst.TAG_TRACK_NUMBER, [None])[0]
92+
track_kwargs['disc_no'] = tags.get(Gst.TAG_ALBUM_VOLUME_NUMBER, [None])[0]
93+
track_kwargs['bitrate'] = tags.get(Gst.TAG_BITRATE, [None])[0]
94+
track_kwargs['musicbrainz_id'] = tags.get('musicbrainz-trackid', [None])[0]
95+
96+
album_kwargs['name'] = tags.get(Gst.TAG_ALBUM, [None])[0]
97+
album_kwargs['num_tracks'] = tags.get(Gst.TAG_TRACK_COUNT, [None])[0]
98+
album_kwargs['num_discs'] = tags.get(Gst.TAG_ALBUM_VOLUME_COUNT, [None])[0]
99+
album_kwargs['musicbrainz_id'] = tags.get('musicbrainz-albumid', [None])[0]
100+
101+
if tags.get(Gst.TAG_DATE) and tags.get(Gst.TAG_DATE)[0]:
102+
track_kwargs['date'] = tags[Gst.TAG_DATE][0].isoformat()
103+
104+
# Clear out any empty values we found
105+
track_kwargs = {k: v for k, v in track_kwargs.items() if v}
106+
album_kwargs = {k: v for k, v in album_kwargs.items() if v}
107+
108+
# Only bother with album if we have a name to show.
109+
if album_kwargs.get('name'):
110+
track_kwargs['album'] = Album(**album_kwargs)
111+
112+
return Track(**track_kwargs)
113+
114+
115+
def _artists(
116+
tags, artist_name, artist_id=None, artist_sortname=None):
117+
118+
# Name missing, don't set artist
119+
if not tags.get(artist_name):
120+
return None
121+
# One artist name and either id or sortname, include all available fields
122+
if len(tags[artist_name]) == 1 and \
123+
(artist_id in tags or artist_sortname in tags):
124+
attrs = {'name': tags[artist_name][0]}
125+
if artist_id in tags:
126+
attrs['musicbrainz_id'] = tags[artist_id][0]
127+
if artist_sortname in tags:
128+
attrs['sortname'] = tags[artist_sortname][0]
129+
return [Artist(**attrs)]
130+
131+
# Multiple artist, provide artists with name only to avoid ambiguity.
132+
return [Artist(name=name) for name in tags[artist_name]]

mopidy/audio/utils.py

Lines changed: 1 addition & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
from __future__ import absolute_import, unicode_literals
22

3-
import collections
4-
import logging
5-
import numbers
6-
73
import gi
84
gi.require_version('Gst', '1.0')
95
from gi.repository import Gst
106

11-
from mopidy import compat, httpclient
12-
from mopidy.models import Album, Artist, Track
13-
14-
logger = logging.getLogger(__name__)
15-
TRACE = logging.getLevelName('TRACE')
7+
from mopidy import httpclient
168

179

1810
def calculate_duration(num_samples, sample_rate):
@@ -68,79 +60,6 @@ def supported_uri_schemes(uri_schemes):
6860
return supported_schemes
6961

7062

71-
def _artists(tags, artist_name, artist_id=None, artist_sortname=None):
72-
# Name missing, don't set artist
73-
if not tags.get(artist_name):
74-
return None
75-
# One artist name and either id or sortname, include all available fields
76-
if len(tags[artist_name]) == 1 and \
77-
(artist_id in tags or artist_sortname in tags):
78-
attrs = {'name': tags[artist_name][0]}
79-
if artist_id in tags:
80-
attrs['musicbrainz_id'] = tags[artist_id][0]
81-
if artist_sortname in tags:
82-
attrs['sortname'] = tags[artist_sortname][0]
83-
return [Artist(**attrs)]
84-
85-
# Multiple artist, provide artists with name only to avoid ambiguity.
86-
return [Artist(name=name) for name in tags[artist_name]]
87-
88-
89-
# TODO: split based on "stream" and "track" based conversion? i.e. handle data
90-
# from radios in it's own helper instead?
91-
def convert_tags_to_track(tags):
92-
"""Convert our normalized tags to a track.
93-
94-
:param tags: dictionary of tag keys with a list of values
95-
:type tags: :class:`dict`
96-
:rtype: :class:`mopidy.models.Track`
97-
"""
98-
album_kwargs = {}
99-
track_kwargs = {}
100-
101-
track_kwargs['composers'] = _artists(tags, Gst.TAG_COMPOSER)
102-
track_kwargs['performers'] = _artists(tags, Gst.TAG_PERFORMER)
103-
track_kwargs['artists'] = _artists(tags, Gst.TAG_ARTIST,
104-
'musicbrainz-artistid',
105-
'musicbrainz-sortname')
106-
album_kwargs['artists'] = _artists(
107-
tags, Gst.TAG_ALBUM_ARTIST, 'musicbrainz-albumartistid')
108-
109-
track_kwargs['genre'] = '; '.join(tags.get(Gst.TAG_GENRE, []))
110-
track_kwargs['name'] = '; '.join(tags.get(Gst.TAG_TITLE, []))
111-
if not track_kwargs['name']:
112-
track_kwargs['name'] = '; '.join(tags.get(Gst.TAG_ORGANIZATION, []))
113-
114-
track_kwargs['comment'] = '; '.join(tags.get('comment', []))
115-
if not track_kwargs['comment']:
116-
track_kwargs['comment'] = '; '.join(tags.get(Gst.TAG_LOCATION, []))
117-
if not track_kwargs['comment']:
118-
track_kwargs['comment'] = '; '.join(tags.get(Gst.TAG_COPYRIGHT, []))
119-
120-
track_kwargs['track_no'] = tags.get(Gst.TAG_TRACK_NUMBER, [None])[0]
121-
track_kwargs['disc_no'] = tags.get(Gst.TAG_ALBUM_VOLUME_NUMBER, [None])[0]
122-
track_kwargs['bitrate'] = tags.get(Gst.TAG_BITRATE, [None])[0]
123-
track_kwargs['musicbrainz_id'] = tags.get('musicbrainz-trackid', [None])[0]
124-
125-
album_kwargs['name'] = tags.get(Gst.TAG_ALBUM, [None])[0]
126-
album_kwargs['num_tracks'] = tags.get(Gst.TAG_TRACK_COUNT, [None])[0]
127-
album_kwargs['num_discs'] = tags.get(Gst.TAG_ALBUM_VOLUME_COUNT, [None])[0]
128-
album_kwargs['musicbrainz_id'] = tags.get('musicbrainz-albumid', [None])[0]
129-
130-
if tags.get(Gst.TAG_DATE) and tags.get(Gst.TAG_DATE)[0]:
131-
track_kwargs['date'] = tags[Gst.TAG_DATE][0].isoformat()
132-
133-
# Clear out any empty values we found
134-
track_kwargs = {k: v for k, v in track_kwargs.items() if v}
135-
album_kwargs = {k: v for k, v in album_kwargs.items() if v}
136-
137-
# Only bother with album if we have a name to show.
138-
if album_kwargs.get('name'):
139-
track_kwargs['album'] = Album(**album_kwargs)
140-
141-
return Track(**track_kwargs)
142-
143-
14463
def setup_proxy(element, config):
14564
"""Configure a GStreamer element with proxy settings.
14665
@@ -157,46 +76,6 @@ def setup_proxy(element, config):
15776
element.set_property('proxy-pw', config.get('password'))
15877

15978

160-
def convert_taglist(taglist):
161-
"""Convert a :class:`Gst.TagList` to plain Python types.
162-
163-
Knows how to convert:
164-
165-
- Dates
166-
- Buffers
167-
- Numbers
168-
- Strings
169-
- Booleans
170-
171-
Unknown types will be ignored and debug logged. Tag keys are all strings
172-
defined as part GStreamer under GstTagList_.
173-
174-
.. _GstTagList: https://developer.gnome.org/gstreamer/stable/\
175-
gstreamer-GstTagList.html
176-
177-
:param taglist: A GStreamer taglist to be converted.
178-
:type taglist: :class:`Gst.TagList`
179-
:rtype: dictionary of tag keys with a list of values.
180-
"""
181-
result = collections.defaultdict(list)
182-
183-
for n in range(taglist.n_tags()):
184-
tag = taglist.nth_tag_name(n)
185-
186-
for i in range(taglist.get_tag_size(tag)):
187-
value = taglist.get_value_index(tag, i)
188-
189-
if isinstance(value, Gst.DateTime):
190-
result[tag].append(value.to_iso8601_string())
191-
if isinstance(value, (compat.string_types, bool, numbers.Number)):
192-
result[tag].append(value)
193-
else:
194-
logger.log(
195-
TRACE, 'Ignoring unknown tag data: %r = %r', tag, value)
196-
197-
return result
198-
199-
20079
class Signals(object):
20180

20281
"""Helper for tracking gobject signal registrations"""

mopidy/file/library.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import urllib2
88

99
from mopidy import backend, exceptions, models
10-
from mopidy.audio import scan, utils
10+
from mopidy.audio import scan, tags
1111
from mopidy.internal import path
1212

1313

@@ -83,7 +83,7 @@ def lookup(self, uri):
8383

8484
try:
8585
result = self._scanner.scan(uri)
86-
track = utils.convert_tags_to_track(result.tags).copy(
86+
track = tags.convert_tags_to_track(result.tags).copy(
8787
uri=uri, length=result.duration)
8888
except exceptions.ScannerError as e:
8989
logger.warning('Failed looking up %s: %s', uri, e)

mopidy/local/commands.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import time
77

88
from mopidy import commands, compat, exceptions
9-
from mopidy.audio import scan, utils
9+
from mopidy.audio import scan, tags
1010
from mopidy.internal import path
1111
from mopidy.local import translator
1212

@@ -140,18 +140,18 @@ def run(self, args, config):
140140
relpath = translator.local_track_uri_to_path(uri, media_dir)
141141
file_uri = path.path_to_uri(os.path.join(media_dir, relpath))
142142
result = scanner.scan(file_uri)
143-
tags, duration = result.tags, result.duration
144143
if not result.playable:
145144
logger.warning('Failed %s: No audio found in file.', uri)
146-
elif duration < MIN_DURATION_MS:
145+
elif result.duration < MIN_DURATION_MS:
147146
logger.warning('Failed %s: Track shorter than %dms',
148147
uri, MIN_DURATION_MS)
149148
else:
150149
mtime = file_mtimes.get(os.path.join(media_dir, relpath))
151-
track = utils.convert_tags_to_track(tags).replace(
152-
uri=uri, length=duration, last_modified=mtime)
150+
track = tags.convert_tags_to_track(result.tags).replace(
151+
uri=uri, length=result.duration, last_modified=mtime)
153152
if library.add_supports_tags_and_duration:
154-
library.add(track, tags=tags, duration=duration)
153+
library.add(
154+
track, tags=result.tags, duration=result.duration)
155155
else:
156156
library.add(track)
157157
logger.debug('Added %s', track.uri)

mopidy/stream/actor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pykka
99

1010
from mopidy import audio as audio_lib, backend, exceptions, stream
11-
from mopidy.audio import scan, utils
11+
from mopidy.audio import scan, tags
1212
from mopidy.compat import urllib
1313
from mopidy.internal import http, playlists
1414
from mopidy.models import Track
@@ -60,7 +60,7 @@ def lookup(self, uri):
6060

6161
try:
6262
result = self._scanner.scan(uri)
63-
track = utils.convert_tags_to_track(result.tags).replace(
63+
track = tags.convert_tags_to_track(result.tags).replace(
6464
uri=uri, length=result.duration)
6565
except exceptions.ScannerError as e:
6666
logger.warning('Problem looking up %s: %s', uri, e)

0 commit comments

Comments
 (0)