Bulk Emby to Jellyfin

This commit is contained in:
mcarlton00 2019-04-25 21:29:13 -04:00
parent df0e503d45
commit 0ef95ea9f6
21 changed files with 267 additions and 266 deletions

View File

@ -2,7 +2,7 @@ include .travis.yml
include LICENSE
include MANIFEST.in
include README.rst
include mopidy_emby/ext.conf
include mopidy_jellyfin/ext.conf
include tox.ini
recursive-include tests *.py

View File

@ -1,26 +0,0 @@
from __future__ import unicode_literals
import logging
from mopidy import backend
import pykka
from mopidy_emby.library import EmbyLibraryProvider
from mopidy_emby.playback import EmbyPlaybackProvider
from mopidy_emby.remote import EmbyHandler
logger = logging.getLogger(__name__)
class EmbyBackend(pykka.ThreadingActor, backend.Backend):
uri_schemes = ['emby']
def __init__(self, config, audio):
super(EmbyBackend, self).__init__()
self.library = EmbyLibraryProvider(backend=self)
self.playback = EmbyPlaybackProvider(audio=audio, backend=self)
self.playlist = None
self.remote = EmbyHandler(config)

View File

@ -13,8 +13,8 @@ logger = logging.getLogger(__name__)
class Extension(ext.Extension):
dist_name = 'Mopidy-Emby'
ext_name = 'emby'
dist_name = 'Mopidy-Jellyfin'
ext_name = 'jellyfin'
version = __version__
def get_default_config(self):
@ -34,5 +34,5 @@ class Extension(ext.Extension):
return schema
def setup(self, registry):
from .backend import EmbyBackend
registry.add('backend', EmbyBackend)
from .backend import JellyfinBackend
registry.add('backend', JellyfinBackend)

View File

@ -0,0 +1,26 @@
from __future__ import unicode_literals
import logging
from mopidy import backend
import pykka
from mopidy_jellyfin.library import JellyfinLibraryProvider
from mopidy_jellyfin.playback import JellyfinPlaybackProvider
from mopidy_jellyfin.remote import JellyfinHandler
logger = logging.getLogger(__name__)
class JellyfinBackend(pykka.ThreadingActor, backend.Backend):
uri_schemes = ['jellyfin']
def __init__(self, config, audio):
super(JellyfinBackend, self).__init__()
self.library = JellyfinLibraryProvider(backend=self)
self.playback = JellyfinPlaybackProvider(audio=audio, backend=self)
self.playlist = None
self.remote = JellyfinHandler(config)

View File

@ -1,4 +1,4 @@
[emby]
[jellyfin]
enabled = true
hostname =
port =

View File

@ -8,32 +8,32 @@ from mopidy import backend, models
logger = logging.getLogger(__name__)
class EmbyLibraryProvider(backend.LibraryProvider):
class JellyfinLibraryProvider(backend.LibraryProvider):
root_directory = models.Ref.directory(uri='emby:',
name='Emby')
root_directory = models.Ref.directory(uri='jellyfin:',
name='Jellyfin')
def browse(self, uri):
# artistlist
if uri == self.root_directory.uri:
logger.debug('Get Emby artist list')
logger.debug('Get Jellyfin artist list')
return self.backend.remote.get_artists()
# split uri
parts = uri.split(':')
# artists albums
# uri: emby:artist:<artist_id>
if uri.startswith('emby:artist:') and len(parts) == 3:
logger.debug('Get Emby album list')
# uri: jellyfin:artist:<artist_id>
if uri.startswith('jellyfin:artist:') and len(parts) == 3:
logger.debug('Get Jellyfin album list')
artist_id = parts[-1]
return self.backend.remote.get_albums(artist_id)
# tracklist
# uri: emby:album:<album_id>
if uri.startswith('emby:album:') and len(parts) == 3:
logger.debug('Get Emby track list')
# uri: jellyfin:album:<album_id>
if uri.startswith('jellyfin:album:') and len(parts) == 3:
logger.debug('Get Jellyfin track list')
album_id = parts[-1]
return self.backend.remote.get_tracks(album_id)
@ -41,15 +41,15 @@ class EmbyLibraryProvider(backend.LibraryProvider):
return []
def lookup(self, uri=None, uris=None):
logger.debug('Emby lookup: {}'.format(uri or uris))
logger.debug('Jellyfin lookup: {}'.format(uri or uris))
if uri:
parts = uri.split(':')
if uri.startswith('emby:track:') and len(parts) == 3:
if uri.startswith('jellyfin:track:') and len(parts) == 3:
track_id = parts[-1]
tracks = [self.backend.remote.get_track(track_id)]
elif uri.startswith('emby:album:') and len(parts) == 3:
elif uri.startswith('jellyfin:album:') and len(parts) == 3:
album_id = parts[-1]
album_data = self.backend.remote.get_directory(album_id)
tracks = [
@ -59,13 +59,13 @@ class EmbyLibraryProvider(backend.LibraryProvider):
tracks = sorted(tracks, key=lambda k: k.track_no)
elif uri.startswith('emby:artist:') and len(parts) == 3:
elif uri.startswith('jellyfin:artist:') and len(parts) == 3:
artist_id = parts[-1]
tracks = self.backend.remote.lookup_artist(artist_id)
else:
logger.info('Unknown Emby lookup URI: {}'.format(uri))
logger.info('Unknown Jellyfin lookup URI: {}'.format(uri))
tracks = []
return [track for track in tracks if track]

View File

@ -8,17 +8,17 @@ from mopidy import backend
logger = logging.getLogger(__name__)
class EmbyPlaybackProvider(backend.PlaybackProvider):
class JellyfinPlaybackProvider(backend.PlaybackProvider):
def translate_uri(self, uri):
if uri.startswith('emby:track:') and len(uri.split(':')) == 3:
if uri.startswith('jellyfin:track:') and len(uri.split(':')) == 3:
id = uri.split(':')[-1]
track_url = self.backend.remote.api_url(
'/Audio/{}/stream?static=true'.format(id)
)
logger.debug('Emby track streaming url: {}'.format(track_url))
logger.debug('Jellyfin track streaming url: {}'.format(track_url))
return track_url

View File

@ -11,24 +11,24 @@ from mopidy import httpclient, models
import requests
import mopidy_emby
from mopidy_emby.utils import cache
import mopidy_jellyfin
from mopidy_jellyfin.utils import cache
logger = logging.getLogger(__name__)
class EmbyHandler(object):
class JellyfinHandler(object):
def __init__(self, config):
self.hostname = config['emby']['hostname']
self.port = config['emby']['port']
self.username = config['emby']['username']
self.password = config['emby']['password']
self.hostname = config['jellyfin']['hostname']
self.port = config['jellyfin']['port']
self.username = config['jellyfin']['username']
self.password = config['jellyfin']['password']
self.proxy = config['proxy']
self.user_id = config['emby'].get('user_id', False)
self.user_id = config['jellyfin'].get('user_id', False)
self.cert = None
client_cert = config['emby'].get('client_cert', None)
client_key = config['emby'].get('client_key', None)
client_cert = config['jellyfin'].get('client_cert', None)
client_key = config['jellyfin'].get('client_key', None)
if client_cert is not None and client_key is not None:
self.cert = (client_cert, client_key)
@ -50,12 +50,13 @@ class EmbyHandler(object):
if user:
return user
else:
raise Exception('No Emby user {} found'.format(self.username))
raise Exception('No Jellyfin user {} found'.format(self.username))
def _get_token(self):
"""Return token for a user.
"""
url = self.api_url('/Users/AuthenticateByName')
logger.info(self.auth_data)
r = requests.post(
url, headers=self.headers, data=self.auth_data, cert=self.cert)
@ -66,11 +67,11 @@ class EmbyHandler(object):
"""
return {
'username': self.username,
'pw': self.password
'Pw': self.password
}
def _create_headers(self, token=None):
"""Return header dict that is needed to talk to the Emby API.
"""Return header dict that is needed to talk to the Jellyfin API.
"""
headers = {}
@ -93,7 +94,7 @@ class EmbyHandler(object):
proxy = httpclient.format_proxy(self.proxy)
full_user_agent = httpclient.format_user_agent(
'/'.join(
(mopidy_emby.Extension.dist_name, mopidy_emby.__version__)
(mopidy_jellyfin.Extension.dist_name, mopidy_jellyfin.__version__)
)
)
@ -120,18 +121,18 @@ class EmbyHandler(object):
except Exception as e:
logger.info(
'Emby connection on try {} with problem: {}'.format(
'Jellyfin connection on try {} with problem: {}'.format(
counter, e
)
)
counter += 1
raise Exception('Cant connect to Emby API')
raise Exception('Cant connect to Jellyfin API')
def api_url(self, endpoint):
"""Returns a joined url.
Takes host, port and endpoint and generates a valid emby API url.
Takes host, port and endpoint and generates a valid jellyfin API url.
"""
# check if http or https is defined as host and create hostname
hostname_list = [self.hostname]
@ -172,19 +173,19 @@ class EmbyHandler(object):
if id:
logging.debug(
'Emby: Found music root dir with ID: {}'.format(id[0])
'Jellyfin: Found music root dir with ID: {}'.format(id[0])
)
return id[0]
else:
logging.debug(
'Emby: All directories found: {}'.format(
'Jellyfin: All directories found: {}'.format(
[i['CollectionType']
for i in data['Items']
if 'CollectionType' in i.items()]
)
)
raise Exception('Emby: Cant find music root directory')
raise Exception('Jellyfin: Cant find music root directory')
def get_artists(self):
music_root = self.get_music_root()
@ -195,7 +196,7 @@ class EmbyHandler(object):
return [
models.Ref.artist(
uri='emby:artist:{}'.format(i['Id']),
uri='jellyfin:artist:{}'.format(i['Id']),
name=i['Name']
)
for i in artists
@ -209,7 +210,7 @@ class EmbyHandler(object):
)
return [
models.Ref.album(
uri='emby:album:{}'.format(i['Id']),
uri='jellyfin:album:{}'.format(i['Id']),
name=i['Name']
)
for i in albums
@ -224,7 +225,7 @@ class EmbyHandler(object):
return [
models.Ref.track(
uri='emby:track:{}'.format(
uri='jellyfin:track:{}'.format(
i['Id']
),
name=i['Name']
@ -235,7 +236,7 @@ class EmbyHandler(object):
@cache()
def get_directory(self, id):
"""Get directory from Emby API.
"""Get directory from Jellyfin API.
:param id: Directory ID
:type id: int
@ -253,7 +254,7 @@ class EmbyHandler(object):
@cache()
def get_item(self, id):
"""Get item from Emby API.
"""Get item from Jellyfin API.
:param id: Item ID
:type id: int
@ -266,21 +267,21 @@ class EmbyHandler(object):
)
)
logger.debug('Emby item: {}'.format(data))
logger.debug('Jellyfin item: {}'.format(data))
return data
def create_track(self, track):
"""Create track from Emby API track dict.
"""Create track from Jellyfin API track dict.
:param track: Track from Emby API
:param track: Track from Jellyfin API
:type track: dict
:returns: Track
:rtype: mopidy.models.Track
"""
# TODO: add more metadata
return models.Track(
uri='emby:track:{}'.format(
uri='jellyfin:track:{}'.format(
track['Id']
),
name=track.get('Name'),
@ -323,7 +324,7 @@ class EmbyHandler(object):
def get_track(self, track_id):
"""Get track.
:param track_id: ID of a Emby track
:param track_id: ID of a Jellyfin track
:type track_id: int
:returns: track
:rtype: mopidy.models.Track
@ -333,7 +334,7 @@ class EmbyHandler(object):
return self.create_track(track)
def _get_search(self, itemtype, term):
"""Gets search data from Emby API.
"""Gets search data from Jellyfin API.
:param itemtype: Type to search for
:param term: Search term
@ -351,7 +352,7 @@ class EmbyHandler(object):
elif itemtype == 'track_name':
query = 'Audio'
else:
raise Exception('Emby search: no itemtype {}'.format(itemtype))
raise Exception('Jellyfin search: no itemtype {}'.format(itemtype))
data = self.r_get(
self.api_url(
@ -367,14 +368,14 @@ class EmbyHandler(object):
@cache()
def search(self, query):
"""Search Emby for a term.
"""Search Jellyfin for a term.
:param query: Search query
:type query: dict
:returns: Search results
:rtype: mopidy.models.SearchResult
"""
logger.debug('Searching in Emby for {}'.format(query))
logger.debug('Searching in Jellyfin for {}'.format(query))
# something to store the results in
data = []
@ -404,7 +405,7 @@ class EmbyHandler(object):
tracks.append(
models.Track(
uri='emby:track:{}'.format(item['ItemId']),
uri='jellyfin:track:{}'.format(item['ItemId']),
track_no=item.get('IndexNumber'),
name=item.get('Name'),
artists=track_artists,
@ -425,7 +426,7 @@ class EmbyHandler(object):
albums.append(
models.Album(
uri='emby:album:{}'.format(item['ItemId']),
uri='jellyfin:album:{}'.format(item['ItemId']),
name=item.get('Name'),
artists=album_artists
)
@ -434,13 +435,13 @@ class EmbyHandler(object):
elif item['Type'] == 'MusicArtist':
artists.append(
models.Artist(
uri='emby:artist:{}'.format(item['ItemId']),
uri='jellyfin:artist:{}'.format(item['ItemId']),
name=item.get('Name')
)
)
return models.SearchResult(
uri='emby:search',
uri='jellyfin:search',
tracks=tracks,
artists=artists,
albums=albums
@ -483,7 +484,7 @@ class EmbyHandler(object):
@staticmethod
def ticks_to_milliseconds(ticks):
"""Converts Emby track length ticks to milliseconds.
"""Converts Jellyfin track length ticks to milliseconds.
:param ticks: Ticks
:type ticks: int

View File

@ -1,5 +1,5 @@
[flake8]
application-import-names = mopidy_emby,tests
application-import-names = mopidy_jellyfin,tests
exclude = .git,.tox
[wheel]

View File

@ -12,13 +12,13 @@ def get_version(filename):
setup(
name='Mopidy-Emby',
version=get_version('mopidy_emby/__init__.py'),
url='https://github.com/xsteadfastx/mopidy-emby',
name='Mopidy-Jellyfin',
version=get_version('mopidy_jellyfin/__init__.py'),
url='https://github.com/xsteadfastx/mopidy-jellyfin',
license='Apache License, Version 2.0',
author='Marvin Steadfast',
author_email='marvin@xsteadfastx.org',
description='Mopidy extension for playing music from Emby',
description='Mopidy extension for playing music from jellyfin',
long_description=open('README.rst').read(),
packages=find_packages(exclude=['tests', 'tests.*']),
zip_safe=False,
@ -31,7 +31,7 @@ setup(
],
entry_points={
'mopidy.ext': [
'emby = mopidy_emby:Extension',
'jellyfin = mopidy_jellyfin:Extension',
],
},
classifiers=[

View File

@ -4,17 +4,17 @@ import mopidy
import pytest
import mopidy_emby
import mopidy_jellyfin
@pytest.fixture
def config():
return {
'emby': {
'jellyfin': {
'hostname': 'https://foo.bar',
'port': 443,
'username': 'embyuser',
'password': 'embypassword'
'username': 'jellyfinuser',
'password': 'jellyfinpassword'
},
'proxy': {
'foo': 'bar'
@ -23,32 +23,32 @@ def config():
@pytest.fixture
def emby_client(config, mocker):
mocker.patch('mopidy_emby.remote.cache')
mocker.patch('mopidy_emby.remote.EmbyHandler._get_token')
mocker.patch('mopidy_emby.remote.EmbyHandler._create_headers')
mocker.patch('mopidy_emby.remote.EmbyHandler._get_user',
def jellyfin_client(config, mocker):
mocker.patch('mopidy_jellyfin.remote.cache')
mocker.patch('mopidy_jellyfin.remote.JellyfinHandler._get_token')
mocker.patch('mopidy_jellyfin.remote.JellyfinHandler._create_headers')
mocker.patch('mopidy_jellyfin.remote.JellyfinHandler._get_user',
return_value=[{'Id': 'mock'}])
mocker.patch('mopidy_emby.remote.EmbyHandler._password_data')
mocker.patch('mopidy_jellyfin.remote.JellyfinHandler._password_data')
return mopidy_emby.remote.EmbyHandler(config)
return mopidy_jellyfin.remote.JellyfinHandler(config)
@pytest.fixture
def backend_mock():
backend_mock = mock.Mock(autospec=mopidy_emby.backend.EmbyBackend)
backend_mock = mock.Mock(autospec=mopidy_jellyfin.backend.JellyfinBackend)
return backend_mock
@pytest.fixture
def libraryprovider(backend_mock):
backend_mock.remote(autospec=mopidy_emby.backend.EmbyHandler)
backend_mock.remote(autospec=mopidy_jellyfin.backend.JellyfinHandler)
backend_mock.remote.get_artists.return_value = ['Artistlist']
backend_mock.remote.get_albums.return_value = ['Albumlist']
backend_mock.remote.get_tracks.return_value = ['Tracklist']
backend_mock.remote.get_track.return_value = mopidy.models.Track(
uri='emby:track:eb6c305bdb1e40d3b46909473c22d906',
uri='jellyfin:track:eb6c305bdb1e40d3b46909473c22d906',
name='The One With the Tambourine',
track_no=1,
genre=None,
@ -76,12 +76,12 @@ def libraryprovider(backend_mock):
}
backend_mock.remote.lookup_artist.return_value = ['track1', 'track2']
return mopidy_emby.backend.EmbyLibraryProvider(backend_mock)
return mopidy_jellyfin.backend.JellyfinLibraryProvider(backend_mock)
@pytest.fixture
def playbackprovider(backend_mock, emby_client):
backend_mock.remote = emby_client
def playbackprovider(backend_mock, jellyfin_client):
backend_mock.remote = jellyfin_client
return mopidy_emby.backend.EmbyPlaybackProvider(audio=mock.Mock(),
return mopidy_jellyfin.backend.JellyfinPlaybackProvider(audio=mock.Mock(),
backend=backend_mock)

View File

@ -1,7 +1,7 @@
{
"ServerId": "1efa5077976bfa92bc71652404f646ec",
"SessionInfo": {
"UserName": "embyuser",
"UserName": "jellyfinuser",
"DeviceName": "mopidy",
"ApplicationVersion": "0.0.0",
"QueueableMediaTypes": [],
@ -22,7 +22,7 @@
"DeviceId": "mopidy"
},
"User": {
"Name": "embyuser",
"Name": "jellyfinuser",
"HasConfiguredEasyPassword": false,
"LastActivityDate": "2016-11-30T08:10:01.7344640Z",
"HasPassword": true,

View File

@ -1,7 +1,7 @@
{
"ServerId": "1efa5077976bfa92bc71652404f646ec",
"SessionInfo": {
"UserName": "embyuser",
"UserName": "jellyfinuser",
"DeviceName": "mopidy",
"ApplicationVersion": "0.0.0",
"QueueableMediaTypes": [],
@ -22,7 +22,7 @@
"DeviceId": "mopidy"
},
"User": {
"Name": "embyuser",
"Name": "jellyfinuser",
"HasConfiguredEasyPassword": false,
"LastActivityDate": "2016-11-30T08:10:01.7344640Z",
"HasPassword": true,

View File

@ -1,6 +1,6 @@
[
{
"Name": "embyuser",
"Name": "jellyfinuser",
"HasConfiguredEasyPassword": false,
"LastActivityDate": "2016-11-29T13:35:42.9569180Z",
"HasPassword": true,

View File

@ -2,16 +2,16 @@ from __future__ import unicode_literals
import mock
from mopidy_emby import library, playback
from mopidy_emby.backend import EmbyBackend
from mopidy_jellyfin import library, playback
from mopidy_jellyfin.backend import JellyfinBackend
@mock.patch('mopidy_emby.backend.EmbyHandler', autospec=True)
def test_backend(embyhander_mock, config):
backend = EmbyBackend(config, mock.Mock())
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler', autospec=True)
def test_backend(jellyfinhander_mock, config):
backend = JellyfinBackend(config, mock.Mock())
assert backend.uri_schemes == ['emby']
assert backend.uri_schemes == ['jellyfin']
assert isinstance(backend.library, library.EmbyLibraryProvider)
assert isinstance(backend.playback, playback.EmbyPlaybackProvider)
assert isinstance(backend.library, library.JellyfinLibraryProvider)
assert isinstance(backend.playback, playback.JellyfinPlaybackProvider)
assert backend.playlist is None

View File

@ -1,6 +1,6 @@
from __future__ import unicode_literals
from mopidy_emby import Extension
from mopidy_jellyfin import Extension
def test_get_default_config():
@ -8,7 +8,7 @@ def test_get_default_config():
config = ext.get_default_config()
assert '[emby]' in config
assert '[jellyfin]' in config
assert 'enabled = true' in config

View File

@ -6,16 +6,16 @@ import pytest
@pytest.mark.parametrize('uri,expected', [
('emby:', ['Artistlist']),
('emby:artist:123', ['Albumlist']),
('emby:album:123', ['Tracklist']),
('jellyfin:', ['Artistlist']),
('jellyfin:artist:123', ['Albumlist']),
('jellyfin:album:123', ['Tracklist']),
])
def test_browse(uri, expected, libraryprovider):
assert libraryprovider.browse(uri) == expected
@pytest.mark.parametrize('uri,expected', [
('emby:track:123', [
('jellyfin:track:123', [
Track(
album=Album(
artists=[
@ -26,10 +26,10 @@ def test_browse(uri, expected, libraryprovider):
length=241162,
name='The One With the Tambourine',
track_no=1,
uri='emby:track:eb6c305bdb1e40d3b46909473c22d906'
uri='jellyfin:track:eb6c305bdb1e40d3b46909473c22d906'
)
]),
('emby:album:123', [
('jellyfin:album:123', [
Track(
album=Album(
artists=[
@ -40,18 +40,18 @@ def test_browse(uri, expected, libraryprovider):
length=241162,
name='The One With the Tambourine',
track_no=1,
uri='emby:track:eb6c305bdb1e40d3b46909473c22d906'
uri='jellyfin:track:eb6c305bdb1e40d3b46909473c22d906'
)
]),
('emby:artist:123', ['track1', 'track2']),
('emby:track', [])
('jellyfin:artist:123', ['track1', 'track2']),
('jellyfin:track', [])
])
def test_lookup_uri(uri, expected, libraryprovider):
assert libraryprovider.lookup(uri=uri) == expected
@pytest.mark.parametrize('uri,expected', [
(['emby:track:123'], {'emby:track:123': [
(['jellyfin:track:123'], {'jellyfin:track:123': [
Track(
album=Album(
artists=[
@ -62,10 +62,10 @@ def test_lookup_uri(uri, expected, libraryprovider):
length=241162,
name='The One With the Tambourine',
track_no=1,
uri='emby:track:eb6c305bdb1e40d3b46909473c22d906'
uri='jellyfin:track:eb6c305bdb1e40d3b46909473c22d906'
)
]}),
(['emby:track'], {u'emby:track': []})
(['jellyfin:track'], {u'jellyfin:track': []})
])
def test_lookup_uris(uri, expected, libraryprovider):
assert libraryprovider.lookup(uris=uri) == expected

View File

@ -5,14 +5,14 @@ import pytest
@pytest.mark.parametrize('uri,expected', [
(
'emby:track:123',
'jellyfin:track:123',
[
'https://foo.bar:443/Audio/123/stream?static=true&format=json',
'https://foo.bar:443/Audio/123/stream?format=json&static=true',
]
),
(
'emby:foobar',
'jellyfin:foobar',
[None]
)
])

View File

@ -10,118 +10,118 @@ import pytest
import requests
from mopidy_emby import backend
from mopidy_jellyfin import backend
@pytest.mark.parametrize('hostname,url,expected', [
('https://foo.bar', '/Foo', 'https://foo.bar:443/Foo?format=json'),
('foo.bar', '/Foo', 'http://foo.bar:443/Foo?format=json'),
])
@mock.patch('mopidy_emby.backend.EmbyHandler._get_token')
@mock.patch('mopidy_emby.backend.EmbyHandler._create_headers')
@mock.patch('mopidy_emby.backend.EmbyHandler._get_user')
@mock.patch('mopidy_emby.backend.EmbyHandler._password_data')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler._get_token')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler._create_headers')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler._get_user')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler._password_data')
def test_api_url(password_data_mock, get_user_mock, create_header_mock,
get_token_mock, config, hostname, url, expected):
get_user_mock.return_value = [{'Id': 'foo'}]
config['emby']['hostname'] = hostname
emby = backend.EmbyHandler(config)
config['jellyfin']['hostname'] = hostname
jellyfin = backend.JellyfinHandler(config)
assert emby.api_url(url) == expected
assert jellyfin.api_url(url) == expected
@pytest.mark.parametrize('data,expected', [
('tests/data/get_music_root0.json', 'eb169f4ba53fc560f549cb0f2a47d577')
])
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
def test_get_music_root(r_get_mock, data, expected, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
def test_get_music_root(r_get_mock, data, expected, jellyfin_client):
with open(data, 'r') as f:
r_get_mock.return_value = json.load(f)
assert emby_client.get_music_root() == expected
assert jellyfin_client.get_music_root() == expected
@pytest.mark.parametrize('data,expected', [
(
'tests/data/get_music_root1.json',
'Emby: Cant find music root directory'
'Jellyfin: Cant find music root directory'
)
])
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
def test_get_music_root_cant_find(r_get_mock, data, expected, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
def test_get_music_root_cant_find(r_get_mock, data, expected, jellyfin_client):
with open(data, 'r') as f:
r_get_mock.return_value = json.load(f)
with pytest.raises(Exception) as execinfo:
emby_client.get_music_root()
jellyfin_client.get_music_root()
assert expected in str(execinfo.value)
@mock.patch('mopidy_emby.backend.EmbyHandler.get_music_root')
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
def test_get_artists(r_get_mock, get_music_root_mock, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.get_music_root')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
def test_get_artists(r_get_mock, get_music_root_mock, jellyfin_client):
expected = [
Ref(name=u'Chairlift',
type='artist',
uri='emby:artist:e0361aff955c30f5a6dcc6fcf0c9d1cf'),
uri='jellyfin:artist:e0361aff955c30f5a6dcc6fcf0c9d1cf'),
Ref(name=u'Hans Zimmer',
type='artist',
uri='emby:artist:36de3368f493ebca94a55a411cc87862'),
uri='jellyfin:artist:36de3368f493ebca94a55a411cc87862'),
Ref(name=u'The Menzingers',
type='artist',
uri='emby:artist:21c8f78763231ece7defd07b5f3f56be')
uri='jellyfin:artist:21c8f78763231ece7defd07b5f3f56be')
]
with open('tests/data/get_artists0.json', 'r') as f:
r_get_mock.return_value = json.load(f)
assert emby_client.get_artists() == expected
assert jellyfin_client.get_artists() == expected
@mock.patch('mopidy_emby.backend.EmbyHandler.get_music_root')
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
def test_get_albums(r_get_mock, get_music_root_mock, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.get_music_root')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
def test_get_albums(r_get_mock, get_music_root_mock, jellyfin_client):
expected = [
Ref(name=u'American Football',
type='album',
uri='emby:album:6e4a2da7df0502650bb9b091312c3dbf'),
uri='jellyfin:album:6e4a2da7df0502650bb9b091312c3dbf'),
Ref(name=u'American Football',
type='album',
uri='emby:album:ca498ea939b28593744c051d9f5e74ed'),
uri='jellyfin:album:ca498ea939b28593744c051d9f5e74ed'),
Ref(name=u'American Football',
type='album',
uri='emby:album:0db6395ab76b6edbaba3a51ef23d0aa3')
uri='jellyfin:album:0db6395ab76b6edbaba3a51ef23d0aa3')
]
with open('tests/data/get_albums0.json', 'r') as f:
r_get_mock.return_value = json.load(f)
assert emby_client.get_albums(0) == expected
assert jellyfin_client.get_albums(0) == expected
@mock.patch('mopidy_emby.backend.EmbyHandler.get_music_root')
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
def test_get_tracks(r_get_mock, get_music_root_mock, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.get_music_root')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
def test_get_tracks(r_get_mock, get_music_root_mock, jellyfin_client):
expected = [
Ref(name=u'The One With the Tambourine',
type='track',
uri='emby:track:eb6c305bdb1e40d3b46909473c22d906'),
uri='jellyfin:track:eb6c305bdb1e40d3b46909473c22d906'),
Ref(name=u'Letters and Packages',
type='track',
uri='emby:track:7739d3830818c7aacf6c346172384914'),
uri='jellyfin:track:7739d3830818c7aacf6c346172384914'),
Ref(name=u'Five Silent Miles',
type='track',
uri='emby:track:f84df9f70e592a3abda82b1d78026608')
uri='jellyfin:track:f84df9f70e592a3abda82b1d78026608')
]
with open('tests/data/get_tracks0.json', 'r') as f:
r_get_mock.return_value = json.load(f)
assert emby_client.get_tracks(0) == expected
assert jellyfin_client.get_tracks(0) == expected
@pytest.mark.parametrize('data,expected', [
@ -133,7 +133,7 @@ def test_get_tracks(r_get_mock, get_music_root_mock, emby_client):
length=295915,
name=u'Ottawa to Osaka',
track_no=6,
uri='emby:track:18e5a9871e6a4a2294d5af998457ca16'
uri='jellyfin:track:18e5a9871e6a4a2294d5af998457ca16'
)
),
(
@ -144,7 +144,7 @@ def test_get_tracks(r_get_mock, get_music_root_mock, emby_client):
length=269035,
name=u'Crying in Public',
track_no=5,
uri='emby:track:37f57f0b370274af96de06895a78c2c3'
uri='jellyfin:track:37f57f0b370274af96de06895a78c2c3'
)
),
(
@ -155,15 +155,15 @@ def test_get_tracks(r_get_mock, get_music_root_mock, emby_client):
length=283115,
name=u'Polymorphing',
track_no=2,
uri='emby:track:3315cccffe37ab47d50d1dbeefd3537b'
uri='jellyfin:track:3315cccffe37ab47d50d1dbeefd3537b'
)
),
])
def test_create_track(data, expected, emby_client):
def test_create_track(data, expected, jellyfin_client):
with open(data, 'r') as f:
track = json.load(f)
assert emby_client.create_track(track) == expected
assert jellyfin_client.create_track(track) == expected
@pytest.mark.parametrize('data,expected', [
@ -180,11 +180,11 @@ def test_create_track(data, expected, emby_client):
Album(artists=[Artist(name=u'Chairlift')], name=u'Moth')
),
])
def test_create_album(data, expected, emby_client):
def test_create_album(data, expected, jellyfin_client):
with open(data, 'r') as f:
track = json.load(f)
assert emby_client.create_album(track) == expected
assert jellyfin_client.create_album(track) == expected
@pytest.mark.parametrize('data,expected', [
@ -201,20 +201,20 @@ def test_create_album(data, expected, emby_client):
[Artist(name=u'Chairlift')]
),
])
def test_create_artists(data, expected, emby_client):
def test_create_artists(data, expected, jellyfin_client):
with open(data, 'r') as f:
track = json.load(f)
assert emby_client.create_artists(track) == expected
assert jellyfin_client.create_artists(track) == expected
@pytest.mark.parametrize('data,user_id', [
('tests/data/get_user0.json', '2ec276a2642e54a19b612b9418a8bd3b')
])
@mock.patch('mopidy_emby.remote.requests.get')
@mock.patch('mopidy_emby.remote.EmbyHandler._get_token')
@mock.patch('mopidy_emby.remote.EmbyHandler._create_headers')
@mock.patch('mopidy_emby.remote.EmbyHandler._password_data')
@mock.patch('mopidy_jellyfin.remote.requests.get')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._get_token')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._create_headers')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._password_data')
def test_get_user(password_mock, create_headers_mock, get_tocken_mock,
get_mock, data, user_id, config):
@ -224,15 +224,15 @@ def test_get_user(password_mock, create_headers_mock, get_tocken_mock,
get_mock.return_value = mock_response
emby = backend.EmbyHandler(config)
jellyfin = backend.JellyfinHandler(config)
assert emby.user_id == user_id
assert jellyfin.user_id == user_id
@mock.patch('mopidy_emby.remote.requests.get')
@mock.patch('mopidy_emby.remote.EmbyHandler._get_token')
@mock.patch('mopidy_emby.remote.EmbyHandler._create_headers')
@mock.patch('mopidy_emby.remote.EmbyHandler._password_data')
@mock.patch('mopidy_jellyfin.remote.requests.get')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._get_token')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._create_headers')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._password_data')
def test_get_user_exception(password_mock, create_headers_mock,
get_tocken_mock, get_mock, config):
@ -243,19 +243,19 @@ def test_get_user_exception(password_mock, create_headers_mock,
get_mock.return_value = mock_response
with pytest.raises(Exception) as execinfo:
backend.EmbyHandler(config)
backend.JellyfinHandler(config)
assert 'No Emby user embyuser found' in str(execinfo.value)
assert 'No Jellyfin user jellyfinuser found' in str(execinfo.value)
@pytest.mark.parametrize('data,token', [
('tests/data/get_token0.json', 'f0d6b372b40b47299ed01b9b2d40489b'),
('tests/data/get_token1.json', None),
])
@mock.patch('mopidy_emby.remote.requests.post')
@mock.patch('mopidy_emby.remote.EmbyHandler._create_headers')
@mock.patch('mopidy_emby.remote.EmbyHandler._password_data')
@mock.patch('mopidy_emby.remote.EmbyHandler._get_user')
@mock.patch('mopidy_jellyfin.remote.requests.post')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._create_headers')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._password_data')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._get_user')
def test_get_token(get_user_mock, password_data_mock,
create_headers_mock, post_mock, data,
token, config):
@ -266,22 +266,22 @@ def test_get_token(get_user_mock, password_data_mock,
post_mock.return_value = mock_response
emby = backend.EmbyHandler(config)
jellyfin = backend.JellyfinHandler(config)
assert emby.token == token
assert jellyfin.token == token
@mock.patch('mopidy_emby.remote.requests')
@mock.patch('mopidy_emby.remote.EmbyHandler._create_headers')
@mock.patch('mopidy_emby.remote.EmbyHandler._get_user')
@mock.patch('mopidy_emby.remote.EmbyHandler._get_token')
@mock.patch('mopidy_jellyfin.remote.requests')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._create_headers')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._get_user')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._get_token')
def test_password_data(get_token_mock, get_user_mock, create_headers_mock,
requests_mock, config):
emby = backend.EmbyHandler(config)
jellyfin = backend.JellyfinHandler(config)
assert emby._password_data() == {
'username': 'embyuser',
assert jellyfin._password_data() == {
'username': 'jellyfinuser',
'password': '444b73bcd9dc4331104c5ef960ee240066f8a3e5',
'passwordMd5': '1d549a7b47c46b7b0a90651360c5574c'
}
@ -291,7 +291,7 @@ def test_password_data(get_token_mock, get_user_mock, create_headers_mock,
(
None,
{
'x-emby-authorization': ('MediaBrowser UserId="123", '
'x-jellyfin-authorization': ('MediaBrowser UserId="123", '
'Client="other", Device="mopidy", '
'DeviceId="mopidy", Version="0.0.0"')
}
@ -299,26 +299,26 @@ def test_password_data(get_token_mock, get_user_mock, create_headers_mock,
(
'f0d6b372b40b47299ed01b9b2d40489b',
{
'x-emby-authorization': ('MediaBrowser UserId="123", '
'x-jellyfin-authorization': ('MediaBrowser UserId="123", '
'Client="other", Device="mopidy", '
'DeviceId="mopidy", Version="0.0.0"'),
'x-mediabrowser-token': 'f0d6b372b40b47299ed01b9b2d40489b'
}
)
])
@mock.patch('mopidy_emby.remote.requests')
@mock.patch('mopidy_emby.remote.EmbyHandler._password_data')
@mock.patch('mopidy_emby.remote.EmbyHandler._get_user')
@mock.patch('mopidy_emby.remote.EmbyHandler._get_token')
@mock.patch('mopidy_jellyfin.remote.requests')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._password_data')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._get_user')
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._get_token')
def test_create_headers(get_token_mock, get_user_mock, password_data_mock,
requests_mock, token, headers, config):
get_user_mock.return_value = [{'Id': 123}]
get_token_mock.return_value = token
emby = backend.EmbyHandler(config)
jellyfin = backend.JellyfinHandler(config)
assert emby.headers == headers
assert jellyfin.headers == headers
@pytest.mark.parametrize('query,data,expected', [
@ -335,10 +335,10 @@ def test_create_headers(get_token_mock, get_user_mock, password_data_mock,
artists=[Artist(name=u'Rainer Maria')],
name='Viva Anger, Viva Hate',
track_no=3,
uri='emby:track:b5d600663238be5b41da4d8429db85f0'
uri='jellyfin:track:b5d600663238be5b41da4d8429db85f0'
)
],
uri='emby:search'
uri='jellyfin:search'
)
),
(
@ -349,9 +349,9 @@ def test_create_headers(get_token_mock, get_user_mock, password_data_mock,
Album(
artists=[Artist(name=u'Morrissey')],
name=u'Viva Hate',
uri='emby:album:4bf594cb601ec46a0295729c4d0f7f80')
uri='jellyfin:album:4bf594cb601ec46a0295729c4d0f7f80')
],
uri='emby:search'
uri='jellyfin:search'
)
),
(
@ -361,68 +361,68 @@ def test_create_headers(get_token_mock, get_user_mock, password_data_mock,
artists=[
Artist(
name=u'Morrissey',
uri='emby:artist:0b74a057d86092f48698be681737c4ed'
uri='jellyfin:artist:0b74a057d86092f48698be681737c4ed'
),
Artist(
name=u'Morrissey & Siouxsie Sioux',
uri='emby:artist:32bbd3db105255b24a83d0d955179dc4'
uri='jellyfin:artist:32bbd3db105255b24a83d0d955179dc4'
),
Artist(
name=u'Morrissey & Siouxsie Sioux',
uri='emby:artist:eb69a3f2db13691d24c6a9794926ddb8'
uri='jellyfin:artist:eb69a3f2db13691d24c6a9794926ddb8'
)
],
uri='emby:search'
uri='jellyfin:search'
)
)
])
@mock.patch('mopidy_emby.backend.EmbyHandler._get_search')
def test_search(get_search_mock, query, data, expected, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler._get_search')
def test_search(get_search_mock, query, data, expected, jellyfin_client):
with open(data, 'r') as f:
get_search_mock.return_value = json.load(f)['SearchHints']
assert emby_client.search(query) == expected
assert jellyfin_client.search(query) == expected
@mock.patch('mopidy_emby.backend.EmbyHandler._get_session')
def test_r_get(session_mock, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler._get_session')
def test_r_get(session_mock, jellyfin_client):
data = {'foo': 'bar'}
session_mock.return_value.get.return_value.json.return_value = data
assert emby_client.r_get('http://foo.bar') == data
assert jellyfin_client.r_get('http://foo.bar') == data
@mock.patch('mopidy_emby.remote.EmbyHandler._get_session')
def test_r_get_exception(session_mock, emby_client):
@mock.patch('mopidy_jellyfin.remote.JellyfinHandler._get_session')
def test_r_get_exception(session_mock, jellyfin_client):
session_mock.return_value.get.side_effect = Exception()
with pytest.raises(Exception) as execinfo:
emby_client.r_get('http://foo.bar')
jellyfin_client.r_get('http://foo.bar')
assert 'Cant connect to Emby API' in str(execinfo.value)
assert 'Cant connect to Jellyfin API' in str(execinfo.value)
@pytest.mark.parametrize('ticks,milliseconds', [
(2010380000, 201038),
(2508020000, 250802),
])
def test_ticks_to_milliseconds(ticks, milliseconds, emby_client):
assert emby_client.ticks_to_milliseconds(ticks) == milliseconds
def test_ticks_to_milliseconds(ticks, milliseconds, jellyfin_client):
assert jellyfin_client.ticks_to_milliseconds(ticks) == milliseconds
@pytest.mark.parametrize('milliseconds,ticks', [
(201038, 2010380000),
(250802, 2508020000),
])
def test_milliseconds_to_ticks(milliseconds, ticks, emby_client):
assert emby_client.milliseconds_to_ticks(milliseconds) == ticks
def test_milliseconds_to_ticks(milliseconds, ticks, jellyfin_client):
assert jellyfin_client.milliseconds_to_ticks(milliseconds) == ticks
def test__get_session(emby_client):
assert isinstance(emby_client._get_session(), requests.sessions.Session)
def test__get_session(jellyfin_client):
assert isinstance(jellyfin_client._get_session(), requests.sessions.Session)
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
@pytest.mark.parametrize('data,expected', [
(
'tests/data/track0.json',
@ -432,17 +432,17 @@ def test__get_session(emby_client):
]
)
])
def test_get_item(r_get_mock, data, expected, emby_client):
def test_get_item(r_get_mock, data, expected, jellyfin_client):
with open(data, 'r') as f:
r_get_mock.return_value = json.load(f)
item_keys = emby_client.get_item(0).keys()
item_keys = jellyfin_client.get_item(0).keys()
for key in expected:
assert key in item_keys
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
@pytest.mark.parametrize('data,expected', [
(
'tests/data/track0.json',
@ -452,15 +452,15 @@ def test_get_item(r_get_mock, data, expected, emby_client):
length=295915,
name=u'Ottawa to Osaka',
track_no=6,
uri='emby:track:18e5a9871e6a4a2294d5af998457ca16'
uri='jellyfin:track:18e5a9871e6a4a2294d5af998457ca16'
)
)
])
def test_get_track(r_get_mock, data, expected, emby_client):
def test_get_track(r_get_mock, data, expected, jellyfin_client):
with open(data, 'r') as f:
r_get_mock.return_value = json.load(f)
assert emby_client.get_track(0) == expected
assert jellyfin_client.get_track(0) == expected
@pytest.mark.parametrize('itemtype,url', [
@ -485,31 +485,31 @@ def test_get_track(r_get_mock, data, expected, emby_client):
'&IncludeItemTypes=Audio')
),
])
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
@mock.patch('mopidy_emby.backend.EmbyHandler.api_url')
def test__get_search(api_url_mock, r_get_mock, itemtype, url, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.api_url')
def test__get_search(api_url_mock, r_get_mock, itemtype, url, jellyfin_client):
with open('tests/data/search_audio0.json', 'r') as f:
r_get_mock.return_value = json.load(f)
emby_client._get_search(itemtype, 'viva hate')
jellyfin_client._get_search(itemtype, 'viva hate')
api_url_mock.assert_called_with(url)
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
def test__get_search_exception(r_get_mock, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
def test__get_search_exception(r_get_mock, jellyfin_client):
with pytest.raises(Exception) as execinfo:
emby_client._get_search('foo', 'bar')
jellyfin_client._get_search('foo', 'bar')
assert 'Emby search: no itemtype foo' in str(execinfo.value)
assert 'Jellyfin search: no itemtype foo' in str(execinfo.value)
@mock.patch('mopidy_emby.backend.EmbyHandler.r_get')
def test_lookup_artist(r_get_mock, emby_client):
@mock.patch('mopidy_jellyfin.backend.JellyfinHandler.r_get')
def test_lookup_artist(r_get_mock, jellyfin_client):
with open('tests/data/lookup_artist0.json', 'r') as f:
r_get_mock.return_value = json.load(f)
assert emby_client.lookup_artist(0) == [
assert jellyfin_client.lookup_artist(0) == [
Track(
album=Album(
artists=[Artist(name=u'Jawbreaker')],
@ -523,7 +523,7 @@ def test_lookup_artist(r_get_mock, emby_client):
length=159840,
name=u'The Boat Dreams From The Hill',
track_no=1,
uri='emby:track:05321ccb30ff9e43bf8070cd5f70c783'
uri='jellyfin:track:05321ccb30ff9e43bf8070cd5f70c783'
),
Track(
album=Album(
@ -538,7 +538,7 @@ def test_lookup_artist(r_get_mock, emby_client):
length=131133,
name=u'Bad Scene, Everyone\u2019s Fault',
track_no=10,
uri='emby:track:0a24ce6c243f2f3a81fa0f99625630b4'
uri='jellyfin:track:0a24ce6c243f2f3a81fa0f99625630b4'
),
Track(
album=Album(
@ -553,6 +553,6 @@ def test_lookup_artist(r_get_mock, emby_client):
length=254107,
name=u'Sluttering (May 4th)',
track_no=11,
uri='emby:track:057801bc10cf08ce96e1e19bf98c407f'
uri='jellyfin:track:057801bc10cf08ce96e1e19bf98c407f'
)
]

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from mock import Mock
from mopidy_emby import utils
from mopidy_jellyfin import utils
def test_decorator():