From 4bbd847a6a508b5bba7668e3847f2e3c721401c5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 26 Mar 2016 21:27:03 +0100 Subject: [PATCH 01/12] audio: Always install audio element --- mopidy/audio/actor.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 267b228d..1fb4fec6 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -22,7 +22,6 @@ logger = logging.getLogger(__name__) gst_logger = logging.getLogger('mopidy.audio.gst') _GST_PLAY_FLAGS_AUDIO = 0x02 -_GST_PLAY_FLAGS_SOFT_VOLUME = 0x10 _GST_STATE_MAPPING = { Gst.State.PLAYING: PlaybackState.PLAYING, @@ -451,8 +450,7 @@ class Audio(pykka.ThreadingActor): def _setup_playbin(self): playbin = Gst.ElementFactory.make('playbin') - playbin.set_property( - 'flags', _GST_PLAY_FLAGS_AUDIO | _GST_PLAY_FLAGS_SOFT_VOLUME) + playbin.set_property('flags', _GST_PLAY_FLAGS_AUDIO) # TODO: turn into config values... playbin.set_property('buffer-size', 5 << 20) # 5MB @@ -489,15 +487,16 @@ class Audio(pykka.ThreadingActor): def _setup_audio_sink(self): audio_sink = Gst.ElementFactory.make('bin', 'audio-sink') + queue = Gst.ElementFactory.make('queue') + volume = Gst.ElementFactory.make('volume') # Queue element to buy us time between the about-to-finish event and # the actual switch, i.e. about to switch can block for longer thanks # to this queue. + # TODO: See if settings should be set to minimize latency. Previous # setting breaks appsrc, and settings before that broke on a few # systems. So leave the default to play it safe. - queue = Gst.ElementFactory.make('queue') - if self._config['audio']['buffer_time'] > 0: queue.set_property( 'max-size-time', @@ -505,15 +504,13 @@ class Audio(pykka.ThreadingActor): audio_sink.add(queue) audio_sink.add(self._outputs) + audio_sink.add(volume) + + queue.link(volume) + volume.link(self._outputs) if self.mixer: - volume = Gst.ElementFactory.make('volume') - audio_sink.add(volume) - queue.link(volume) - volume.link(self._outputs) self.mixer.setup(volume, self.actor_ref.proxy().mixer) - else: - queue.link(self._outputs) ghost_pad = Gst.GhostPad.new('sink', queue.get_static_pad('sink')) audio_sink.add_pad(ghost_pad) From fb823d1a7c396ec6b0bda4070c92cb0b7b3e1ac3 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2016 21:09:38 +0200 Subject: [PATCH 02/12] lint: Workaround and fix to account for new version of flake8 --- mopidy/__main__.py | 2 +- mopidy/config/__init__.py | 7 +++++-- tests/models/test_fields.py | 2 +- tox.ini | 3 ++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 86a0c19c..7963900e 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -5,7 +5,7 @@ import os import signal import sys -from mopidy.internal.gi import Gst # noqa: Import to initialize +from mopidy.internal.gi import Gst # noqa: F401 try: # Make GObject's mainloop the event loop for python-dbus diff --git a/mopidy/config/__init__.py b/mopidy/config/__init__.py index 21a6a00b..ec5c9a99 100644 --- a/mopidy/config/__init__.py +++ b/mopidy/config/__init__.py @@ -9,12 +9,15 @@ import re from mopidy import compat from mopidy.compat import configparser from mopidy.config import keyring -from mopidy.config.schemas import * # noqa -from mopidy.config.types import * # noqa +from mopidy.config.schemas import * +from mopidy.config.types import * from mopidy.internal import path, versioning logger = logging.getLogger(__name__) +# flake8: noqa: +# TODO: Update this to be flake8 compliant + _core_schema = ConfigSchema('core') _core_schema['cache_dir'] = Path() _core_schema['config_dir'] = Path() diff --git a/tests/models/test_fields.py b/tests/models/test_fields.py index bf842fd5..3374c822 100644 --- a/tests/models/test_fields.py +++ b/tests/models/test_fields.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals import unittest -from mopidy.models.fields import * # noqa: F403 +from mopidy.models.fields import Collection, Field, Integer, String def create_instance(field): diff --git a/tox.ini b/tox.ini index da6bcc38..b39fc68b 100644 --- a/tox.ini +++ b/tox.ini @@ -37,7 +37,8 @@ commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html [testenv:flake8] deps = flake8 - flake8-import-order +# TODO: Re-enable once https://github.com/PyCQA/flake8-import-order/issues/79 is released. +# flake8-import-order pep8-naming commands = flake8 --show-source --statistics mopidy tests From 3ee91240a0c4bb4403ef1c636a12cd96300b40d7 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 13 Jun 2016 22:44:03 +0200 Subject: [PATCH 03/12] Merge pull request #1496 from dublok/fix/1462-flac-seek-freeze audio: Ignore position of _on_position_changed callback (fixes #1462) --- mopidy/core/playback.py | 4 ++-- tests/core/test_playback.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index ab96171e..da505b22 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -230,8 +230,8 @@ class PlaybackController(object): self._seek(self._pending_position) def _on_position_changed(self, position): - if self._pending_position == position: - self._trigger_seeked(position) + if self._pending_position is not None: + self._trigger_seeked(self._pending_position) self._pending_position = None def _on_about_to_finish_callback(self): diff --git a/tests/core/test_playback.py b/tests/core/test_playback.py index 3572800c..34c9d367 100644 --- a/tests/core/test_playback.py +++ b/tests/core/test_playback.py @@ -734,6 +734,7 @@ class EventEmissionTest(BaseTest): self.core.playback.play(tl_tracks[0]) self.trigger_about_to_finish(replay_until='stream_changed') + self.replay_events() listener_mock.reset_mock() self.core.playback.seek(1000) From 5e2b44ab44afd2e4db0387fae5e36578df7ef6d9 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 13 Jun 2016 22:12:56 +0200 Subject: [PATCH 04/12] Merge pull request #1522 from SeppSTA/fix/1521-download-timeout-sec fix/1521 download timeout sec --- mopidy/stream/actor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/stream/actor.py b/mopidy/stream/actor.py index 0861b5b0..1bdd05ca 100644 --- a/mopidy/stream/actor.py +++ b/mopidy/stream/actor.py @@ -68,7 +68,7 @@ class StreamLibraryProvider(backend.LibraryProvider): track = tags.convert_tags_to_track(scan_result.tags).replace( uri=uri, length=scan_result.duration) else: - logger.warning('Problem looking up %s: %s', uri) + logger.warning('Problem looking up %s', uri) track = Track(uri=uri) return [track] @@ -142,7 +142,7 @@ def _unwrap_stream(uri, timeout, scanner, requests_session): uri, timeout) return None, None content = http.download( - requests_session, uri, timeout=download_timeout) + requests_session, uri, timeout=download_timeout / 1000) if content is None: logger.info( From 0c6be281dfd01df80f0d4a95f925dace19b31f7c Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2016 21:47:10 +0200 Subject: [PATCH 05/12] doc: Add changelog entry for PR#1496 and PR#1522 --- docs/changelog.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8830edd3..60255266 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -23,6 +23,9 @@ Bug fix release. (Fixes: :issue:`935`, :issue:`1453`, :issue:`1474` and :issue:`1480`, PR: :issue:`1487`) +- Audio: Better handling of seek when position does not match the expected + pending position. (Fixes: :issue:`1462` PR: :issue:`1496`) + - Core: Avoid endless loop if all tracks in the tracklist are unplayable and consume mode is off. (Fixes: :issue:`1221`, :issue:`1454`, PR: :issue:`1455`) @@ -30,6 +33,9 @@ Bug fix release. where a :confval:`file/media_dirs` path contained non-ASCII characters. (Fixes: :issue:`1345`, PR: :issue:`1493`) +- Stream: Fix milliseconds vs seconds mistake in timeout handling. + (Fixes: :issue:`1521` PR: :issue:`1522`) + v2.0.0 (2016-02-15) =================== From 692138a51e146d1b25a8d0db4ad057773406c6c5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2016 22:00:51 +0200 Subject: [PATCH 06/12] Merge pull request #1525 from palfrey/dodgy-date-tags If the date in a tag is invalid, skip it --- mopidy/audio/tags.py | 13 +++++++++---- tests/audio/test_tags.py | 8 ++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/mopidy/audio/tags.py b/mopidy/audio/tags.py index 5ae86468..7fabefd6 100644 --- a/mopidy/audio/tags.py +++ b/mopidy/audio/tags.py @@ -44,10 +44,15 @@ gstreamer-GstTagList.html value = taglist.get_value_index(tag, i) if isinstance(value, GLib.Date): - date = datetime.date( - value.get_year(), value.get_month(), value.get_day()) - result[tag].append(date.isoformat().decode('utf-8')) - if isinstance(value, Gst.DateTime): + try: + date = datetime.date( + value.get_year(), value.get_month(), value.get_day()) + result[tag].append(date.isoformat().decode('utf-8')) + except ValueError: + logger.debug( + 'Ignoring dodgy date value: %d-%d-%d', + value.get_year(), value.get_month(), value.get_day()) + elif isinstance(value, Gst.DateTime): result[tag].append(value.to_iso8601_string().decode('utf-8')) elif isinstance(value, bytes): result[tag].append(value.decode('utf-8', 'replace')) diff --git a/tests/audio/test_tags.py b/tests/audio/test_tags.py index 01475124..d85bcc12 100644 --- a/tests/audio/test_tags.py +++ b/tests/audio/test_tags.py @@ -44,6 +44,14 @@ class TestConvertTaglist(object): assert isinstance(result[Gst.TAG_DATE][0], compat.text_type) assert result[Gst.TAG_DATE][0] == '2014-01-07' + def test_date_tag_bad_value(self): + date = GLib.Date.new_dmy(7, 1, 10000) + taglist = self.make_taglist(Gst.TAG_DATE, [date]) + + result = tags.convert_taglist(taglist) + + assert len(result[Gst.TAG_DATE]) == 0 + def test_date_time_tag(self): taglist = self.make_taglist(Gst.TAG_DATE_TIME, [ Gst.DateTime.new_from_iso8601_string(b'2014-01-07 14:13:12') From 44ff6697444574da3ad8035c1dc5eaaa4b34bfd1 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2016 22:05:34 +0200 Subject: [PATCH 07/12] Merge pull request #1534 from edran/fix-scrobbling Get correct track position on change events --- docs/changelog.rst | 4 ++++ mopidy/core/playback.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 60255266..8ed0bbb9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -29,6 +29,10 @@ Bug fix release. - Core: Avoid endless loop if all tracks in the tracklist are unplayable and consume mode is off. (Fixes: :issue:`1221`, :issue:`1454`, PR: :issue:`1455`) +- Core: Correctly record the last position of a track when switching to another + one. Particularly relevant for `mopidy-scrobbler` users, as before it was + essentially unusable. (Fixes: :issue:`1456`, PR: :issue:`1534`) + - File: Ensure path comparision is done between bytestrings only. Fixes crash where a :confval:`file/media_dirs` path contained non-ASCII characters. (Fixes: :issue:`1345`, PR: :issue:`1493`) diff --git a/mopidy/core/playback.py b/mopidy/core/playback.py index da505b22..0106abf2 100644 --- a/mopidy/core/playback.py +++ b/mopidy/core/playback.py @@ -251,6 +251,12 @@ class PlaybackController(object): if self._state == PlaybackState.STOPPED: return + # Unless overridden by other calls (e.g. next / previous / stop) this + # will be the last position recorded until the track gets reassigned. + # TODO: Check if case when track.length isn't populated needs to be + # handled. + self._last_position = self._current_tl_track.track.length + pending = self.core.tracklist.eot_track(self._current_tl_track) # avoid endless loop if 'repeat' is 'true' and no track is playable # * 2 -> second run to get all playable track in a shuffled playlist @@ -394,6 +400,10 @@ class PlaybackController(object): if not backend: return False + # This must happen before prepare_change gets called, otherwise the + # backend flushes the information of the track. + self._last_position = self.get_time_position() + # TODO: Wrap backend call in error handling. backend.playback.prepare_change() From c1679964ff2a5062ddc637cb3add9d3938cc05b1 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2016 22:09:52 +0200 Subject: [PATCH 08/12] docs: Add PR#1525 and PR#1517 to changelog --- docs/changelog.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8ed0bbb9..e63204c5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -24,7 +24,11 @@ Bug fix release. :issue:`1487`) - Audio: Better handling of seek when position does not match the expected - pending position. (Fixes: :issue:`1462` PR: :issue:`1496`) + pending position. (Fixes: :issue:`1462`, PR: :issue:`1496`) + +- Audio: Handle bad date tags from audio, thanks to Mario Lang and Tom Parker + who fixed this in parallel. (Fixes: :issue:`1506`, PR: :issue:`1525`, + :issue:`1517`) - Core: Avoid endless loop if all tracks in the tracklist are unplayable and consume mode is off. (Fixes: :issue:`1221`, :issue:`1454`, PR: :issue:`1455`) @@ -38,7 +42,7 @@ Bug fix release. (Fixes: :issue:`1345`, PR: :issue:`1493`) - Stream: Fix milliseconds vs seconds mistake in timeout handling. - (Fixes: :issue:`1521` PR: :issue:`1522`) + (Fixes: :issue:`1521`, PR: :issue:`1522`) v2.0.0 (2016-02-15) From 242a771062ab4da277096cdd94f73e1aab118ced Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2016 23:48:53 +0200 Subject: [PATCH 09/12] docs: Add sphinx_rtd_theme in docs/requirements.txt --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index c75793d9..62c7e3e5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ Sphinx >= 1.0 +sphinx_rtd_theme pygraphviz From ad4225d38d717d1c1e88e4ca517419290995c914 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 26 Jul 2016 21:16:31 +0200 Subject: [PATCH 10/12] audio: Make scanner handle media with not duration (Fixes: #1526) --- docs/changelog.rst | 3 +++ mopidy/audio/scan.py | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e63204c5..c69514da 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -30,6 +30,9 @@ Bug fix release. who fixed this in parallel. (Fixes: :issue:`1506`, PR: :issue:`1525`, :issue:`1517`) +- Audio: Make sure scanner handles streams without a duration. + (Fixes: :issue:`1526`) + - Core: Avoid endless loop if all tracks in the tracklist are unplayable and consume mode is off. (Fixes: :issue:`1221`, :issue:`1454`, PR: :issue:`1455`) diff --git a/mopidy/audio/scan.py b/mopidy/audio/scan.py index 27888638..f99c4489 100644 --- a/mopidy/audio/scan.py +++ b/mopidy/audio/scan.py @@ -135,6 +135,17 @@ def _start_pipeline(pipeline): pipeline.set_state(Gst.State.PLAYING) +def _query_duration(pipeline): + success, duration = pipeline.query_duration(Gst.Format.TIME) + if not success: + duration = None # Make sure error case preserves None. + elif duration < 0: + duration = None # Stream without duration. + else: + duration = duration // Gst.MSECOND + return success, duration + + def _query_seekable(pipeline): query = Gst.Query.new_seeking(Gst.Format.TIME) pipeline.query(query) @@ -187,13 +198,8 @@ def _process(pipeline, timeout_ms): elif message.type == Gst.MessageType.EOS: return tags, mime, have_audio, duration elif message.type == Gst.MessageType.ASYNC_DONE: - success, duration = pipeline.query_duration(Gst.Format.TIME) - if success: - duration = duration // Gst.MSECOND - else: - duration = None - - if tags and duration is not None: + success, duration = _query_duration(pipeline) + if tags and success: return tags, mime, have_audio, duration # Workaround for upstream bug which causes tags/duration to arrive From e594d560ff66b9795fb744e64d3ddd378d37b148 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 26 Jul 2016 21:33:16 +0200 Subject: [PATCH 11/12] audio: Make sure tags are never none (Fixes #1449) --- docs/changelog.rst | 2 ++ mopidy/audio/actor.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index c69514da..e82b2d06 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -33,6 +33,8 @@ Bug fix release. - Audio: Make sure scanner handles streams without a duration. (Fixes: :issue:`1526`) +- Audio: Ensure audio tags are never `None`. (Fixes: :issue:`1449`) + - Core: Avoid endless loop if all tracks in the tracklist are unplayable and consume mode is off. (Fixes: :issue:`1221`, :issue:`1454`, PR: :issue:`1455`) diff --git a/mopidy/audio/actor.py b/mopidy/audio/actor.py index 1fb4fec6..61a8e008 100644 --- a/mopidy/audio/actor.py +++ b/mopidy/audio/actor.py @@ -368,7 +368,7 @@ class _Handler(object): # Emit any postponed tags that we got after about-to-finish. tags, self._audio._pending_tags = self._audio._pending_tags, None - self._audio._tags = tags + self._audio._tags = tags or {} if tags: logger.debug('Audio event: tags_changed(tags=%r)', tags.keys()) From 8707a7c9cf1d581911b76b2379fab8d014044e3b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 26 Jul 2016 21:41:49 +0200 Subject: [PATCH 12/12] lint: Re-enable flake8-import-order --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index b39fc68b..da6bcc38 100644 --- a/tox.ini +++ b/tox.ini @@ -37,8 +37,7 @@ commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html [testenv:flake8] deps = flake8 -# TODO: Re-enable once https://github.com/PyCQA/flake8-import-order/issues/79 is released. -# flake8-import-order + flake8-import-order pep8-naming commands = flake8 --show-source --statistics mopidy tests