Merge branch 'develop' into feature/local-uris
This commit is contained in:
commit
0ce791f215
2
.mailmap
2
.mailmap
@ -5,3 +5,5 @@ Kristian Klette <klette@samfundet.no>
|
||||
Johannes Knutsen <johannes@knutseninfo.no> <johannes@iterate.no>
|
||||
Johannes Knutsen <johannes@knutseninfo.no> <johannes@barbarmaclin.(none)>
|
||||
John Bäckstrand <sopues@gmail.com> <sandos@XBMCLive.(none)>
|
||||
Alexandre Petitjean <alpetitjean@gmail.com>
|
||||
Alexandre Petitjean <alpetitjean@gmail.com> <alexandre.petitjean@lne.fr>
|
||||
|
||||
1
AUTHORS
1
AUTHORS
@ -22,3 +22,4 @@
|
||||
- Janez Troha <janez.troha@gmail.com>
|
||||
- Tobias Sauerwein <cgtobi@gmail.com>
|
||||
- alzeih <alzeih@gmail.com>
|
||||
- Alexandre Petitjean <alpetitjean@gmail.com>
|
||||
|
||||
@ -22,6 +22,11 @@ v0.15.0 (UNRELEASED)
|
||||
- :option:`mopidy --show-config` will now take into consideration any
|
||||
:option:`mopidy --option` arguments appearing later on the command line.
|
||||
|
||||
**Audio**
|
||||
|
||||
- Added support for viusalization. :confval:`audio/visualizer` can now be set
|
||||
to GStreamer visualizers.
|
||||
|
||||
**Local backend**
|
||||
|
||||
- An album's number of discs and a track's disc number are now extracted when
|
||||
@ -33,6 +38,9 @@ v0.15.0 (UNRELEASED)
|
||||
hierarchy from your Spotify account is available in Mopidy. (Fixes:
|
||||
:issue:`62`)
|
||||
|
||||
- Fix proxy config values that was broken with the config system change in
|
||||
0.14. (Fixes: :issue:`472`)
|
||||
|
||||
|
||||
v0.14.2 (2013-07-01)
|
||||
====================
|
||||
|
||||
@ -101,7 +101,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'Mopidy'
|
||||
copyright = '2010-2013, Stein Magnus Jodal and contributors'
|
||||
copyright = '2009-2013, Stein Magnus Jodal and contributors'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
||||
@ -90,6 +90,16 @@ Core configuration values
|
||||
``gst-inspect-0.10`` to see what output properties can be set on the sink.
|
||||
For example: ``gst-inspect-0.10 shout2send``
|
||||
|
||||
.. confval:: audio/visualizer
|
||||
|
||||
Visualizer to use.
|
||||
|
||||
Can be left blank if no visualizer is desired. Otherwise this expects a
|
||||
GStreamer visualizer. Typical values are ``monoscope``, ``goom``,
|
||||
``goom2k1`` or one of the `libvisual`_ visualizers.
|
||||
|
||||
.. _libvisual: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-plugin-libvisual.html
|
||||
|
||||
.. confval:: logging/console_format
|
||||
|
||||
The log format used for informational logging.
|
||||
|
||||
@ -48,7 +48,7 @@ About
|
||||
:maxdepth: 1
|
||||
|
||||
authors
|
||||
licenses
|
||||
license
|
||||
changelog
|
||||
versioning
|
||||
|
||||
|
||||
10
docs/license.rst
Normal file
10
docs/license.rst
Normal file
@ -0,0 +1,10 @@
|
||||
*******
|
||||
License
|
||||
*******
|
||||
|
||||
Mopidy is copyright 2009-2013 Stein Magnus Jodal and contributors. For a list
|
||||
of contributors, see :doc:`authors`. For details on who have contributed what,
|
||||
please refer to our git repository.
|
||||
|
||||
Mopidy is licensed under the `Apache License, Version 2.0
|
||||
<http://www.apache.org/licenses/LICENSE-2.0>`_.
|
||||
@ -1,34 +0,0 @@
|
||||
********
|
||||
Licenses
|
||||
********
|
||||
|
||||
For a list of contributors, see :doc:`authors`. For details on who have
|
||||
contributed what, please refer to our git repository.
|
||||
|
||||
Source code license
|
||||
===================
|
||||
|
||||
Copyright 2009-2013 Stein Magnus Jodal and contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
Documentation license
|
||||
=====================
|
||||
|
||||
Copyright 2010-2013 Stein Magnus Jodal and contributors
|
||||
|
||||
This work is licensed under the Creative Commons Attribution-ShareAlike 3.0
|
||||
Unported License. To view a copy of this license, visit
|
||||
http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative
|
||||
Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
|
||||
12
js/README.md
12
js/README.md
@ -51,15 +51,15 @@ Building from source
|
||||
1. Install [Node.js](http://nodejs.org/) and npm. There is a PPA if you're
|
||||
running Ubuntu:
|
||||
|
||||
sudo apt-get install python-software-properties
|
||||
sudo add-apt-repository ppa:chris-lea/node.js
|
||||
sudo apt-get update
|
||||
sudo apt-get install nodejs npm
|
||||
sudo apt-get install python-software-properties
|
||||
sudo add-apt-repository ppa:chris-lea/node.js
|
||||
sudo apt-get update
|
||||
sudo apt-get install nodejs
|
||||
|
||||
2. Enter the `js/` in Mopidy's Git repo dir and install all dependencies:
|
||||
|
||||
cd js/
|
||||
npm install
|
||||
cd js/
|
||||
npm install
|
||||
|
||||
That's it.
|
||||
|
||||
|
||||
@ -22,6 +22,22 @@ mixers.register_mixers()
|
||||
|
||||
MB = 1 << 20
|
||||
|
||||
# GST_PLAY_FLAG_VIDEO (1<<0)
|
||||
# GST_PLAY_FLAG_AUDIO (1<<1)
|
||||
# GST_PLAY_FLAG_TEXT (1<<2)
|
||||
# GST_PLAY_FLAG_VIS (1<<3)
|
||||
# GST_PLAY_FLAG_SOFT_VOLUME (1<<4)
|
||||
# GST_PLAY_FLAG_NATIVE_AUDIO (1<<5)
|
||||
# GST_PLAY_FLAG_NATIVE_VIDEO (1<<6)
|
||||
# GST_PLAY_FLAG_DOWNLOAD (1<<7)
|
||||
# GST_PLAY_FLAG_BUFFERING (1<<8)
|
||||
# GST_PLAY_FLAG_DEINTERLACE (1<<9)
|
||||
# GST_PLAY_FLAG_SOFT_COLORBALANCE (1<<10)
|
||||
|
||||
# Default flags to use for playbin: AUDIO, SOFT_VOLUME, DOWNLOAD
|
||||
PLAYBIN_FLAGS = (1<<1) | (1<<4) | (1<<7)
|
||||
PLAYBIN_VIS_FLAGS = PLAYBIN_FLAGS | (1<<3)
|
||||
|
||||
|
||||
class Audio(pykka.ThreadingActor):
|
||||
"""
|
||||
@ -55,6 +71,7 @@ class Audio(pykka.ThreadingActor):
|
||||
try:
|
||||
self._setup_playbin()
|
||||
self._setup_output()
|
||||
self._setup_visualizer()
|
||||
self._setup_mixer()
|
||||
self._setup_message_processor()
|
||||
except gobject.GError as ex:
|
||||
@ -78,9 +95,7 @@ class Audio(pykka.ThreadingActor):
|
||||
|
||||
def _setup_playbin(self):
|
||||
playbin = gst.element_factory_make('playbin2')
|
||||
|
||||
fakesink = gst.element_factory_make('fakesink')
|
||||
playbin.set_property('video-sink', fakesink)
|
||||
playbin.set_property('flags', PLAYBIN_FLAGS)
|
||||
|
||||
self._connect(playbin, 'about-to-finish', self._on_about_to_finish)
|
||||
self._connect(playbin, 'notify::source', self._on_new_source)
|
||||
@ -149,6 +164,19 @@ class Audio(pykka.ThreadingActor):
|
||||
'Failed to create audio output "%s": %s', output_desc, ex)
|
||||
process.exit_process()
|
||||
|
||||
def _setup_visualizer(self):
|
||||
visualizer_element = self._config['audio']['visualizer']
|
||||
if not visualizer_element:
|
||||
return
|
||||
try:
|
||||
visualizer = gst.element_factory_make(visualizer_element)
|
||||
self._playbin.set_property('vis-plugin', visualizer)
|
||||
self._playbin.set_property('flags', PLAYBIN_VIS_FLAGS)
|
||||
logger.info('Audio visualizer set to "%s"', visualizer_element)
|
||||
except gobject.GError as ex:
|
||||
logger.error(
|
||||
'Failed to create audio visualizer "%s": %s', visualizer_element, ex)
|
||||
|
||||
def _setup_mixer(self):
|
||||
mixer_desc = self._config['audio']['mixer']
|
||||
track_desc = self._config['audio']['mixer_track']
|
||||
|
||||
@ -33,9 +33,17 @@ class SpotifySessionManager(process.BaseThread, PyspotifySessionManager):
|
||||
self.cache_location = config['spotify']['cache_dir']
|
||||
self.settings_location = config['spotify']['cache_dir']
|
||||
|
||||
full_proxy = ''
|
||||
if config['proxy']['hostname']:
|
||||
full_proxy = config['proxy']['hostname']
|
||||
if config['proxy']['port']:
|
||||
full_proxy += ':' + str(config['proxy']['port'])
|
||||
if config['proxy']['scheme']:
|
||||
full_proxy = config['proxy']['scheme'] + "://" + full_proxy
|
||||
|
||||
PyspotifySessionManager.__init__(
|
||||
self, config['spotify']['username'], config['spotify']['password'],
|
||||
proxy=config['proxy']['hostname'],
|
||||
proxy=full_proxy,
|
||||
proxy_username=config['proxy']['username'],
|
||||
proxy_password=config['proxy']['password'])
|
||||
|
||||
|
||||
@ -24,9 +24,13 @@ _audio_schema = ConfigSchema('audio')
|
||||
_audio_schema['mixer'] = String()
|
||||
_audio_schema['mixer_track'] = String(optional=True)
|
||||
_audio_schema['output'] = String()
|
||||
_audio_schema['visualizer'] = String(optional=True)
|
||||
|
||||
_proxy_schema = ConfigSchema('proxy')
|
||||
_proxy_schema['scheme'] = String(optional=True,
|
||||
choices=['http', 'https', 'socks4', 'socks5'])
|
||||
_proxy_schema['hostname'] = Hostname(optional=True)
|
||||
_proxy_schema['port'] = Port(optional=True)
|
||||
_proxy_schema['username'] = String(optional=True)
|
||||
_proxy_schema['password'] = Secret(optional=True)
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ def convert(settings):
|
||||
helper('audio/output', 'OUTPUT')
|
||||
|
||||
helper('proxy/hostname', 'SPOTIFY_PROXY_HOST')
|
||||
helper('proxy/port', 'SPOTIFY_PROXY_PORT')
|
||||
helper('proxy/username', 'SPOTIFY_PROXY_USERNAME')
|
||||
helper('proxy/password', 'SPOTIFY_PROXY_PASSWORD')
|
||||
|
||||
|
||||
@ -11,8 +11,11 @@ pykka = info
|
||||
mixer = autoaudiomixer
|
||||
mixer_track =
|
||||
output = autoaudiosink
|
||||
visualizer =
|
||||
|
||||
[proxy]
|
||||
scheme =
|
||||
hostname =
|
||||
port =
|
||||
username =
|
||||
password =
|
||||
|
||||
@ -71,9 +71,9 @@ class String(ConfigValue):
|
||||
def deserialize(self, value):
|
||||
value = decode(value).strip()
|
||||
validators.validate_required(value, self._required)
|
||||
validators.validate_choice(value, self._choices)
|
||||
if not value:
|
||||
return None
|
||||
validators.validate_choice(value, self._choices)
|
||||
return value
|
||||
|
||||
def serialize(self, value, display=False):
|
||||
@ -111,12 +111,16 @@ class Secret(ConfigValue):
|
||||
class Integer(ConfigValue):
|
||||
"""Integer value."""
|
||||
|
||||
def __init__(self, minimum=None, maximum=None, choices=None):
|
||||
def __init__(self, minimum=None, maximum=None, choices=None, optional=False):
|
||||
self._required = not optional
|
||||
self._minimum = minimum
|
||||
self._maximum = maximum
|
||||
self._choices = choices
|
||||
|
||||
def deserialize(self, value):
|
||||
validators.validate_required(value, self._required)
|
||||
if not value:
|
||||
return None
|
||||
value = int(value)
|
||||
validators.validate_choice(value, self._choices)
|
||||
validators.validate_minimum(value, self._minimum)
|
||||
@ -222,9 +226,9 @@ class Port(Integer):
|
||||
allocate a port for us.
|
||||
"""
|
||||
# TODO: consider probing if port is free or not?
|
||||
def __init__(self, choices=None):
|
||||
def __init__(self, choices=None, optional=False):
|
||||
super(Port, self).__init__(
|
||||
minimum=0, maximum=2 ** 16 - 1, choices=choices)
|
||||
minimum=0, maximum=2 ** 16 - 1, choices=choices, optional=optional)
|
||||
|
||||
|
||||
class Path(ConfigValue):
|
||||
|
||||
@ -21,6 +21,7 @@ class AudioTest(unittest.TestCase):
|
||||
'mixer': 'fakemixer track_max_volume=65536',
|
||||
'mixer_track': None,
|
||||
'output': 'fakesink',
|
||||
'visualizer': None,
|
||||
}
|
||||
}
|
||||
self.song_uri = path_to_uri(path_to_data_dir('song1.wav'))
|
||||
@ -70,6 +71,7 @@ class AudioTest(unittest.TestCase):
|
||||
'mixer': 'fakemixer track_max_volume=40',
|
||||
'mixer_track': None,
|
||||
'output': 'fakesink',
|
||||
'visualizer': None,
|
||||
}
|
||||
}
|
||||
self.audio = audio.Audio.start(config=config).proxy()
|
||||
|
||||
@ -98,6 +98,11 @@ class StringTest(unittest.TestCase):
|
||||
self.assertIsInstance(result, bytes)
|
||||
self.assertEqual(b'', result)
|
||||
|
||||
def test_deserialize_enforces_choices_optional(self):
|
||||
value = types.String(optional=True, choices=['foo', 'bar', 'baz'])
|
||||
self.assertEqual(None, value.deserialize(b''))
|
||||
self.assertRaises(ValueError, value.deserialize, b'foobar')
|
||||
|
||||
|
||||
class SecretTest(unittest.TestCase):
|
||||
def test_deserialize_passes_through(self):
|
||||
@ -163,6 +168,10 @@ class IntegerTest(unittest.TestCase):
|
||||
self.assertEqual(5, value.deserialize('5'))
|
||||
self.assertRaises(ValueError, value.deserialize, '15')
|
||||
|
||||
def test_deserialize_respects_optional(self):
|
||||
value = types.Integer(optional=True)
|
||||
self.assertEqual(None, value.deserialize(''))
|
||||
|
||||
|
||||
class BooleanTest(unittest.TestCase):
|
||||
def test_deserialize_conversion_success(self):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user