mopidy/mopidy/m3u/translator.py
Stein Magnus Jodal 86e0d12a8c Merge pull request #1193 from glogiotatidis/m3u8loading
Support loading of m3u8 playlists.
2015-08-07 15:21:39 +02:00

130 lines
3.9 KiB
Python

from __future__ import absolute_import, unicode_literals
import codecs
import logging
import os
import re
import urllib
import urlparse
from mopidy import compat
from mopidy.internal import encoding, path
from mopidy.models import Track
M3U_EXTINF_RE = re.compile(r'#EXTINF:(-1|\d+),(.*)')
logger = logging.getLogger(__name__)
def playlist_uri_to_path(uri, playlists_dir):
if not uri.startswith('m3u:'):
raise ValueError('Invalid URI %s' % uri)
file_path = path.uri_to_path(uri)
return os.path.join(playlists_dir, file_path)
def path_to_playlist_uri(relpath):
"""Convert path relative to playlists_dir to M3U URI."""
if isinstance(relpath, compat.text_type):
relpath = relpath.encode('utf-8')
return b'm3u:%s' % urllib.quote(relpath)
def m3u_extinf_to_track(line):
"""Convert extended M3U directive to track template."""
m = M3U_EXTINF_RE.match(line)
if not m:
logger.warning('Invalid extended M3U directive: %s', line)
return Track()
(runtime, title) = m.groups()
if int(runtime) > 0:
return Track(name=title, length=1000 * int(runtime))
else:
return Track(name=title)
def parse_m3u(file_path, media_dir=None):
r"""
Convert M3U file list to list of tracks
Example M3U data::
# This is a comment
Alternative\Band - Song.mp3
Classical\Other Band - New Song.mp3
Stuff.mp3
D:\More Music\Foo.mp3
http://www.example.com:8000/Listen.pls
http://www.example.com/~user/Mine.mp3
Example extended M3U data::
#EXTM3U
#EXTINF:123, Sample artist - Sample title
Sample.mp3
#EXTINF:321,Example Artist - Example title
Greatest Hits\Example.ogg
#EXTINF:-1,Radio XMP
http://mp3stream.example.com:8000/
- Relative paths of songs should be with respect to location of M3U.
- Paths are normally platform specific.
- Lines starting with # are ignored, except for extended M3U directives.
- Track.name and Track.length are set from extended M3U directives.
- m3u files are latin-1.
- m3u8 files are utf-8
"""
# TODO: uris as bytes
file_encoding = 'utf-8' if file_path.endswith(b'.m3u8') else 'latin1'
tracks = []
try:
with codecs.open(file_path, 'rb', file_encoding, 'replace') as m3u:
contents = m3u.readlines()
except IOError as error:
logger.warning('Couldn\'t open m3u: %s', encoding.locale_decode(error))
return tracks
if not contents:
return tracks
# Strip newlines left by codecs
contents = [line.strip() for line in contents]
extended = contents[0].startswith('#EXTM3U')
track = Track()
for line in contents:
if line.startswith('#'):
if extended and line.startswith('#EXTINF'):
track = m3u_extinf_to_track(line)
continue
if urlparse.urlsplit(line).scheme:
tracks.append(track.replace(uri=line))
elif os.path.normpath(line) == os.path.abspath(line):
uri = path.path_to_uri(line)
tracks.append(track.replace(uri=uri))
elif media_dir is not None:
uri = path.path_to_uri(os.path.join(media_dir, line))
tracks.append(track.replace(uri=uri))
track = Track()
return tracks
def save_m3u(filename, tracks, encoding='latin1', errors='replace'):
extended = any(track.name for track in tracks)
# codecs.open() always uses binary mode, just being explicit here
with codecs.open(filename, 'wb', encoding, errors) as m3u:
if extended:
m3u.write('#EXTM3U' + os.linesep)
for track in tracks:
if extended and track.name:
m3u.write('#EXTINF:%d,%s%s' % (
track.length // 1000 if track.length else -1,
track.name,
os.linesep))
m3u.write(track.uri + os.linesep)