diff --git a/Makefile b/Makefile
index 4bd3fc72..aecab9e8 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-python ?= python2.7
+python ?= python3
user ?= $(shell whoami)
all: pulseaudio_dlna.egg-info
diff --git a/README.md b/README.md
index 6f9016bc..258b3edd 100644
--- a/README.md
+++ b/README.md
@@ -252,23 +252,21 @@ is loaded.
These are the requirements _pulseaudio-dlna_ acutally needs to run. These dependencies
will get installed if you install it via the PPA.
-- python2.7
-- python-pip
-- python-setuptools
-- python-dbus
-- python-docopt
-- python-requests
-- python-setproctitle
-- python-gi
-- python-protobuf
-- python-notify2
-- python-psutil
-- python-concurrent.futures
-- python-chardet
-- python-netifaces
-- python-pyroute2 | python-netaddr
-- python-lxml
-- python-zeroconf
+- python3
+- python3-pip
+- python3-setuptools
+- python3-dbus
+- python3-docopt
+- python3-requests
+- python3-setproctitle
+- python3-gi
+- python3-notify2
+- python3-psutil
+- python3-chardet
+- python3-netifaces
+- python3-pyroute2 | python3-netaddr
+- python3-lxml
+- python3-pychromecast
- vorbis-tools
- sox
- lame
@@ -278,7 +276,7 @@ will get installed if you install it via the PPA.
You can install all the dependencies in Ubuntu via:
- sudo apt-get install python2.7 python-pip python-setuptools python-dbus python-docopt python-requests python-setproctitle python-gi python-protobuf python-notify2 python-psutil python-concurrent.futures python-chardet python-netifaces python-pyroute2 python-netaddr python-lxml python-zeroconf vorbis-tools sox lame flac faac opus-tools
+ sudo apt-get install python3 python3-pip python3-setuptools python3-dbus python3-docopt python3-requests python3-setproctitle python3-gi python3-notify2 python3-psutil python3-chardet python3-netifaces python3-pyroute2 python3-netaddr python3-lxml python3-pychromecast vorbis-tools sox lame flac faac opus-tools
### PulseAudio DBus module ###
@@ -314,7 +312,7 @@ So all Ubuntu versions prior to _14.10 Utopic_ need to install:
All Ubuntu versions above install:
- sudo apt-get install virtualenv python-dev
+ sudo apt-get install virtualenv python3-dev
#### Installing & starting ####
diff --git a/man/pulseaudio-dlna.1 b/man/pulseaudio-dlna.1
index afbc4d24..52ce3b52 100644
--- a/man/pulseaudio-dlna.1
+++ b/man/pulseaudio-dlna.1
@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2.
-.TH PULSEAUDIO-DLNA "1" "April 2016" "pulseaudio-dlna 0.5.2" "User Commands"
+.TH PULSEAUDIO-DLNA "1" "April 2016" "pulseaudio-dlna 0.6.0" "User Commands"
.SH NAME
pulseaudio-dlna \- Stream audio to DLNA devices and Chromecasts
.SH DESCRIPTION
diff --git a/pulseaudio_dlna/__init__.py b/pulseaudio_dlna/__init__.py
index 427d5a79..1ee758a2 100644
--- a/pulseaudio_dlna/__init__.py
+++ b/pulseaudio_dlna/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,12 +15,10 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import os
import pkg_resources
-import utils.git
+from .utils import git
try:
version = pkg_resources.get_distribution(__package__).version
@@ -30,7 +28,7 @@
if os.environ.get('USE_PKG_VERSION', None) == '1':
branch, rev = None, None
else:
- branch, rev = utils.git.get_head_version()
+ branch, rev = git.get_head_version()
__version__ = '{version}{rev}'.format(
version=version,
diff --git a/pulseaudio_dlna/__main__.py b/pulseaudio_dlna/__main__.py
index f49941bf..86d7d7a7 100644
--- a/pulseaudio_dlna/__main__.py
+++ b/pulseaudio_dlna/__main__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -115,9 +115,6 @@
'''
-
-from __future__ import unicode_literals
-
import sys
import os
import docopt
@@ -170,5 +167,6 @@ def acquire_lock():
except socket.error:
return False
+
if __name__ == "__main__":
sys.exit(main())
diff --git a/pulseaudio_dlna/application.py b/pulseaudio_dlna/application.py
index bca5a10a..215356bb 100644
--- a/pulseaudio_dlna/application.py
+++ b/pulseaudio_dlna/application.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import multiprocessing
import signal
import setproctitle
@@ -33,7 +31,6 @@
import pulseaudio_dlna.plugins.dlna.ssdp.listener
import pulseaudio_dlna.plugins.dlna.ssdp.discover
import pulseaudio_dlna.plugins.chromecast
-import pulseaudio_dlna.plugins.chromecast.mdns
import pulseaudio_dlna.encoders
import pulseaudio_dlna.covermodes
import pulseaudio_dlna.streamserver
@@ -47,7 +44,6 @@
class Application(object):
- ENCODING = 'utf-8'
DEVICE_CONFIG_PATHS = [
os.path.expanduser('~/.local/share/pulseaudio-dlna'),
'/etc/pulseaudio-dlna',
@@ -213,7 +209,7 @@ def run(self, options):
logger.info(' {}'.format(encoder))
logger.info('Codec settings:')
- for identifier, _type in pulseaudio_dlna.codecs.CODECS.iteritems():
+ for identifier, _type in pulseaudio_dlna.codecs.CODECS.items():
codec = _type()
logger.info(' {}'.format(codec))
@@ -331,9 +327,9 @@ def obj_to_dict(obj):
continue
try:
with open(config_file, 'w') as h:
- h.write(json_text.encode(self.ENCODING))
+ h.write(json_text)
logger.info('Found the following devices:')
- for device in holder.devices.values():
+ for device in list(holder.devices.values()):
logger.info('{name} ({flavour})'.format(
name=device.name, flavour=device.flavour))
for codec in device.codecs:
@@ -356,7 +352,7 @@ def read_device_config(self):
if os.path.isfile(config_file) and \
os.access(config_file, os.R_OK):
with open(config_file, 'r') as h:
- json_text = h.read().decode(self.ENCODING)
+ json_text = h.read()
logger.debug('Device configuration:\n{}'.format(json_text))
json_text = json_text.replace('\n', '')
try:
diff --git a/pulseaudio_dlna/codecs.py b/pulseaudio_dlna/codecs.py
index cb671cb5..b1e51675 100644
--- a/pulseaudio_dlna/codecs.py
+++ b/pulseaudio_dlna/codecs.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import functools
import logging
import re
@@ -67,7 +65,7 @@ def set_backend(backend):
def set_codecs(identifiers):
step = 3
priority = (len(CODECS) + 1) * step
- for identifier, _type in CODECS.iteritems():
+ for identifier, _type in CODECS.items():
_type.ENABLED = False
_type.PRIORITY = 0
for identifier in identifiers:
@@ -81,7 +79,7 @@ def set_codecs(identifiers):
def enabled_codecs():
codecs = []
- for identifier, _type in CODECS.iteritems():
+ for identifier, _type in CODECS.items():
if _type.ENABLED:
codecs.append(_type())
return codecs
@@ -168,7 +166,7 @@ def __str__(self, detailed=False):
def to_json(self):
attributes = ['priority', 'suffix', 'mime_type']
d = {
- k: v for k, v in self.__dict__.iteritems()
+ k: v for k, v in iter(self.__dict__.items())
if k not in attributes
}
d['mime_type'] = self.specific_mime_type
@@ -363,4 +361,5 @@ def load_codecs():
CODECS[_type.IDENTIFIER] = _type
return None
+
load_codecs()
diff --git a/pulseaudio_dlna/covermodes.py b/pulseaudio_dlna/covermodes.py
index d3dbd905..8a3992ea 100644
--- a/pulseaudio_dlna/covermodes.py
+++ b/pulseaudio_dlna/covermodes.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import sys
import inspect
import socket
@@ -82,7 +80,7 @@ class DefaultCoverMode(BaseCoverMode):
def thumb(self):
try:
return self.bridge.device.get_image_url('default.png')
- except:
+ except Exception:
return None
@@ -111,7 +109,7 @@ def thumb(self):
try:
return self.bridge.device.get_image_url(
'distribution-{}.png'.format(dist_icon))
- except:
+ except Exception:
return None
@@ -124,7 +122,7 @@ def thumb(self):
try:
return self.bridge.device.get_sys_icon_url(
self.bridge.sink.primary_application_name)
- except:
+ except Exception:
return None
@@ -136,4 +134,5 @@ def load_modes():
MODES[_type.IDENTIFIER] = _type
return None
+
load_modes()
diff --git a/pulseaudio_dlna/daemon.py b/pulseaudio_dlna/daemon.py
index 3f9e7cda..e86b4b5c 100644
--- a/pulseaudio_dlna/daemon.py
+++ b/pulseaudio_dlna/daemon.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
from gi.repository import GObject
import dbus
@@ -250,6 +248,9 @@ def __eq__(self, other):
def __gt__(self, other):
return self.pid > other.pid
+ def __hash__(self):
+ return self.pid
+
class PulseAudioFinder(object):
@staticmethod
diff --git a/pulseaudio_dlna/encoders/__init__.py b/pulseaudio_dlna/encoders/__init__.py
index 667e15fe..7b1ccbe6 100644
--- a/pulseaudio_dlna/encoders/__init__.py
+++ b/pulseaudio_dlna/encoders/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import distutils.spawn
import inspect
import sys
@@ -66,10 +64,7 @@ def _find_executable(path):
# The distutils module uses python's ascii default encoding and is
# therefore not capable of handling unicode properly when it contains
# non-ascii characters.
- encoding = 'utf-8'
- result = distutils.spawn.find_executable(path.encode(encoding))
- if result is not None and type(result) is str:
- result = result.decode(encoding)
+ result = distutils.spawn.find_executable(path)
return result
@@ -113,7 +108,7 @@ def supported_bit_rates(self):
def __str__(self):
return '<{} available="{}">'.format(
self.__class__.__name__,
- unicode(self.available),
+ str(self.available),
)
@@ -139,8 +134,8 @@ def supported_bit_rates(self):
def __str__(self):
return '<{} available="{}" bit-rate="{}">'.format(
self.__class__.__name__,
- unicode(self.available),
- unicode(self.bit_rate),
+ str(self.available),
+ str(self.bit_rate),
)
@@ -165,9 +160,9 @@ def channels(self, value):
def __str__(self):
return '<{} available="{}" sample-rate="{}" channels="{}">'.format(
self.__class__.__name__,
- unicode(self.available),
- unicode(self.sample_rate),
- unicode(self.channels),
+ str(self.available),
+ str(self.sample_rate),
+ str(self.channels),
)
@@ -194,4 +189,5 @@ def load_encoders():
ENCODERS.append(_type)
return None
+
load_encoders()
diff --git a/pulseaudio_dlna/encoders/avconv.py b/pulseaudio_dlna/encoders/avconv.py
index 5f09b25a..9d0accbf 100644
--- a/pulseaudio_dlna/encoders/avconv.py
+++ b/pulseaudio_dlna/encoders/avconv.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
from pulseaudio_dlna.encoders.ffmpeg import (
diff --git a/pulseaudio_dlna/encoders/ffmpeg.py b/pulseaudio_dlna/encoders/ffmpeg.py
index c16b2a4b..4a444e82 100644
--- a/pulseaudio_dlna/encoders/ffmpeg.py
+++ b/pulseaudio_dlna/encoders/ffmpeg.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
from pulseaudio_dlna.encoders import (
diff --git a/pulseaudio_dlna/encoders/generic.py b/pulseaudio_dlna/encoders/generic.py
index e68e1267..5940f4fa 100644
--- a/pulseaudio_dlna/encoders/generic.py
+++ b/pulseaudio_dlna/encoders/generic.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
from pulseaudio_dlna.encoders import (
diff --git a/pulseaudio_dlna/holder.py b/pulseaudio_dlna/holder.py
index 69e061be..f6697caa 100644
--- a/pulseaudio_dlna/holder.py
+++ b/pulseaudio_dlna/holder.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
import threading
import requests
@@ -73,7 +71,7 @@ def search(self, ttl=None, host=None):
break
if all_dead:
break
- except:
+ except Exception:
traceback.print_exc()
logger.info('Holder.search()')
@@ -96,7 +94,7 @@ def lookup(self, locations):
'Connection refused.'.format(url=url))
for plugin in self.plugins:
- for url, xml in xmls.items():
+ for url, xml in list(xmls.items()):
device = plugin.lookup(url, xml)
self.add_device(device)
diff --git a/pulseaudio_dlna/images.py b/pulseaudio_dlna/images.py
index 40a46349..92a100e5 100644
--- a/pulseaudio_dlna/images.py
+++ b/pulseaudio_dlna/images.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-from __future__ import with_statement
import tempfile
import logging
@@ -62,7 +60,7 @@ def get_icon_by_name(name, size=256):
try:
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
- except:
+ except Exception:
raise MissingDependencies(
'Unable to lookup system icons!',
['gir1.2-gtk-3.0']
@@ -99,7 +97,7 @@ def __init__(self, path, cached=True):
def _read_data(self):
try:
- with open(self.path) as h:
+ with open(self.path, 'rb') as h:
self._data = h.read()
except EnvironmentError:
raise ImageNotAccessible(self.path)
@@ -123,13 +121,13 @@ def __init__(self, path, cached=True, size=256):
try:
gi.require_version('Rsvg', '2.0')
from gi.repository import Rsvg
- except:
+ except Exception:
raise MissingDependencies(
'Unable to convert SVG image to PNG!', ['gir1.2-rsvg-2.0']
)
try:
import cairo
- except:
+ except Exception:
raise MissingDependencies(
'Unable to convert SVG image to PNG!', ['cairo']
)
diff --git a/pulseaudio_dlna/notification.py b/pulseaudio_dlna/notification.py
index d270c4e7..62e37a57 100644
--- a/pulseaudio_dlna/notification.py
+++ b/pulseaudio_dlna/notification.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
import notify2
@@ -29,14 +27,15 @@ def show(title, message, icon=''):
notice = notify2.Notification(title, message, icon)
notice.set_timeout(notify2.EXPIRES_DEFAULT)
notice.show()
- except:
+ except Exception:
logger.info(
'notify2 failed to display: {title} - {message}'.format(
title=title,
message=message))
+
try:
notify2.init('pulseaudio_dlna')
-except:
+except Exception:
logger.error('notify2 could not be initialized! Notifications will '
'most likely not work.')
diff --git a/pulseaudio_dlna/plugins/__init__.py b/pulseaudio_dlna/plugins/__init__.py
index 98f2252c..3cff717f 100644
--- a/pulseaudio_dlna/plugins/__init__.py
+++ b/pulseaudio_dlna/plugins/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import functools
diff --git a/pulseaudio_dlna/plugins/chromecast/__init__.py b/pulseaudio_dlna/plugins/chromecast/__init__.py
index adf8318d..3595a261 100644
--- a/pulseaudio_dlna/plugins/chromecast/__init__.py
+++ b/pulseaudio_dlna/plugins/chromecast/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,12 +15,11 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
+import threading
+import pychromecast
import pulseaudio_dlna.plugins
-import pulseaudio_dlna.plugins.chromecast.mdns
from pulseaudio_dlna.plugins.chromecast.renderer import ChromecastRendererFactory
logger = logging.getLogger('pulseaudio_dlna.plugins.chromecast')
@@ -28,8 +27,6 @@
class ChromecastPlugin(pulseaudio_dlna.plugins.BasePlugin):
- GOOGLE_MDNS_DOMAIN = '_googlecast._tcp.local.'
-
def __init__(self, *args):
pulseaudio_dlna.plugins.BasePlugin.__init__(self, *args)
@@ -38,20 +35,17 @@ def lookup(self, url, xml):
def discover(self, holder, ttl=None, host=None):
self.holder = holder
- mdns = pulseaudio_dlna.plugins.chromecast.mdns.MDNSListener(
- domain=self.GOOGLE_MDNS_DOMAIN,
- host=host,
- cb_on_device_added=self._on_device_added,
- cb_on_device_removed=self._on_device_removed
- )
- mdns.run(ttl)
+ stop_discovery = pychromecast.get_chromecasts(
+ blocking=False, callback=self._on_device_added)
+ if ttl:
+ t = threading.Timer(ttl, stop_discovery)
+ t.start()
+ logger.info('ChromecastPlugin.discover()')
@pulseaudio_dlna.plugins.BasePlugin.add_device_after
- def _on_device_added(self, mdns_info):
- if mdns_info:
- return ChromecastRendererFactory.from_mdns_info(mdns_info)
- return None
+ def _on_device_added(self, device):
+ return ChromecastRendererFactory.from_pychromecast(device)
@pulseaudio_dlna.plugins.BasePlugin.remove_device_after
- def _on_device_removed(self, mdns_info):
+ def _on_device_removed(self, device):
return None
diff --git a/pulseaudio_dlna/plugins/chromecast/mdns.py b/pulseaudio_dlna/plugins/chromecast/mdns.py
deleted file mode 100644
index 625e3c58..00000000
--- a/pulseaudio_dlna/plugins/chromecast/mdns.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/usr/bin/python
-
-# This file is part of pulseaudio-dlna.
-
-# pulseaudio-dlna is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# pulseaudio-dlna is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with pulseaudio-dlna. If not, see .
-
-from __future__ import unicode_literals
-
-from gi.repository import GObject
-
-import logging
-import zeroconf
-import time
-
-logger = logging.getLogger('pulseaudio_dlna.plugins.chromecast.mdns')
-
-
-class MDNSHandler(object):
-
- def __init__(self, server):
- self.server = server
-
- def add_service(self, zeroconf, type, name):
- info = zeroconf.get_service_info(type, name)
- if self.server.cb_on_device_added:
- self.server.cb_on_device_added(info)
-
- def remove_service(self, zeroconf, type, name):
- info = zeroconf.get_service_info(type, name)
- if self.server.cb_on_device_removed:
- self.server.cb_on_device_removed(info)
-
-
-class MDNSListener(object):
-
- def __init__(
- self, domain,
- host=None,
- cb_on_device_added=None, cb_on_device_removed=None):
- self.domain = domain
- self.host = host
- self.cb_on_device_added = cb_on_device_added
- self.cb_on_device_removed = cb_on_device_removed
-
- def run(self, ttl=None):
- if self.host:
- self.zeroconf = zeroconf.Zeroconf(interfaces=[self.host])
- else:
- self.zeroconf = zeroconf.Zeroconf()
- zeroconf.ServiceBrowser(self.zeroconf, self.domain, MDNSHandler(self))
-
- if ttl:
- GObject.timeout_add(ttl * 1000, self.shutdown)
-
- self.__running = True
- self.__mainloop = GObject.MainLoop()
- context = self.__mainloop.get_context()
- try:
- while self.__running:
- if context.pending():
- context.iteration(True)
- else:
- time.sleep(0.01)
- except KeyboardInterrupt:
- pass
- self.zeroconf.close()
- logger.info('MDNSListener.run()')
-
- def shutdown(self):
- logger.info('MDNSListener.shutdown()')
- self.__running = False
diff --git a/pulseaudio_dlna/plugins/chromecast/pycastv2/__init__.py b/pulseaudio_dlna/plugins/chromecast/pycastv2/__init__.py
deleted file mode 100644
index cd6fbce4..00000000
--- a/pulseaudio_dlna/plugins/chromecast/pycastv2/__init__.py
+++ /dev/null
@@ -1,313 +0,0 @@
-#!/usr/bin/python
-
-# This file is part of pulseaudio-dlna.
-
-# pulseaudio-dlna is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# pulseaudio-dlna is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with pulseaudio-dlna. If not, see .
-
-from __future__ import unicode_literals
-
-import time
-import logging
-
-import commands
-import cast_socket
-
-logger = logging.getLogger('pycastv2')
-
-
-class ChannelClosedException(Exception):
- pass
-
-
-class TimeoutException(Exception):
- pass
-
-
-class LaunchErrorException(Exception):
- pass
-
-
-class ChannelController(object):
- def __init__(self, socket):
- self.request_id = 1
- self.transport_id = 'receiver-0'
- self.session_id = None
- self.app_id = None
-
- self.channels = []
-
- self.socket = socket
- self.socket.add_send_listener(self._handle_send)
- self.socket.add_read_listener(self._handle_response)
- self.socket.send_and_wait(commands.StatusCommand())
-
- def _get_unused_request_id(self):
- self.request_id += 1
- return self.request_id - 1
-
- def _handle_send(self, command):
- if command.request_id is not None:
- command.request_id = (command.request_id or
- self._get_unused_request_id())
- if command.session_id is not None:
- command.session_id = command.session_id or self.session_id
- command.sender_id = command.sender_id or 'sender-0'
- if command.destination_id is None:
- command.destination_id = 'receiver-0'
- else:
- command.destination_id = (command.destination_id or
- self.transport_id)
- if not self.is_channel_connected(command.destination_id):
- self.connect_channel(command.destination_id)
- return command
-
- def _handle_response(self, response):
- if 'type' in response:
- response_type = response['type']
- if response_type == 'RECEIVER_STATUS':
- if 'applications' in response['status']:
- applications = response['status']['applications'][0]
- self.transport_id = (
- applications.get('transportId') or self.transport_id)
- self.session_id = (
- applications.get('sessionId') or self.session_id)
- self.app_id = (
- applications.get('appId') or self.app_id)
- else:
- self.transport_id = 'receiver-0'
- self.session_id = None
- self.app_id = None
- elif response_type == 'PING':
- self.socket.send(commands.PongCommand())
- elif response_type == 'CLOSE':
- raise ChannelClosedException()
- elif response_type == 'LAUNCH_ERROR':
- raise LaunchErrorException()
-
- def is_channel_connected(self, destination_id):
- return destination_id in self.channels
-
- def connect_channel(self, destination_id):
- self.channels.append(destination_id)
- self.socket.send(commands.ConnectCommand(destination_id))
-
- def disconnect_channel(self, destination_id):
- self.socket.send(commands.CloseCommand(destination_id))
- self.channels.remove(destination_id)
-
- def __str__(self):
- return ('\n'
- ' request_id: {request_id}\n'
- ' transport_id: {transport_id}\n'
- ' session_id: {session_id}\n'
- ' app_id: {app_id}'.format(
- request_id=self.request_id,
- transport_id=self.transport_id,
- session_id=self.session_id,
- app_id=self.app_id))
-
-
-class ChromecastController():
-
- APP_BACKDROP = 'E8C28D3C'
- WAIT_INTERVAL = 0.1
-
- def __init__(self, ip, port, timeout=10):
- self.timeout = timeout
- self.socket = cast_socket.CastSocket(ip, port)
- self.channel_controller = ChannelController(self.socket)
-
- def is_app_running(self, app_id):
- return self.channel_controller.app_id == app_id
-
- def launch_application(self, app_id):
- if not self.is_app_running(app_id):
- self.socket.send(commands.LaunchCommand(app_id))
- start_time = time.time()
- while not self.is_app_running(app_id):
- self.socket.send_and_wait(commands.StatusCommand())
- current_time = time.time()
- if current_time - start_time > self.timeout:
- raise TimeoutException()
- time.sleep(self.WAIT_INTERVAL)
- else:
- logger.debug('Starting not necessary. Application is running ...')
-
- def stop_application(self):
- if not self.is_app_running(self.APP_BACKDROP):
- self.socket.send(commands.StopCommand())
- start_time = time.time()
- while not self.is_app_running(None):
- self.socket.send_and_wait(commands.StatusCommand())
- current_time = time.time()
- if current_time - start_time > self.timeout:
- raise TimeoutException()
- time.sleep(self.WAIT_INTERVAL)
- else:
- logger.debug('Stop not necessary. Backdrop is running ...')
-
- def disconnect_application(self):
- if not self.is_app_running(self.APP_BACKDROP):
- self.socket.send(commands.CloseCommand(destination_id=False))
- start_time = time.time()
- while not self.is_app_running(None):
- try:
- self.socket.send_and_wait(commands.StatusCommand())
- except cast_socket.ConnectionTerminatedException:
- break
- current_time = time.time()
- if current_time - start_time > self.timeout:
- raise TimeoutException()
- time.sleep(self.WAIT_INTERVAL)
- else:
- logger.debug('Closing not necessary. Backdrop is running ...')
-
- def wait(self, timeout):
- self.socket.wait(timeout)
-
- def cleanup(self):
- self.socket.close()
-
-
-class LoadCommand(commands.BaseCommand):
- def __init__(self, url, mime_type, artist=None, title=None, thumb=None,
- session_id=None, destination_id=None, namespace=None):
- commands.BaseCommand.__init__(self)
- self.data = {
- 'autoplay': True,
- 'currentTime': 0,
- 'media': {'contentId': url,
- 'contentType': mime_type,
- 'streamType': 'LIVE',
- },
- 'type': 'LOAD'
- }
- if artist or title or thumb:
- self.data['media']['metadata'] = {
- 'metadataType': 3,
- }
- if artist:
- self.data['media']['metadata']['artist'] = artist
- if title:
- self.data['media']['metadata']['title'] = title
- if thumb:
- self.data['media']['metadata']['images'] = [
- {'url': thumb},
- ]
-
- self.request_id = False
- self.session_id = False
- self.destination_id = destination_id
- self.namespace = namespace or 'urn:x-cast:com.google.cast.media'
-
-
-class LoadFailedException(Exception):
- pass
-
-
-class MediaPlayerController(ChromecastController):
-
- APP_MEDIA_PLAYER = 'CC1AD845'
-
- PLAYER_STATE_BUFFERING = 'BUFFERING'
- PLAYER_STATE_PLAYING = 'PLAYING'
- PLAYER_STATE_PAUSED = 'PAUSED'
- PLAYER_STATE_IDLE = 'IDLE'
-
- def __init__(self, ip, port, timeout=10):
- ChromecastController.__init__(self, ip, port, timeout)
- self._media_session_id = None
- self._current_time = None
- self._media = None
- self._playback_rate = None
- self._volume = None
- self._player_state = None
-
- self.socket.add_read_listener(self._handle_response)
-
- def launch(self):
- self.launch_application(self.APP_MEDIA_PLAYER)
-
- def load(self, url, mime_type, artist=None, title=None, thumb=None):
- self.launch()
- try:
- self.socket.send_and_wait(
- LoadCommand(
- url, mime_type,
- artist=artist,
- title=title,
- thumb=thumb,
- destination_id=False))
- return True
- except (cast_socket.NoResponseException, LoadFailedException):
- return False
-
- def set_volume(self, volume):
- self.socket.send_and_wait(commands.SetVolumeCommand(volume))
-
- def set_mute(self, muted):
- self.socket.send_and_wait(commands.SetVolumeMuteCommand(muted))
-
- def _update_attribute(self, name, value):
- if value is not None:
- setattr(self, name, value)
-
- def _handle_response(self, response):
- if 'type' in response:
- if response['type'] == 'MEDIA_STATUS':
- try:
- status = response['status'][0]
- except IndexError:
- return
- self._update_attribute(
- '_media_session_id', status.get('mediaSessionId', None))
- self._update_attribute(
- '_current_time', status.get('currentTime', None))
- self._update_attribute(
- '_media', status.get('media', None))
- self._update_attribute(
- '_playback_rate', status.get('playbackRate', None))
- self._update_attribute(
- '_volume', status.get('volume', None))
- self._update_attribute(
- '_player_state', status.get('playerState', None))
- elif response['type'] == 'LOAD_FAILED':
- raise LoadFailedException()
-
- @property
- def player_state(self):
- return self._player_state
-
- @property
- def is_playing(self):
- return (self._player_state is not None and
- self._player_state == self.PLAYER_STATE_PLAYING)
-
- @property
- def is_paused(self):
- return (self._player_state is not None and
- self._player_state == self.PLAYER_STATE_PAUSED)
-
- @property
- def is_idle(self):
- return (self._player_state is not None and
- self._player_state == self.PLAYER_STATE_IDLE)
-
- @property
- def volume(self):
- return self._volume.get('level') if self._volume else None
-
- @property
- def is_muted(self):
- return self._volume.get('muted') if self._volume else None
diff --git a/pulseaudio_dlna/plugins/chromecast/pycastv2/cast_channel_pb2.py b/pulseaudio_dlna/plugins/chromecast/pycastv2/cast_channel_pb2.py
deleted file mode 100644
index b9b084de..00000000
--- a/pulseaudio_dlna/plugins/chromecast/pycastv2/cast_channel_pb2.py
+++ /dev/null
@@ -1,326 +0,0 @@
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: cast_channel.proto
-
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import message as _message
-from google.protobuf import reflection as _reflection
-from google.protobuf import descriptor_pb2
-# @@protoc_insertion_point(imports)
-
-
-
-
-DESCRIPTOR = _descriptor.FileDescriptor(
- name='cast_channel.proto',
- package='extensions.api.cast_channel',
- serialized_pb='\n\x12\x63\x61st_channel.proto\x12\x1b\x65xtensions.api.cast_channel\"\xe3\x02\n\x0b\x43\x61stMessage\x12R\n\x10protocol_version\x18\x01 \x02(\x0e\x32\x38.extensions.api.cast_channel.CastMessage.ProtocolVersion\x12\x11\n\tsource_id\x18\x02 \x02(\t\x12\x16\n\x0e\x64\x65stination_id\x18\x03 \x02(\t\x12\x11\n\tnamespace\x18\x04 \x02(\t\x12J\n\x0cpayload_type\x18\x05 \x02(\x0e\x32\x34.extensions.api.cast_channel.CastMessage.PayloadType\x12\x14\n\x0cpayload_utf8\x18\x06 \x01(\t\x12\x16\n\x0epayload_binary\x18\x07 \x01(\x0c\"!\n\x0fProtocolVersion\x12\x0e\n\nCASTV2_1_0\x10\x00\"%\n\x0bPayloadType\x12\n\n\x06STRING\x10\x00\x12\n\n\x06\x42INARY\x10\x01\"\x0f\n\rAuthChallenge\"B\n\x0c\x41uthResponse\x12\x11\n\tsignature\x18\x01 \x02(\x0c\x12\x1f\n\x17\x63lient_auth_certificate\x18\x02 \x02(\x0c\"~\n\tAuthError\x12\x44\n\nerror_type\x18\x01 \x02(\x0e\x32\x30.extensions.api.cast_channel.AuthError.ErrorType\"+\n\tErrorType\x12\x12\n\x0eINTERNAL_ERROR\x10\x00\x12\n\n\x06NO_TLS\x10\x01\"\xc6\x01\n\x11\x44\x65viceAuthMessage\x12=\n\tchallenge\x18\x01 \x01(\x0b\x32*.extensions.api.cast_channel.AuthChallenge\x12;\n\x08response\x18\x02 \x01(\x0b\x32).extensions.api.cast_channel.AuthResponse\x12\x35\n\x05\x65rror\x18\x03 \x01(\x0b\x32&.extensions.api.cast_channel.AuthErrorB\x02H\x03')
-
-
-
-_CASTMESSAGE_PROTOCOLVERSION = _descriptor.EnumDescriptor(
- name='ProtocolVersion',
- full_name='extensions.api.cast_channel.CastMessage.ProtocolVersion',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='CASTV2_1_0', index=0, number=0,
- options=None,
- type=None),
- ],
- containing_type=None,
- options=None,
- serialized_start=335,
- serialized_end=368,
-)
-
-_CASTMESSAGE_PAYLOADTYPE = _descriptor.EnumDescriptor(
- name='PayloadType',
- full_name='extensions.api.cast_channel.CastMessage.PayloadType',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='STRING', index=0, number=0,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='BINARY', index=1, number=1,
- options=None,
- type=None),
- ],
- containing_type=None,
- options=None,
- serialized_start=370,
- serialized_end=407,
-)
-
-_AUTHERROR_ERRORTYPE = _descriptor.EnumDescriptor(
- name='ErrorType',
- full_name='extensions.api.cast_channel.AuthError.ErrorType',
- filename=None,
- file=DESCRIPTOR,
- values=[
- _descriptor.EnumValueDescriptor(
- name='INTERNAL_ERROR', index=0, number=0,
- options=None,
- type=None),
- _descriptor.EnumValueDescriptor(
- name='NO_TLS', index=1, number=1,
- options=None,
- type=None),
- ],
- containing_type=None,
- options=None,
- serialized_start=577,
- serialized_end=620,
-)
-
-
-_CASTMESSAGE = _descriptor.Descriptor(
- name='CastMessage',
- full_name='extensions.api.cast_channel.CastMessage',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='protocol_version', full_name='extensions.api.cast_channel.CastMessage.protocol_version', index=0,
- number=1, type=14, cpp_type=8, label=2,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='source_id', full_name='extensions.api.cast_channel.CastMessage.source_id', index=1,
- number=2, type=9, cpp_type=9, label=2,
- has_default_value=False, default_value=unicode("", "utf-8"),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='destination_id', full_name='extensions.api.cast_channel.CastMessage.destination_id', index=2,
- number=3, type=9, cpp_type=9, label=2,
- has_default_value=False, default_value=unicode("", "utf-8"),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='namespace', full_name='extensions.api.cast_channel.CastMessage.namespace', index=3,
- number=4, type=9, cpp_type=9, label=2,
- has_default_value=False, default_value=unicode("", "utf-8"),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='payload_type', full_name='extensions.api.cast_channel.CastMessage.payload_type', index=4,
- number=5, type=14, cpp_type=8, label=2,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='payload_utf8', full_name='extensions.api.cast_channel.CastMessage.payload_utf8', index=5,
- number=6, type=9, cpp_type=9, label=1,
- has_default_value=False, default_value=unicode("", "utf-8"),
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='payload_binary', full_name='extensions.api.cast_channel.CastMessage.payload_binary', index=6,
- number=7, type=12, cpp_type=9, label=1,
- has_default_value=False, default_value="",
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- _CASTMESSAGE_PROTOCOLVERSION,
- _CASTMESSAGE_PAYLOADTYPE,
- ],
- options=None,
- is_extendable=False,
- extension_ranges=[],
- serialized_start=52,
- serialized_end=407,
-)
-
-
-_AUTHCHALLENGE = _descriptor.Descriptor(
- name='AuthChallenge',
- full_name='extensions.api.cast_channel.AuthChallenge',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- extension_ranges=[],
- serialized_start=409,
- serialized_end=424,
-)
-
-
-_AUTHRESPONSE = _descriptor.Descriptor(
- name='AuthResponse',
- full_name='extensions.api.cast_channel.AuthResponse',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='signature', full_name='extensions.api.cast_channel.AuthResponse.signature', index=0,
- number=1, type=12, cpp_type=9, label=2,
- has_default_value=False, default_value="",
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='client_auth_certificate', full_name='extensions.api.cast_channel.AuthResponse.client_auth_certificate', index=1,
- number=2, type=12, cpp_type=9, label=2,
- has_default_value=False, default_value="",
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- extension_ranges=[],
- serialized_start=426,
- serialized_end=492,
-)
-
-
-_AUTHERROR = _descriptor.Descriptor(
- name='AuthError',
- full_name='extensions.api.cast_channel.AuthError',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='error_type', full_name='extensions.api.cast_channel.AuthError.error_type', index=0,
- number=1, type=14, cpp_type=8, label=2,
- has_default_value=False, default_value=0,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- _AUTHERROR_ERRORTYPE,
- ],
- options=None,
- is_extendable=False,
- extension_ranges=[],
- serialized_start=494,
- serialized_end=620,
-)
-
-
-_DEVICEAUTHMESSAGE = _descriptor.Descriptor(
- name='DeviceAuthMessage',
- full_name='extensions.api.cast_channel.DeviceAuthMessage',
- filename=None,
- file=DESCRIPTOR,
- containing_type=None,
- fields=[
- _descriptor.FieldDescriptor(
- name='challenge', full_name='extensions.api.cast_channel.DeviceAuthMessage.challenge', index=0,
- number=1, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='response', full_name='extensions.api.cast_channel.DeviceAuthMessage.response', index=1,
- number=2, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- _descriptor.FieldDescriptor(
- name='error', full_name='extensions.api.cast_channel.DeviceAuthMessage.error', index=2,
- number=3, type=11, cpp_type=10, label=1,
- has_default_value=False, default_value=None,
- message_type=None, enum_type=None, containing_type=None,
- is_extension=False, extension_scope=None,
- options=None),
- ],
- extensions=[
- ],
- nested_types=[],
- enum_types=[
- ],
- options=None,
- is_extendable=False,
- extension_ranges=[],
- serialized_start=623,
- serialized_end=821,
-)
-
-_CASTMESSAGE.fields_by_name['protocol_version'].enum_type = _CASTMESSAGE_PROTOCOLVERSION
-_CASTMESSAGE.fields_by_name['payload_type'].enum_type = _CASTMESSAGE_PAYLOADTYPE
-_CASTMESSAGE_PROTOCOLVERSION.containing_type = _CASTMESSAGE;
-_CASTMESSAGE_PAYLOADTYPE.containing_type = _CASTMESSAGE;
-_AUTHERROR.fields_by_name['error_type'].enum_type = _AUTHERROR_ERRORTYPE
-_AUTHERROR_ERRORTYPE.containing_type = _AUTHERROR;
-_DEVICEAUTHMESSAGE.fields_by_name['challenge'].message_type = _AUTHCHALLENGE
-_DEVICEAUTHMESSAGE.fields_by_name['response'].message_type = _AUTHRESPONSE
-_DEVICEAUTHMESSAGE.fields_by_name['error'].message_type = _AUTHERROR
-DESCRIPTOR.message_types_by_name['CastMessage'] = _CASTMESSAGE
-DESCRIPTOR.message_types_by_name['AuthChallenge'] = _AUTHCHALLENGE
-DESCRIPTOR.message_types_by_name['AuthResponse'] = _AUTHRESPONSE
-DESCRIPTOR.message_types_by_name['AuthError'] = _AUTHERROR
-DESCRIPTOR.message_types_by_name['DeviceAuthMessage'] = _DEVICEAUTHMESSAGE
-
-class CastMessage(_message.Message):
- __metaclass__ = _reflection.GeneratedProtocolMessageType
- DESCRIPTOR = _CASTMESSAGE
-
- # @@protoc_insertion_point(class_scope:extensions.api.cast_channel.CastMessage)
-
-class AuthChallenge(_message.Message):
- __metaclass__ = _reflection.GeneratedProtocolMessageType
- DESCRIPTOR = _AUTHCHALLENGE
-
- # @@protoc_insertion_point(class_scope:extensions.api.cast_channel.AuthChallenge)
-
-class AuthResponse(_message.Message):
- __metaclass__ = _reflection.GeneratedProtocolMessageType
- DESCRIPTOR = _AUTHRESPONSE
-
- # @@protoc_insertion_point(class_scope:extensions.api.cast_channel.AuthResponse)
-
-class AuthError(_message.Message):
- __metaclass__ = _reflection.GeneratedProtocolMessageType
- DESCRIPTOR = _AUTHERROR
-
- # @@protoc_insertion_point(class_scope:extensions.api.cast_channel.AuthError)
-
-class DeviceAuthMessage(_message.Message):
- __metaclass__ = _reflection.GeneratedProtocolMessageType
- DESCRIPTOR = _DEVICEAUTHMESSAGE
-
- # @@protoc_insertion_point(class_scope:extensions.api.cast_channel.DeviceAuthMessage)
-
-
-DESCRIPTOR.has_options = True
-DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003')
-# @@protoc_insertion_point(module_scope)
diff --git a/pulseaudio_dlna/plugins/chromecast/pycastv2/cast_socket.py b/pulseaudio_dlna/plugins/chromecast/pycastv2/cast_socket.py
deleted file mode 100644
index fdcc6cd3..00000000
--- a/pulseaudio_dlna/plugins/chromecast/pycastv2/cast_socket.py
+++ /dev/null
@@ -1,213 +0,0 @@
-#!/usr/bin/python
-
-# This file is part of pulseaudio-dlna.
-
-# pulseaudio-dlna is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# pulseaudio-dlna is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with pulseaudio-dlna. If not, see .
-
-from __future__ import unicode_literals
-
-import ssl
-import socket
-import logging
-import struct
-import json
-import time
-import traceback
-import select
-
-import cast_channel_pb2
-
-logger = logging.getLogger('pycastv2.cast_socket')
-
-
-class NoResponseException(Exception):
- pass
-
-
-class ConnectionTerminatedException(Exception):
- pass
-
-
-class BaseChromecastSocket(object):
- def __init__(self, ip, port):
- self.sock = socket.socket()
- self.sock = ssl.wrap_socket(self.sock)
- self.sock.connect((ip, port))
- self.agent = 'chromecast_v2'
-
- def _generate_message(self,
- source_id='sender-0', destination_id='receiver-0',
- namespace=None):
- message = cast_channel_pb2.CastMessage()
- message.protocol_version = message.CASTV2_1_0
- message.source_id = source_id
- message.destination_id = destination_id
- message.payload_type = cast_channel_pb2.CastMessage.STRING
- if namespace:
- message.namespace = namespace
- return message
-
- def close(self):
- self.sock.close()
- logger.debug('Chromecast socket was cleaned up.')
-
- def send(self, data, sender_id, destination_id, namespace=None):
- json_data = json.dumps(data)
- message = self._generate_message(
- source_id=sender_id,
- destination_id=destination_id,
- namespace=namespace)
- message.payload_utf8 = json_data
- size = struct.pack('>I', message.ByteSize())
- formatted_message = size + message.SerializeToString()
- self.sock.sendall(formatted_message)
-
- def read(self, timeout=10):
- try:
- start_time = time.time()
- data = str('')
- while len(data) < 4:
- if time.time() - start_time > timeout:
- raise NoResponseException()
- part = self.sock.recv(1)
- if len(part) == 0:
- raise ConnectionTerminatedException()
- data += part
- length = struct.unpack('>I', data)[0]
- data = str('')
- while len(data) < length:
- part = self.sock.recv(2048)
- data += part
- message = self._generate_message()
- message.ParseFromString(data)
- response = json.loads(message.payload_utf8)
- return response
- except ssl.SSLError as e:
- if e.message == 'The read operation timed out':
- raise NoResponseException()
- else:
- logger.debug('Catched exception:')
- traceback.print_exc()
- return {}
-
-
-class CastSocket(BaseChromecastSocket):
- def __init__(self, ip, port):
- BaseChromecastSocket.__init__(self, ip, port)
- self.read_listeners = []
- self.send_listeners = []
- self.response_cache = {}
-
- def send(self, command):
- for listener in self.send_listeners:
- command = listener(command)
- logger.debug('Sending message:\n{command}'.format(
- command=command))
- BaseChromecastSocket.send(
- self,
- data=command.data,
- sender_id=command.sender_id,
- destination_id=command.destination_id,
- namespace=command.namespace)
- return command.request_id
-
- def read(self, timeout=None):
- if timeout is not None:
- self.wait_for_read(timeout)
- response = BaseChromecastSocket.read(self)
- logger.debug('Recieved message:\n {message}'.format(
- message=json.dumps(response, indent=2)))
- for listener in self.read_listeners:
- listener(response)
- return response
-
- def add_read_listener(self, listener):
- self.read_listeners.append(listener)
-
- def add_send_listener(self, listener):
- self.send_listeners.append(listener)
-
- def send_and_wait(self, command):
- req_id = self.send(command)
- return self.wait_for_response_id(req_id)
-
- def wait_for_read(self, timeout=None):
- start_time = time.time()
- while True:
- if self._is_socket_readable():
- return
- current_time = time.time()
- if current_time - start_time > timeout:
- raise NoResponseException()
- time.sleep(0.1)
-
- def wait_for_response_id(self, req_id, timeout=10):
- start_time = time.time()
- while True:
- if not self._is_socket_readable():
- time.sleep(0.1)
- else:
- response = self.read()
- self._add_to_response_cache(response)
- if req_id in self.response_cache:
- return response
- current_time = time.time()
- if current_time - start_time > timeout:
- raise NoResponseException()
- return None
-
- def wait_for_response_type(self, _type, timeout=10):
- start_time = time.time()
- while True:
- if not self._is_socket_readable():
- time.sleep(0.1)
- else:
- response = self.read()
- self._add_to_response_cache(response)
- if response.get('type', None) == _type:
- return response
- current_time = time.time()
- if current_time - start_time > timeout:
- raise NoResponseException()
- return None
-
- def wait(self, timeout=10):
- start_time = time.time()
- while True:
- if not self._is_socket_readable():
- time.sleep(0.1)
- else:
- response = self.read()
- self._add_to_response_cache(response)
- current_time = time.time()
- if current_time - start_time > timeout:
- return
- return None
-
- def _add_to_response_cache(self, response):
- if 'requestId' in response:
- req_id = response['requestId']
- if int(req_id) != 0:
- self.response_cache[req_id] = response
-
- def _is_socket_readable(self):
- try:
- r, w, e = select.select([self.sock], [], [self.sock], 0)
- for sock in r:
- return True
- for sock in e:
- raise NoResponseException()
- except socket.error:
- raise NoResponseException()
- return False
diff --git a/pulseaudio_dlna/plugins/chromecast/pycastv2/commands.py b/pulseaudio_dlna/plugins/chromecast/pycastv2/commands.py
deleted file mode 100644
index e9bc9e43..00000000
--- a/pulseaudio_dlna/plugins/chromecast/pycastv2/commands.py
+++ /dev/null
@@ -1,194 +0,0 @@
-#!/usr/bin/python
-
-# This file is part of pulseaudio-dlna.
-
-# pulseaudio-dlna is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# pulseaudio-dlna is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with pulseaudio-dlna. If not, see .
-
-from __future__ import unicode_literals
-
-
-class BaseCommand(object):
- def __init__(self):
- self._sender_id = None
- self._destination_id = None
- self._namespace = None
- self._data = None
-
- @property
- def sender_id(self):
- return self._sender_id
-
- @sender_id.setter
- def sender_id(self, value):
- self._sender_id = value
-
- @property
- def destination_id(self):
- return self._destination_id
-
- @destination_id.setter
- def destination_id(self, value):
- self._destination_id = value
-
- @property
- def request_id(self):
- if 'requestId' in self.data:
- return self.data['requestId']
- return 0
-
- @request_id.setter
- def request_id(self, value):
- if value is not None:
- self.data['requestId'] = value
-
- @property
- def session_id(self):
- if 'sessionId' in self.data:
- return self.data['sessionId']
- return None
-
- @session_id.setter
- def session_id(self, value):
- if value is not None:
- self.data['sessionId'] = value
-
- @property
- def namespace(self):
- return self._namespace
-
- @namespace.setter
- def namespace(self, value):
- self._namespace = value
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, value):
- self._data = value
-
- def __str__(self):
- return ('<{class_name}>\n'
- ' namespace: {namespace}\n'
- ' destination_id: {destination_id}\n'
- ' data: {data}'.format(
- class_name=self.__class__.__name__,
- namespace=self.namespace,
- destination_id=self.destination_id,
- data=self.data))
-
-
-class ConnectCommand(BaseCommand):
- def __init__(self, destination_id=None, namespace=None, agent=None):
- BaseCommand.__init__(self)
- self.data = {
- 'origin': {},
- 'type': 'CONNECT',
- 'userAgent': agent or 'Unknown'
- }
- self.destination_id = destination_id
- self.namespace = (namespace or
- 'urn:x-cast:com.google.cast.tp.connection')
-
-
-class CloseCommand(BaseCommand):
- def __init__(self, destination_id=None, namespace=None):
- BaseCommand.__init__(self)
- self.data = {
- 'origin': {},
- 'type': 'CLOSE'
- }
- self.destination_id = destination_id
- self.namespace = (namespace or
- 'urn:x-cast:com.google.cast.tp.connection')
-
-
-class StatusCommand(BaseCommand):
- def __init__(self, destination_id=None, namespace=None):
- BaseCommand.__init__(self)
- self.data = {
- 'type': 'GET_STATUS'
- }
- self.request_id = False
- self.destination_id = destination_id
- self.namespace = namespace or 'urn:x-cast:com.google.cast.receiver'
-
-
-class LaunchCommand(BaseCommand):
- def __init__(self, app_id, destination_id=None, namespace=None):
- BaseCommand.__init__(self)
- self.data = {
- 'appId': app_id,
- 'type': 'LAUNCH'
- }
- self.request_id = False
- self.destination_id = destination_id
- self.namespace = namespace or 'urn:x-cast:com.google.cast.receiver'
-
-
-class StopCommand(BaseCommand):
- def __init__(self, session_id=False, destination_id=None,
- namespace=None):
- BaseCommand.__init__(self)
- self.data = {
- 'type': 'STOP'
- }
- self.session_id = False
- self.request_id = False
- self.destination_id = destination_id
- self.namespace = namespace or 'urn:x-cast:com.google.cast.receiver'
-
-
-class SetVolumeCommand(BaseCommand):
- def __init__(self, volume, session_id=False, destination_id=None,
- namespace=None):
- BaseCommand.__init__(self)
- self.data = {
- 'type': 'SET_VOLUME',
- 'volume': {
- 'level': volume,
- },
- }
- self.request_id = False
- self.destination_id = destination_id
- self.namespace = namespace or 'urn:x-cast:com.google.cast.receiver'
-
-
-class SetVolumeMuteCommand(BaseCommand):
- def __init__(self, muted, session_id=False, destination_id=None,
- namespace=None):
- BaseCommand.__init__(self)
- self.data = {
- 'type': 'SET_VOLUME',
- 'volume': {
- 'muted': muted,
- },
- }
- self.request_id = False
- self.destination_id = destination_id
- self.namespace = namespace or 'urn:x-cast:com.google.cast.receiver'
-
-
-class PongCommand(BaseCommand):
- def __init__(self, session_id=False, destination_id=None,
- namespace=None):
- BaseCommand.__init__(self)
- self.data = {
- 'type': 'PONG'
- }
- self.session_id = False
- self.request_id = False
- self.destination_id = destination_id
- self.namespace = namespace or 'urn:x-cast:com.google.cast.tp.heartbeat'
diff --git a/pulseaudio_dlna/plugins/chromecast/pycastv2/example.py b/pulseaudio_dlna/plugins/chromecast/pycastv2/example.py
deleted file mode 100644
index ebb296d3..00000000
--- a/pulseaudio_dlna/plugins/chromecast/pycastv2/example.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/python
-
-# This file is part of pulseaudio-dlna.
-
-# pulseaudio-dlna is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# pulseaudio-dlna is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with pulseaudio-dlna. If not, see .
-
-from __future__ import unicode_literals
-
-import logging
-import __init__ as pycastv2
-
-logging.basicConfig(level=logging.DEBUG)
-
-mc = pycastv2.MediaPlayerController('192.168.1.3')
-try:
- mc.load('http://192.168.1.2:8080/stream.mp3', 'audio/mpeg')
- mc.wait(10)
- mc.disconnect_application()
- # mc.stop_application()
-except pycastv2.ChannelClosedException:
- print('Channel was closed.')
-except pycastv2.TimeoutException:
- print('Request timed out.')
-finally:
- mc.cleanup()
diff --git a/pulseaudio_dlna/plugins/chromecast/renderer.py b/pulseaudio_dlna/plugins/chromecast/renderer.py
index fc051bbe..6950a569 100644
--- a/pulseaudio_dlna/plugins/chromecast/renderer.py
+++ b/pulseaudio_dlna/plugins/chromecast/renderer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,20 +15,18 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import requests
import logging
-import urlparse
-import socket
+import urllib.parse
import traceback
import lxml
+import pychromecast
-import pycastv2
import pulseaudio_dlna.plugins.renderer
import pulseaudio_dlna.rules
import pulseaudio_dlna.codecs
+
logger = logging.getLogger('pulseaudio_dlna.plugins.chromecast.renderer')
@@ -64,84 +62,51 @@ def activate(self, config):
self.apply_device_rules()
self.prioritize_codecs()
+ def _create_pychromecast(self):
+ chromecast = pychromecast._get_chromecast_from_host(
+ (self.ip, self.port, self.udn, self.model_name, self.name))
+ return chromecast
+
def play(self, url=None, codec=None, artist=None, title=None, thumb=None):
self._before_play()
url = url or self.get_stream_url()
try:
- cast = pycastv2.MediaPlayerController(
- self.ip, self.port, self.REQUEST_TIMEOUT)
- cast.load(
+ chromecast = self._create_pychromecast()
+ chromecast.media_controller.play_media(
url,
- mime_type=self.codec.mime_type,
- artist=artist,
+ content_type=self.codec.mime_type,
title=title,
- thumb=thumb)
+ thumb=thumb,
+ stream_type=pychromecast.controllers.media.STREAM_TYPE_LIVE,
+ autoplay=True,
+ )
self.state = self.STATE_PLAYING
return 200, None
- except pycastv2.LaunchErrorException:
- message = 'The media player could not be launched. ' \
- 'Maybe the chromecast is still closing a ' \
- 'running player instance. Try again in 30 seconds.'
- return 503, message
- except pycastv2.ChannelClosedException:
- message = 'Connection was closed. I guess another ' \
- 'client is attached to it.'
- return 423, message
- except pycastv2.TimeoutException:
- message = 'PLAY command - Could no connect to "{device}". ' \
- 'Connection timeout.'.format(device=self.label)
- return 408, message
- except socket.error as e:
- if e.errno == 111:
- message = 'The chromecast refused the connection. ' \
- 'Perhaps it does not support the castv2 ' \
- 'protocol.'
- return 403, message
- else:
- traceback.print_exc()
- return 500, None
+ except pychromecast.error.PyChromecastError as e:
+ return 500, str(e)
except (pulseaudio_dlna.plugins.renderer.NoEncoderFoundException,
- pulseaudio_dlna.plugins.renderer.NoSuitableHostFoundException)\
- as e:
+ pulseaudio_dlna.plugins.renderer.NoSuitableHostFoundException) as e:
return 500, e
except Exception:
traceback.print_exc()
return 500, 'Unknown exception.'
finally:
self._after_play()
- cast.cleanup()
def stop(self):
self._before_stop()
try:
- cast = pycastv2.MediaPlayerController(
- self.ip, self.port, self.REQUEST_TIMEOUT)
self.state = self.STATE_STOPPED
- cast.disconnect_application()
+ chromecast = self._create_pychromecast()
+ chromecast.quit_app()
return 200, None
- except pycastv2.ChannelClosedException:
- message = 'Connection was closed. I guess another ' \
- 'client is attached to it.'
- return 423, message
- except pycastv2.TimeoutException:
- message = 'STOP command - Could no connect to "{device}". ' \
- 'Connection timeout.'.format(device=self.label)
- return 408, message
- except socket.error as e:
- if e.errno == 111:
- message = 'The chromecast refused the connection. ' \
- 'Perhaps it does not support the castv2 ' \
- 'protocol.'
- return 403, message
- else:
- traceback.print_exc()
- return 500, 'Unknown exception.'
+ except pychromecast.error.PyChromecastError as e:
+ return 500, e
except Exception:
traceback.print_exc()
return 500, 'Unknown exception.'
finally:
self._after_stop()
- cast.cleanup()
def pause(self):
raise NotImplementedError()
@@ -150,14 +115,7 @@ def pause(self):
class ChromecastRendererFactory(object):
NOTIFICATION_TYPES = [
- 'urn:dial-multiscreen-org:device:dial:1',
- ]
-
- CHROMECAST_MODELS = [
- 'Eureka Dongle',
- 'Chromecast Audio',
- 'Nexus Player',
- 'Freebox Player Mini',
+ 'urn:dial-multiscreen-org:device:dial:',
]
@classmethod
@@ -180,7 +138,7 @@ def from_url(cls, url):
@classmethod
def from_xml(cls, url, xml):
- url_object = urlparse.urlparse(url)
+ url_object = urllib.parse.urlparse(url)
ip, port = url_object.netloc.split(':')
try:
xml_root = lxml.etree.fromstring(xml)
@@ -191,27 +149,25 @@ def from_xml(cls, url, xml):
device_modelname = device.find('{*}modelName')
device_manufacturer = device.find('{*}manufacturer')
- if device_type.text not in cls.NOTIFICATION_TYPES:
- continue
-
- if device_modelname.text.strip() not in cls.CHROMECAST_MODELS:
- logger.info(
- 'The Chromecast seems not to be an original one. '
- 'Model name: "{}" Skipping device ...'.format(
- device_modelname.text))
- return None
-
- return ChromecastRenderer(
- name=unicode(device_friendlyname.text),
- ip=unicode(ip),
- port=None,
- udn=unicode(device_udn.text),
- model_name=unicode(device_modelname.text),
- model_number=None,
- model_description=None,
- manufacturer=unicode(device_manufacturer.text),
- )
- except:
+ valid_notification_type = False
+ for notification_type in cls.NOTIFICATION_TYPES:
+ if device_type.text.startswith(notification_type):
+ valid_notification_type = True
+ break
+
+ if valid_notification_type:
+ return ChromecastRenderer(
+ name=str(device_friendlyname.text),
+ ip=str(ip),
+ port=None,
+ udn=str(device_udn.text),
+ model_name=str(device_modelname.text),
+ model_number=None,
+ model_description=None,
+ manufacturer=str(device_manufacturer.text),
+ )
+ except Exception as e:
+ traceback.print_exc()
logger.error('No valid XML returned from {url}.'.format(url=url))
return None
@@ -221,37 +177,14 @@ def from_header(cls, header):
return cls.from_url(header['location'])
@classmethod
- def from_mdns_info(cls, info):
-
- def _bytes2string(bytes):
- ip = []
- for b in bytes:
- subnet = int(b.encode('hex'), 16)
- ip.append(str(subnet))
- return '.'.join(ip)
-
- def _get_device_info(info):
- try:
- return {
- 'udn': '{}:{}'.format('uuid', info.properties['id']),
- 'type': info.properties['md'].decode('utf-8'),
- 'name': info.properties['fn'].decode('utf-8'),
- 'ip': _bytes2string(info.address),
- 'port': int(info.port),
- }
- except (KeyError, AttributeError, TypeError):
- return None
-
- device_info = _get_device_info(info)
- if device_info:
- return ChromecastRenderer(
- name=device_info['name'],
- ip=device_info['ip'],
- port=device_info['port'],
- udn=device_info['udn'],
- model_name=device_info['type'],
- model_number=None,
- model_description=None,
- manufacturer='Google Inc.'
- )
- return None
+ def from_pychromecast(self, pychromecast):
+ return ChromecastRenderer(
+ name=pychromecast.name,
+ ip=pychromecast.host,
+ port=pychromecast.port,
+ udn='uuid:{}'.format(pychromecast.uuid),
+ model_name=pychromecast.model_name,
+ model_number=None,
+ model_description=None,
+ manufacturer=pychromecast.device.manufacturer,
+ )
diff --git a/pulseaudio_dlna/plugins/dlna/__init__.py b/pulseaudio_dlna/plugins/dlna/__init__.py
index fe5ca2d3..03c28607 100644
--- a/pulseaudio_dlna/plugins/dlna/__init__.py
+++ b/pulseaudio_dlna/plugins/dlna/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
import threading
import traceback
@@ -73,7 +71,7 @@ def launch_listener():
thread.start()
for thread in threads:
thread.join()
- except:
+ except Exception:
traceback.print_exc()
logger.info('DLNAPlugin.discover()')
diff --git a/pulseaudio_dlna/plugins/dlna/pyupnpv2/__init__.py b/pulseaudio_dlna/plugins/dlna/pyupnpv2/__init__.py
index 6fb0ea0c..03cfda02 100644
--- a/pulseaudio_dlna/plugins/dlna/pyupnpv2/__init__.py
+++ b/pulseaudio_dlna/plugins/dlna/pyupnpv2/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,16 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import requests
-import urlparse
+import urllib.parse
import logging
import collections
+import urllib.parse
+import os
import lxml
import lxml.builder
-import byto
+from . import byto
logger = logging.getLogger('pyupnpv2')
@@ -138,16 +138,16 @@ def etree_to_dict(t):
if children:
dd = defaultdict(list)
for dc in map(etree_to_dict, children):
- for k, v in dc.items():
+ for k, v in list(dc.items()):
dd[k].append(v)
d = {
_tag_name(t): {
- k: v[0] if len(v) == 1 else v for k, v in dd.items()
+ k: v[0] if len(v) == 1 else v for k, v in list(dd.items())
}
}
if t.attrib:
d[_tag_name(t)].update(
- ('@' + k, v) for k, v in t.attrib.items()
+ ('@' + k, v) for k, v in list(t.attrib.items())
)
if t.text:
text = t.text.strip()
@@ -161,7 +161,7 @@ def etree_to_dict(t):
try:
xml = lxml.etree.fromstring(xml)
return etree_to_dict(xml)
- except:
+ except Exception:
raise XmlParsingException(xml)
@@ -207,16 +207,19 @@ def __str__(self):
class UpnpServiceFactory(object):
@classmethod
- def from_dict(cls, ip, port, service, request):
+ def from_dict(cls, ip, port, service, access_url, request):
if service['service_type'].startswith(
'{}:'.format(SERVICE_TYPE_AVTRANSPORT)):
- return UpnpAVTransportService(ip, port, service, request)
+ return UpnpAVTransportService(
+ ip, port, service, access_url, request)
elif service['service_type'].startswith(
'{}:'.format(SERVICE_TYPE_CONNECTION_MANAGER)):
- return UpnpConnectionManagerService(ip, port, service, request)
+ return UpnpConnectionManagerService(
+ ip, port, service, access_url, request)
elif service['service_type'].startswith(
'{}:'.format(SERVICE_TYPE_RENDERING_CONTROL)):
- return UpnpRenderingControlService(ip, port, service, request)
+ return UpnpRenderingControlService(
+ ip, port, service, access_url, request)
else:
raise UnsupportedServiceTypeException(service['service_type'])
@@ -226,20 +229,29 @@ class UpnpService(object):
ENCODING = 'utf-8'
TIMEOUT = 10
- def __init__(self, ip, port, service, request=None):
+ def __init__(self, ip, port, service, access_url, request=None):
self.ip = ip
self.port = port
self.supported_actions = []
+ self.access_url = access_url
self._request = request or requests
self._service_type = service['service_type']
- self._control_url = service['control_url']
- self._event_url = service['eventsub_url']
- self._scpd_url = service['scpd_url']
+ self._control_url = self._ensure_absolute_url(service['control_url'])
+ self._event_url = self._ensure_absolute_url(service['eventsub_url'])
+ self._scpd_url = self._ensure_absolute_url(service['scpd_url'])
self._update_supported_actions()
+ def _ensure_absolute_url(self, url):
+ if not url.startswith('/'):
+ url_object = urllib.parse.urlparse(self.access_url)
+ access_url_path = os.path.dirname(url_object.path)
+ return os.path.join('/', access_url_path, url)
+ else:
+ return url
+
def _update_supported_actions(self):
self.supported_actions = []
response = self._request.get(self.scpd_url)
@@ -261,7 +273,7 @@ def _generate_soap_xml(
xml_declaration=True, pretty_print=False, encoding='utf-8'):
def _add_dict(root, dict_):
- for tag, value in dict_.items():
+ for tag, value in list(dict_.items()):
if isinstance(value, dict):
element = lxml.etree.Element(tag)
_add_dict(element, value)
@@ -336,7 +348,7 @@ def _do_post_request(self, url, headers, data):
try:
response = None
response = self._request.post(
- url, data=data.encode(self.ENCODING), headers=headers,
+ url, data=data, headers=headers,
timeout=self.TIMEOUT)
return response
finally:
@@ -386,7 +398,7 @@ def control_url(self):
ip=self.ip,
port=self.port,
)
- return urlparse.urljoin(host, self._control_url)
+ return urllib.parse.urljoin(host, self._control_url)
@property
def event_url(self):
@@ -394,7 +406,7 @@ def event_url(self):
ip=self.ip,
port=self.port,
)
- return urlparse.urljoin(host, self._event_url)
+ return urllib.parse.urljoin(host, self._event_url)
@property
def scpd_url(self):
@@ -402,7 +414,7 @@ def scpd_url(self):
ip=self.ip,
port=self.port,
)
- return urlparse.urljoin(host, self._scpd_url)
+ return urllib.parse.urljoin(host, self._scpd_url)
class UpnpAVTransportService(UpnpService):
@@ -537,7 +549,7 @@ def __init__(self, description_xml, access_url, ip, port, name, udn,
for service in services:
try:
service = UpnpServiceFactory.from_dict(
- ip, port, service, self._request)
+ ip, port, service, access_url, self._request)
if isinstance(service, UpnpAVTransportService):
self.av_transport = service
if isinstance(service, UpnpConnectionManagerService):
@@ -642,7 +654,7 @@ def from_url(cls, url):
def from_xml(cls, url, xml):
def process_xml(url, xml_root, xml):
- url_object = urlparse.urlparse(url)
+ url_object = urllib.parse.urlparse(url)
ip, port = url_object.netloc.split(':')
services = []
for device in xml_root.findall('.//{*}device'):
@@ -671,20 +683,20 @@ def process_xml(url, xml_root, xml):
upnp_device = UpnpMediaRenderer(
description_xml=xml,
access_url=url,
- ip=unicode(ip),
+ ip=str(ip),
port=port,
- name=unicode(device_friendlyname.text),
- udn=unicode(device_udn.text),
- model_name=unicode(
+ name=str(device_friendlyname.text),
+ udn=str(device_udn.text),
+ model_name=str(
device_modelname.text) if (
device_modelname is not None) else None,
- model_number=unicode(
+ model_number=str(
device_modelnumber.text) if (
device_modelnumber is not None) else None,
- model_description=unicode(
+ model_description=str(
device_modeldescription.text) if (
device_modeldescription is not None) else None,
- manufacturer=unicode(
+ manufacturer=str(
device_manufacturer.text) if (
device_manufacturer is not None) else None,
services=services,
@@ -701,15 +713,16 @@ def process_xml(url, xml_root, xml):
'Device skipped! \n{}'.format(
device_friendlyname.text, e.xml))
try:
+ xml = xml.decode("utf-8").replace(" urn:microsoft-com:wmc-1-0", "urn:microsoft-com:wmc-1-0").encode("utf-8")
xml_root = lxml.etree.fromstring(xml)
return process_xml(url, xml_root, xml)
- except:
+ except Exception:
logger.debug('Got broken xml, trying to fix it.')
xml = byto.repair_xml(xml)
try:
xml_root = lxml.etree.fromstring(xml)
return process_xml(url, xml_root, xml)
- except:
+ except Exception:
import traceback
traceback.print_exc()
logger.error('No valid XML returned from {url}.'.format(
diff --git a/pulseaudio_dlna/plugins/dlna/pyupnpv2/byto.py b/pulseaudio_dlna/plugins/dlna/pyupnpv2/byto.py
index c4ca9070..a5106dbb 100644
--- a/pulseaudio_dlna/plugins/dlna/pyupnpv2/byto.py
+++ b/pulseaudio_dlna/plugins/dlna/pyupnpv2/byto.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -17,7 +17,7 @@
"""A module which runs things without importing unicode_literals
-Sometimes you want pythons builtin functions just to run on raw bytes. Since
+Sometimes you want python3s builtin functions just to run on raw bytes. Since
the unicode_literals module changes that behavior for many string manipulations
this module is a workarounds for not using future.utils.bytes_to_native_str
method.
diff --git a/pulseaudio_dlna/plugins/dlna/renderer.py b/pulseaudio_dlna/plugins/dlna/renderer.py
index b695e8d5..0b8de0d9 100644
--- a/pulseaudio_dlna/plugins/dlna/renderer.py
+++ b/pulseaudio_dlna/plugins/dlna/renderer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
import time
import traceback
@@ -27,7 +25,7 @@
import pulseaudio_dlna.codecs
import pulseaudio_dlna.rules
import pulseaudio_dlna.plugins.renderer
-import pyupnpv2
+from . import pyupnpv2
logger = logging.getLogger('pulseaudio_dlna.plugins.dlna.renderer')
@@ -123,8 +121,7 @@ def play(self, url=None, codec=None, artist=None, title=None, thumb=None):
pyupnpv2.ConnectionErrorException,
pyupnpv2.ConnectionTimeoutException,
pulseaudio_dlna.plugins.renderer.NoEncoderFoundException,
- pulseaudio_dlna.plugins.renderer.NoSuitableHostFoundException)\
- as e:
+ pulseaudio_dlna.plugins.renderer.NoSuitableHostFoundException) as e:
return 500, '"{}" : {}'.format(self.label, str(e))
except Exception:
traceback.print_exc()
@@ -156,12 +153,12 @@ def get_volume(self):
return int(d['GetVolumeResponse']['CurrentVolume'])
except KeyError:
e = MissingAttributeException('get_protocol_info')
+ logger.error('"{}" : {}'.format(self.label, str(e)))
except (pyupnpv2.UnsupportedActionException,
pyupnpv2.XmlParsingException,
pyupnpv2.ConnectionErrorException,
pyupnpv2.ConnectionTimeoutException) as e:
- pass
- logger.error('"{}" : {}'.format(self.label, str(e)))
+ logger.error('"{}" : {}'.format(self.label, str(e)))
return None
def set_volume(self, volume):
@@ -171,8 +168,7 @@ def set_volume(self, volume):
pyupnpv2.XmlParsingException,
pyupnpv2.ConnectionErrorException,
pyupnpv2.ConnectionTimeoutException) as e:
- pass
- logger.error('"{}" : {}'.format(self.label, str(e)))
+ logger.error('"{}" : {}'.format(self.label, str(e)))
return None
def get_mute(self):
@@ -181,12 +177,12 @@ def get_mute(self):
return int(d['GetMuteResponse']['CurrentMute']) != 0
except KeyError:
e = MissingAttributeException('get_mute')
+ logger.error('"{}" : {}'.format(self.label, str(e)))
except (pyupnpv2.UnsupportedActionException,
pyupnpv2.XmlParsingException,
pyupnpv2.ConnectionErrorException,
pyupnpv2.ConnectionTimeoutException) as e:
- pass
- logger.error('"{}" : {}'.format(self.label, str(e)))
+ logger.error('"{}" : {}'.format(self.label, str(e)))
return None
def set_mute(self, mute):
@@ -196,8 +192,7 @@ def set_mute(self, mute):
pyupnpv2.XmlParsingException,
pyupnpv2.ConnectionErrorException,
pyupnpv2.ConnectionTimeoutException) as e:
- pass
- logger.error('"{}" : {}'.format(self.label, str(e)))
+ logger.error('"{}" : {}'.format(self.label, str(e)))
return None
def get_mime_types(self):
@@ -212,12 +207,12 @@ def get_mime_types(self):
return mime_types
except KeyError:
e = MissingAttributeException('get_protocol_info')
+ logger.error('"{}" : {}'.format(self.label, str(e)))
except (pyupnpv2.UnsupportedActionException,
pyupnpv2.XmlParsingException,
pyupnpv2.ConnectionErrorException,
pyupnpv2.ConnectionTimeoutException) as e:
- pass
- logger.error('"{}" : {}'.format(self.label, str(e)))
+ logger.error('"{}" : {}'.format(self.label, str(e)))
return None
def get_transport_state(self):
@@ -227,11 +222,11 @@ def get_transport_state(self):
return state
except KeyError:
e = MissingAttributeException('get_transport_state')
+ logger.error('"{}" : {}'.format(self.label, str(e)))
except (pyupnpv2.XmlParsingException,
pyupnpv2.ConnectionErrorException,
pyupnpv2.ConnectionTimeoutException) as e:
- pass
- logger.error('"{}" : {}'.format(self.label, str(e)))
+ logger.error('"{}" : {}'.format(self.label, str(e)))
return None
def get_position_info(self):
@@ -241,12 +236,12 @@ def get_position_info(self):
return state
except KeyError:
e = MissingAttributeException('get_position_info')
+ logger.error('"{}" : {}'.format(self.label, str(e)))
except (pyupnpv2.UnsupportedActionException,
pyupnpv2.XmlParsingException,
pyupnpv2.ConnectionErrorException,
pyupnpv2.ConnectionTimeoutException) as e:
- pass
- logger.error('"{}" : {}'.format(self.label, str(e)))
+ logger.error('"{}" : {}'.format(self.label, str(e)))
return None
def _update_current_state(self):
diff --git a/pulseaudio_dlna/plugins/dlna/ssdp/__init__.py b/pulseaudio_dlna/plugins/dlna/ssdp/__init__.py
index 5cef4942..5115f644 100644
--- a/pulseaudio_dlna/plugins/dlna/ssdp/__init__.py
+++ b/pulseaudio_dlna/plugins/dlna/ssdp/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,15 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import re
def _get_header_map(header):
header = re.findall(r"(?P.*?):(?P.*?)\n", header)
header = {
- k.strip().lower(): v.strip() for k, v in dict(header).items()
+ k.strip().lower(): v.strip() for k, v in list(dict(header).items())
}
return header
diff --git a/pulseaudio_dlna/plugins/dlna/ssdp/discover.py b/pulseaudio_dlna/plugins/dlna/ssdp/discover.py
index 5a52694f..33828bff 100644
--- a/pulseaudio_dlna/plugins/dlna/ssdp/discover.py
+++ b/pulseaudio_dlna/plugins/dlna/ssdp/discover.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import socket
import logging
import chardet
@@ -81,7 +79,7 @@ def search(self, ssdp_ttl=None, ssdp_mx=None, ssdp_amount=None):
thread.start()
for thread in threads:
thread.join()
- except:
+ except Exception:
traceback.print_exc()
logger.info('SSDPDiscover.search()')
@@ -117,7 +115,7 @@ def _search(self, host, ssdp_ttl, ssdp_mx, ssdp_amount):
def _send_discover(self, sock, ssdp_mx):
msg = self.MSEARCH_MSG.format(
- host=self.SSDP_ADDRESS, port=self.SSDP_PORT, mx=ssdp_mx)
+ host=self.SSDP_ADDRESS, port=self.SSDP_PORT, mx=ssdp_mx).encode()
if self.USE_SINGLE_SOCKET:
for addr in self.addresses:
sock.setsockopt(
diff --git a/pulseaudio_dlna/plugins/dlna/ssdp/listener.py b/pulseaudio_dlna/plugins/dlna/ssdp/listener.py
index 7f1663c4..350a9bc6 100644
--- a/pulseaudio_dlna/plugins/dlna/ssdp/listener.py
+++ b/pulseaudio_dlna/plugins/dlna/ssdp/listener.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,11 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
from gi.repository import GObject
-import SocketServer
+import socketserver
import logging
import socket
import struct
@@ -32,7 +30,7 @@
logger = logging.getLogger('pulseaudio_dlna.plugins.dlna.ssdp')
-class SSDPHandler(SocketServer.BaseRequestHandler):
+class SSDPHandler(socketserver.BaseRequestHandler):
SSDP_ALIVE = 'ssdp:alive'
SSDP_BYEBYE = 'ssdp:byebye'
@@ -57,7 +55,7 @@ def _decode(self, data):
for encoding in [guess['encoding'], 'utf-8', 'ascii']:
try:
return data.decode(encoding)
- except:
+ except Exception:
pass
logger.error('Could not decode SSDP packet.')
return ''
@@ -70,7 +68,7 @@ def _get_method(self, method_header):
return method_header.split(' ')[0]
-class SSDPListener(SocketServer.UDPServer):
+class SSDPListener(socketserver.UDPServer):
SSDP_ADDRESS = '239.255.255.250'
SSDP_PORT = 1900
@@ -89,7 +87,7 @@ def run(self, ttl=None):
return
self.allow_reuse_address = True
- SocketServer.UDPServer.__init__(
+ socketserver.UDPServer.__init__(
self, (self.host or '', self.SSDP_PORT), SSDPHandler)
self.socket.setsockopt(
socket.IPPROTO_IP,
@@ -148,5 +146,5 @@ def shutdown(self, *args):
class ThreadedSSDPListener(
- GobjectMainLoopMixin, SocketServer.ThreadingMixIn, SSDPListener):
+ GobjectMainLoopMixin, socketserver.ThreadingMixIn, SSDPListener):
pass
diff --git a/pulseaudio_dlna/plugins/renderer.py b/pulseaudio_dlna/plugins/renderer.py
index 3cb17cee..923a51fa 100644
--- a/pulseaudio_dlna/plugins/renderer.py
+++ b/pulseaudio_dlna/plugins/renderer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,12 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import re
import random
-import urlparse
-import urllib
+import urllib.parse
import functools
import logging
import base64
@@ -189,7 +186,7 @@ def codec(self):
missing_encoders = []
for codec in self.codecs:
- for identifier, encoder_type in codec.ENCODERS.items():
+ for identifier, encoder_type in list(codec.ENCODERS.items()):
encoder = encoder_type()
if encoder.binary not in missing_encoders:
missing_encoders.append(encoder.binary)
@@ -257,7 +254,7 @@ def stop(self):
raise NotImplementedError()
def add_mime_type(self, mime_type):
- for identifier, _type in pulseaudio_dlna.codecs.CODECS.iteritems():
+ for identifier, _type in pulseaudio_dlna.codecs.CODECS.items():
if _type.accepts(mime_type):
codec = _type(mime_type)
if codec not in self.codecs:
@@ -312,7 +309,7 @@ def set_rules_from_config(self, config):
codec_type = pulseaudio_dlna.codecs.CODECS[
codec_properties['identifier']]
codec = codec_type(codec_properties['mime_type'])
- for k, v in codec_properties.iteritems():
+ for k, v in codec_properties.items():
forbidden_attributes = ['mime_type', 'identifier', 'rules']
if hasattr(codec, k) and k not in forbidden_attributes:
setattr(codec, k, v)
@@ -338,12 +335,12 @@ def _encode_settings(self, settings, suffix=''):
port=server_port,
)
data_string = ','.join(
- ['{}="{}"'.format(k, v) for k, v in settings.iteritems()])
+ ['{}="{}"'.format(k, v) for k, v in settings.items()])
stream_name = '/{base_string}/{suffix}'.format(
- base_string=urllib.quote(base64.b64encode(data_string)),
+ base_string=urllib.parse.quote(base64.b64encode(data_string.encode())),
suffix=suffix,
)
- return urlparse.urljoin(base_url, stream_name)
+ return urllib.parse.urljoin(base_url, stream_name)
def get_stream_url(self):
settings = {
diff --git a/pulseaudio_dlna/pulseaudio.py b/pulseaudio_dlna/pulseaudio.py
index 5f3752c0..5960a408 100644
--- a/pulseaudio_dlna/pulseaudio.py
+++ b/pulseaudio_dlna/pulseaudio.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
from gi.repository import GObject
import sys
@@ -67,7 +65,7 @@ def _connect(self, signals):
dbus_interface='org.freedesktop.DBus.Properties')
self.fallback_sink = PulseSinkFactory.new(
self.bus, fallback_sink_path)
- except:
+ except Exception:
logger.info(
'Could not get default sink. Perhaps there is no one set?')
@@ -227,7 +225,7 @@ def dbus_server_lookup(self):
'org.PulseAudio.ServerLookup1',
'Address',
dbus_interface='org.freedesktop.DBus.Properties')
- return unicode(address)
+ return str(address)
except dbus.exceptions.DBusException:
return None
@@ -237,14 +235,14 @@ def get_modules(self):
stdout=subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode == 0:
- matches = re.findall(r'(\d+)\s+([\w-]+)(.*?)\n', stdout)
+ matches = re.findall(r'(\d+)\s+([\w-]+)(.*?)\n', stdout.decode())
return [match[1] for match in matches]
return None
def load_module(self, module_name, options=None):
command = ['pactl', 'load-module', module_name]
if options:
- for key, value in options.items():
+ for key, value in list(options.items()):
command.append('{}={}'.format(key, value))
process = subprocess.Popen(command, stdout=subprocess.PIPE)
stdout, stderr = process.communicate()
@@ -299,8 +297,8 @@ def new(self, bus, client_path):
icon_bytes = properties.get('application.icon_name', [])
binary_bytes = properties.get('application.process.binary', [])
return PulseClient(
- object_path=unicode(client_path),
- index=unicode(obj.Get('org.PulseAudio.Core1.Client', 'Index')),
+ object_path=str(client_path),
+ index=str(obj.Get('org.PulseAudio.Core1.Client', 'Index')),
name=self._convert_bytes_to_unicode(name_bytes),
icon=self._convert_bytes_to_unicode(icon_bytes),
binary=self._convert_bytes_to_unicode(binary_bytes),
@@ -352,9 +350,9 @@ def new(self, bus, module_path):
try:
obj = bus.get_object(object_path=module_path)
return PulseModule(
- object_path=unicode(module_path),
- index=unicode(obj.Get('org.PulseAudio.Core1.Module', 'Index')),
- name=unicode(obj.Get('org.PulseAudio.Core1.Module', 'Name')),
+ object_path=str(module_path),
+ index=str(obj.Get('org.PulseAudio.Core1.Module', 'Index')),
+ name=str(obj.Get('org.PulseAudio.Core1.Module', 'Name')),
)
except dbus.exceptions.DBusException:
logger.error(
@@ -400,13 +398,13 @@ def new(self, bus, object_path):
properties = obj.Get('org.PulseAudio.Core1.Device', 'PropertyList')
description_bytes = properties.get('device.description', [])
- module_path = unicode(
+ module_path = str(
obj.Get('org.PulseAudio.Core1.Device', 'OwnerModule'))
return PulseSink(
- object_path=unicode(object_path),
- index=unicode(obj.Get('org.PulseAudio.Core1.Device', 'Index')),
- name=unicode(obj.Get('org.PulseAudio.Core1.Device', 'Name')),
+ object_path=str(object_path),
+ index=str(obj.Get('org.PulseAudio.Core1.Device', 'Index')),
+ name=str(obj.Get('org.PulseAudio.Core1.Device', 'Name')),
label=self._convert_bytes_to_unicode(description_bytes),
module=PulseModuleFactory.new(bus, module_path),
)
@@ -444,7 +442,7 @@ def stream_client_names(self):
for stream in self.streams:
try:
names.append(stream.client.name)
- except:
+ except Exception:
names.append('?')
return names
@@ -498,13 +496,13 @@ class PulseStreamFactory(object):
def new(self, bus, stream_path):
try:
obj = bus.get_object(object_path=stream_path)
- client_path = unicode(
+ client_path = str(
obj.Get('org.PulseAudio.Core1.Stream', 'Client'))
return PulseStream(
- object_path=unicode(stream_path),
- index=unicode(obj.Get(
+ object_path=str(stream_path),
+ index=str(obj.Get(
'org.PulseAudio.Core1.Stream', 'Index')),
- device=unicode(obj.Get(
+ device=str(obj.Get(
'org.PulseAudio.Core1.Stream', 'Device')),
client=PulseClientFactory.new(bus, client_path),
)
@@ -634,7 +632,7 @@ def run(self):
def _on_new_message(self, fd, condition):
try:
message = self.pulse_queue.get_nowait()
- except:
+ except Exception:
return True
message_type = message.get('type', None)
diff --git a/pulseaudio_dlna/recorders.py b/pulseaudio_dlna/recorders.py
index fb3270a2..bd7ba281 100644
--- a/pulseaudio_dlna/recorders.py
+++ b/pulseaudio_dlna/recorders.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import pulseaudio_dlna.codecs
diff --git a/pulseaudio_dlna/rules.py b/pulseaudio_dlna/rules.py
index 31690d87..f9a494a2 100644
--- a/pulseaudio_dlna/rules.py
+++ b/pulseaudio_dlna/rules.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import functools
import logging
import inspect
@@ -44,9 +42,9 @@ def __eq__(self, other):
if type(other) is type:
return type(self) is other
try:
- if isinstance(other, basestring):
+ if isinstance(other, str):
return type(self) is RULES[other]
- except:
+ except Exception:
raise RuleNotFoundException(other)
return type(self) is type(other)
@@ -54,16 +52,16 @@ def __gt__(self, other):
if type(other) is type:
return type(self) > other
try:
- if isinstance(other, basestring):
+ if isinstance(other, str):
return type(self) > RULES[other]
- except:
+ except Exception:
raise RuleNotFoundException()
return type(self) > type(other)
def to_json(self):
attributes = []
d = {
- k: v for k, v in self.__dict__.iteritems()
+ k: v for k, v in iter(self.__dict__.items())
if k not in attributes
}
d['name'] = str(self)
@@ -122,11 +120,11 @@ def append(self, *args):
except KeyError:
raise RuleNotFoundException(name)
attributes = ['name']
- for k, v in arg.iteritems():
+ for k, v in arg.items():
if hasattr(rule, k) and k not in attributes:
setattr(rule, k, v)
self._add_rule(rule)
- elif isinstance(arg, basestring):
+ elif isinstance(arg, str):
try:
rule = RULES[arg]()
self._add_rule(rule)
@@ -155,4 +153,5 @@ def load_rules():
RULES[name] = _type
return None
+
load_rules()
diff --git a/pulseaudio_dlna/streamserver.py b/pulseaudio_dlna/streamserver.py
index fd42ac66..f431462f 100644
--- a/pulseaudio_dlna/streamserver.py
+++ b/pulseaudio_dlna/streamserver.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
from gi.repository import GObject
import re
@@ -27,14 +25,14 @@
import select
import sys
import base64
-import urllib
+import urllib.parse
import json
import os
import signal
import pkg_resources
-import BaseHTTPServer
-import SocketServer
-import Queue
+import http.server
+import socketserver
+import queue
import threading
import pulseaudio_dlna.encoders
@@ -49,7 +47,7 @@
PROTOCOL_VERSION_V11 = 'HTTP/1.1'
-class ProcessQueue(Queue.Queue):
+class ProcessQueue(queue.Queue):
def data(self):
data = self.get()
@@ -120,10 +118,10 @@ def terminate_processes(processes):
try:
os.kill(pid, signal.SIGTERM)
_pid, return_code = os.waitpid(pid, 0)
- except:
+ except Exception:
try:
os.kill(pid, signal.SIGKILL)
- except:
+ except Exception:
pass
chunk_size = self.CHUNK_SIZE
@@ -266,28 +264,29 @@ def __str__(self):
'\n'.join(
[' {}\n {}'.format(
path,
- ' '.join([str(s) for id, s in streams.items()]))
- for path, streams in self.streams.items()],
+ ' '.join(
+ [str(s) for id, s in list(streams.items())]))
+ for path, streams in list(self.streams.items())],
),
)
-class StreamRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+class StreamRequestHandler(http.server.BaseHTTPRequestHandler):
def __init__(self, *args):
try:
- BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args)
+ http.server.BaseHTTPRequestHandler.__init__(self, *args)
except IOError:
pass
def do_HEAD(self):
logger.debug('Got the following HEAD request:\n{header}'.format(
- header=json.dumps(self.headers.items(), indent=2)))
+ header=json.dumps(list(self.headers.items()), indent=2)))
item = self.get_requested_item()
self.handle_headers(item)
def do_GET(self):
logger.debug('Got the following GET request:\n{header}'.format(
- header=json.dumps(self.headers.items(), indent=2)))
+ header=json.dumps(list(self.headers.items()), indent=2)))
item = self.get_requested_item()
self.handle_headers(item)
if isinstance(item, pulseaudio_dlna.images.BaseImage):
@@ -344,7 +343,7 @@ def handle_headers(self, item):
header=json.dumps(headers, indent=2),
))
self.send_response(response_code)
- for name, value in headers.items():
+ for name, value in list(headers.items()):
self.send_header(name, value)
self.end_headers()
@@ -385,7 +384,8 @@ def get_requested_item(self):
def _decode_settings(self, path):
try:
data_quoted = re.findall(r'/(.*?)/', path)[0]
- data_string = base64.b64decode(urllib.unquote(data_quoted))
+ data_bytes = base64.b64decode(urllib.parse.unquote(data_quoted))
+ data_string = data_bytes.decode()
settings = {
k: v for k, v in re.findall('(.*?)="(.*?)",?', data_string)
}
@@ -402,7 +402,7 @@ def log_message(self, format, *args):
pass
-class StreamServer(SocketServer.TCPServer):
+class StreamServer(socketserver.TCPServer):
HOST = None
PORT = None
@@ -423,7 +423,7 @@ def run(self):
self.allow_reuse_address = True
self.daemon_threads = True
try:
- SocketServer.TCPServer.__init__(
+ socketserver.TCPServer.__init__(
self, (self.ip or '', self.port), StreamRequestHandler)
except socket.error:
logger.critical(
@@ -460,7 +460,7 @@ def serve_forever(self, poll_interval=0.5):
def _on_new_message(self, fd, condition):
try:
message = self.stream_queue.get_nowait()
- except:
+ except Exception:
return True
message_type = message.get('type', None)
@@ -486,5 +486,5 @@ def shutdown(self, *args):
class ThreadedStreamServer(
- GobjectMainLoopMixin, SocketServer.ThreadingMixIn, StreamServer):
+ GobjectMainLoopMixin, socketserver.ThreadingMixIn, StreamServer):
pass
diff --git a/pulseaudio_dlna/utils/encoding.py b/pulseaudio_dlna/utils/encoding.py
index 86b14adb..58a50c77 100644
--- a/pulseaudio_dlna/utils/encoding.py
+++ b/pulseaudio_dlna/utils/encoding.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
import sys
import locale
@@ -34,10 +32,10 @@ def __init__(self, var):
)
-def decode_default(bytes):
- if type(bytes) is not str:
- raise NotBytesException(bytes)
- guess = chardet.detect(bytes)
+def decode_default(in_bytes):
+ if type(in_bytes) is not bytes:
+ raise NotBytesException(in_bytes)
+ guess = chardet.detect(in_bytes)
encodings = {
'sys.stdout.encoding': sys.stdout.encoding,
'locale.getpreferredencoding': locale.getpreferredencoding(),
@@ -45,27 +43,27 @@ def decode_default(bytes):
'utf-8': 'utf-8',
'latin1': 'latin1',
}
- for encoding in encodings.values():
+ for encoding in list(encodings.values()):
if encoding and encoding != 'ascii':
try:
- return bytes.decode(encoding)
+ return in_bytes.decode(encoding)
except UnicodeDecodeError:
continue
try:
- return bytes.decode('ascii', errors='replace')
+ return in_bytes.decode('ascii', errors='replace')
except UnicodeDecodeError:
logger.error(
'Decoding failed using the following encodings: "{}"'.format(
','.join(
- ['{}:{}'.format(f, e) for f, e in encodings.items()]
+ ['{}:{}'.format(f, e) for f, e in list(encodings.items())]
)))
return 'Unknown'
-def _bytes2hex(bytes, seperator=':'):
- if type(bytes) is not str:
- raise NotBytesException(bytes)
- return seperator.join('{:02x}'.format(ord(b)) for b in bytes)
+def _bytes2hex(in_bytes, seperator=':'):
+ if type(in_bytes) is not bytes:
+ raise NotBytesException(in_bytes)
+ return seperator.join('{:02x}'.format(ord(b)) for b in in_bytes)
def _hex2bytes(hex, seperator=':'):
diff --git a/pulseaudio_dlna/utils/git.py b/pulseaudio_dlna/utils/git.py
index b2e911fe..052adba1 100644
--- a/pulseaudio_dlna/utils/git.py
+++ b/pulseaudio_dlna/utils/git.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import os
GIT_DIRECTORY = '../../.git/'
diff --git a/pulseaudio_dlna/utils/network.py b/pulseaudio_dlna/utils/network.py
index 47fbbee6..9dc8f79e 100644
--- a/pulseaudio_dlna/utils/network.py
+++ b/pulseaudio_dlna/utils/network.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,8 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import netifaces
import traceback
import socket
@@ -31,7 +29,7 @@ def default_ipv4():
try:
default_if = netifaces.gateways()['default'][netifaces.AF_INET][1]
return netifaces.ifaddresses(default_if)[netifaces.AF_INET][0]['addr']
- except:
+ except Exception:
traceback.print_exc()
return None
diff --git a/pulseaudio_dlna/utils/psutil.py b/pulseaudio_dlna/utils/psutil.py
index 011e9766..1259988f 100644
--- a/pulseaudio_dlna/utils/psutil.py
+++ b/pulseaudio_dlna/utils/psutil.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,9 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import absolute_import
-from __future__ import unicode_literals
-
import logging
import psutil
diff --git a/pulseaudio_dlna/utils/subprocess.py b/pulseaudio_dlna/utils/subprocess.py
index 20091632..643dff39 100644
--- a/pulseaudio_dlna/utils/subprocess.py
+++ b/pulseaudio_dlna/utils/subprocess.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,9 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import absolute_import
-from __future__ import unicode_literals
-
from gi.repository import GObject
import subprocess
@@ -75,7 +72,7 @@ def __init__(self, *args, **kwargs):
pipe, GObject.IO_IN | GObject.IO_PRI, self._on_new_data)
def _on_new_data(self, fd, condition):
- line = fd.readline()
+ line = fd.readline().decode('utf-8')
sys.stdout.write(line)
sys.stdout.flush()
return True
diff --git a/pulseaudio_dlna/workarounds.py b/pulseaudio_dlna/workarounds.py
index c82b3bcd..7a74a78d 100644
--- a/pulseaudio_dlna/workarounds.py
+++ b/pulseaudio_dlna/workarounds.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -15,12 +15,10 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
-
import logging
from lxml import etree
import requests
-import urlparse
+import urllib.parse
import traceback
@@ -115,7 +113,7 @@ def __init__(self, xml):
if (not self._detect_remotecontrolinterface(xml)):
raise Exception()
self.enabled = True
- except:
+ except Exception:
logger.warning(
'The YamahaWorkaround initialization failed. '
'Automatic source switching will not be enabled'
@@ -170,7 +168,7 @@ def _parse_xml(self, xml):
control_url = xml_root.find(self.MR_YAMAHA_CONTROLURL_PATH, namespaces)
if ((url_base is None) or (control_url is None)):
return False
- ip, port = urlparse.urlparse(url_base.text).netloc.split(':')
+ ip, port = urllib.parse.urlparse(url_base.text).netloc.split(':')
if ((not ip) or (not port)):
return False
diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh
index 487bf5b6..4d4b0a35 100755
--- a/scripts/bootstrap.sh
+++ b/scripts/bootstrap.sh
@@ -59,31 +59,29 @@ function install_fonts() {
function install_dev() {
sudo apt-get install \
- python2.7 \
- python-pip \
- python-setuptools \
- python-dbus \
- python-docopt \
- python-requests \
- python-setproctitle \
- python-gi \
- python-protobuf \
- python-notify2 \
- python-psutil \
- python-concurrent.futures \
- python-chardet \
- python-netifaces \
- python-netaddr \
- python-pyroute2 \
- python-lxml \
- python-zeroconf \
+ python3 \
+ python3-pip \
+ python3-setuptools \
+ python3-dbus \
+ python3-docopt \
+ python3-requests \
+ python3-setproctitle \
+ python3-gi \
+ python3-notify2 \
+ python3-psutil \
+ python3-chardet \
+ python3-netifaces \
+ python3-netaddr \
+ python3-pyroute2 \
+ python3-lxml \
+ python3-pychromecast \
vorbis-tools \
sox \
lame \
flac \
opus-tools \
pavucontrol \
- virtualenv python-dev git-core
+ virtualenv python3-dev git-core
[[ -d ~/pulseaudio-dlna ]] || \
git clone https://github.com/masmu/pulseaudio-dlna.git ~/pulseaudio-dlna
}
diff --git a/scripts/chromecast-beam.py b/scripts/chromecast-beam.py
index 5b594f37..8af1bfe6 100755
--- a/scripts/chromecast-beam.py
+++ b/scripts/chromecast-beam.py
@@ -30,6 +30,7 @@
[--transcode-audio ]
[--start-time=]
[--sub-titles]
+ [--debug]
Options:
@@ -110,8 +111,6 @@
'''
-from __future__ import unicode_literals
-
import docopt
import logging
import os
@@ -125,11 +124,12 @@
import shutil
import traceback
import re
-import SimpleHTTPServer
-import SocketServer
+import errno
+import http.server
+import socketserver
+import pychromecast
import pulseaudio_dlna.utils.network
-import pulseaudio_dlna.plugins.chromecast.pycastv2 as pycastv2
logger = logging.getLogger('chromecast-beam')
@@ -140,12 +140,18 @@
class StoppableThread(threading.Thread):
+ MODE_IMMEDIATE = 'immediate'
+
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
self.stop_event = threading.Event()
+ self.stop_mode = None
- def stop(self):
- self.stop_event.set()
+ def stop(self, immediate=False):
+ if not self.is_stopped:
+ if immediate:
+ self.stop_mode = immediate
+ self.stop_event.set()
def wait(self):
self.stop_event.wait()
@@ -166,34 +172,32 @@ def __init__(self, chromecast_host, media_url, mime_type=None,
self.media_url = media_url
self.mime_type = mime_type or 'video/mp4'
self.desired_volume = 1.0
+ self.timeout = 5
def run(self):
- def play(host, port, url, mime_type, timeout=5):
- cast = pycastv2.MediaPlayerController(host, port, timeout)
- cast.load(url, mime_type=mime_type)
- logger.info(
- 'Chromecast status: Volume {volume} ({muted})'.format(
- muted='Muted' if cast.is_muted else 'Unmuted',
- volume=cast.volume * 100))
- if cast.is_muted:
- logger.info('Unmuting Chromecast ...')
- cast.set_mute(False)
- if cast.volume != self.desired_volume:
- logger.info('Setting Chromecast volume to {} ...'.format(
- self.desired_volume * 100))
- cast.set_volume(self.desired_volume)
-
- def stop(host, port, timeout=5):
- cast = pycastv2.MediaPlayerController(host, port, timeout)
- cast.stop_application()
- cast.disconnect_application()
-
- play(self.chromecast_host, self.PORT, self.media_url, self.mime_type)
+ chromecast = pychromecast.Chromecast(
+ host=chromecast_host, port=self.PORT, timeout=self.timeout)
+
+ chromecast.media_controller.play_media(
+ self.media_url,
+ content_type=self.mime_type,
+ stream_type=pychromecast.controllers.media.STREAM_TYPE_LIVE,
+ autoplay=True,
+ )
+
+ logger.info(
+ 'Chromecast status: Volume {volume} ({muted})'.format(
+ muted='Muted'
+ if chromecast.media_controller.status.volume_muted
+ else 'Unmuted',
+ volume=chromecast.media_controller.status.volume_level * 100))
+
self.wait()
- stop(self.chromecast_host, self.PORT)
+ if self.stop_mode != StoppableThread.MODE_IMMEDIATE:
+ chromecast.quit_app()
-class ThreadedHTTPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+class ThreadedHTTPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
allow_reuse_address = True
daemon_threads = True
@@ -207,7 +211,7 @@ def __init__(self, file_uri, bind_host, request_host, port, handler=None):
self.handler = handler or DefaultRequestHandler
os.chdir(self.file_path)
- SocketServer.TCPServer.__init__(
+ socketserver.TCPServer.__init__(
self, (self.bind_host, self.port), self.handler)
@property
@@ -222,6 +226,8 @@ def handle_error(self, *args, **kwargs):
class EncoderSettings(object):
+ ENCODER_BINARY = '/usr/bin/vlc'
+
TRANSCODE_USED = False
TRANSCODE_VIDEO_CODEC = None
@@ -238,10 +244,13 @@ class EncoderSettings(object):
AUDIO_TRACK = None
AUDIO_TRACK_ID = None
+ TRANSCODE_VIDEO_DEFAULTS_SET = False
TRANSCODE_VIDEO_CODEC_DEF = 'h264'
- TRANSCODE_VIDEO_BITRATE_DEF = 800
- TRANSCODE_VIDEO_SCALE_DEF = 'Automatisch'
+ TRANSCODE_VIDEO_BITRATE_DEF = 3000
+ TRANSCODE_VIDEO_SCALE_DEF = 'Auto'
+ TRANSCODE_VIDEO_MUX_DEF = 'mkv'
+ TRANSCODE_AUDIO_DEFAULTS_SET = False
TRANSCODE_AUDIO_CODEC_DEF = 'mp3'
TRANSCODE_AUDIO_BITRATE_DEF = 192
TRANSCODE_AUDIO_CHANNELS_DEF = 2
@@ -259,7 +268,7 @@ def _decode_settings(cls, settings):
k, v = setting.split('=')
data[k] = v
return data
- except:
+ except Exception:
return {}
@classmethod
@@ -276,6 +285,9 @@ def _apply_options(cls, options, option_map):
@classmethod
def set_video_defaults(cls):
+ if cls.TRANSCODE_VIDEO_DEFAULTS_SET:
+ return
+ cls.TRANSCODE_VIDEO_DEFAULTS_SET = True
cls._apply_option(
'TRANSCODE_VIDEO_CODEC', cls.TRANSCODE_VIDEO_CODEC_DEF)
cls._apply_option(
@@ -285,6 +297,9 @@ def set_video_defaults(cls):
@classmethod
def set_audio_defaults(cls):
+ if cls.TRANSCODE_AUDIO_DEFAULTS_SET:
+ return
+ cls.TRANSCODE_AUDIO_DEFAULTS_SET = True
cls._apply_option(
'TRANSCODE_AUDIO_CODEC', cls.TRANSCODE_AUDIO_CODEC_DEF)
cls._apply_option(
@@ -297,6 +312,29 @@ def set_audio_defaults(cls):
@classmethod
def set_options(cls, options):
used = False
+ if options.get('--start-time', None):
+ used = True
+ cls.TRANSCODE_USED = True
+ cls.set_video_defaults()
+ cls._apply_option('START_TIME', options['--start-time'])
+ if options.get('--sub-titles', None):
+ used = True
+ cls._apply_option('SUB_TITLES', True)
+ if options.get('--audio-track', None):
+ used = True
+ cls.TRANSCODE_USED = True
+ cls.set_audio_defaults()
+ cls._apply_option('AUDIO_TRACK', options['--audio-track'])
+ if options.get('--audio-language', None):
+ used = True
+ cls.TRANSCODE_USED = True
+ cls.set_audio_defaults()
+ cls._apply_option('AUDIO_LANGUAGE', options['--audio-language'])
+ if options.get('--audio-track-id', None):
+ used = True
+ cls.TRANSCODE_USED = True
+ cls.set_audio_defaults()
+ cls._apply_option('AUDIO_TRACK_ID', options['--audio-track-id'])
if options.get('--transcode', None):
if options['--transcode'] in ['both', 'audio', 'video']:
used = True
@@ -339,21 +377,6 @@ def set_options(cls, options):
}
cls.set_audio_defaults()
cls._apply_options(options['--transcode-audio'], option_map)
- if options.get('--audio-language', None):
- used = True
- cls._apply_option('AUDIO_LANGUAGE', options['--audio-language'])
- if options.get('--audio-track', None):
- used = True
- cls._apply_option('AUDIO_TRACK', options['--audio-track'])
- if options.get('--audio-track-id', None):
- used = True
- cls._apply_option('AUDIO_TRACK_ID', options['--audio-track-id'])
- if options.get('--start-time', None):
- used = True
- cls._apply_option('START_TIME', options['--start-time'])
- if options.get('--sub-titles', None):
- used = True
- cls._apply_option('SUB_TITLES', True)
return used
@@ -387,7 +410,9 @@ def _transcode_cmd_str(cls):
@classmethod
def command(cls, file_path):
command = [
- 'cvlc', file_path,
+ cls.ENCODER_BINARY,
+ '--intf', 'dummy',
+ file_path,
':play-and-exit',
':no-sout-all',
]
@@ -402,51 +427,58 @@ def command(cls, file_path):
if cls.TRANSCODE_USED:
return command + [
':sout=#transcode{' + cls._transcode_cmd_str() + '}'
- ':std{access=file,mux=mkv,dst=-}',
+ ':std{access=file,mux=' + cls.TRANSCODE_VIDEO_MUX_DEF + ',dst=-}',
]
else:
return command + [
- ':sout=#file{access=file,mux=mkv,dst=-}',
+ ':sout=#file{access=file,mux=' + cls.TRANSCODE_VIDEO_MUX_DEF + ',dst=-}'
]
-class DefaultRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+class DefaultRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self, *args, **kwargs):
logger.info('Serving unmodified media file to {} ...'.format(
self.client_address[0]))
- SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self, *args, **kwargs)
+ http.server.SimpleHTTPRequestHandler.do_GET(self, *args, **kwargs)
def log_request(self, code='-', size='-'):
logger.info('{} - {}'.format(self.requestline, code))
-class TranscodeRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+class TranscodeRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
- client_address = self.client_address[0]
- logger.info('Serving transcoded media file to {} ...'.format(
- client_address))
+ try:
+ client_address = self.client_address[0]
+ logger.info('Serving transcoded media file to {} ...'.format(
+ client_address))
- self.send_head()
- path = self.translate_path(self.path)
- command = VLCEncoderSettings.command(path)
- logger.info('Launching {}'.format(command))
+ self.send_head()
+ path = self.translate_path(self.path)
+ command = VLCEncoderSettings.command(path)
+ logger.info('Launching {}'.format(command))
- try:
with open(os.devnull, 'w') as dev_null:
encoder_process = subprocess.Popen(
- command, stdout=subprocess.PIPE, stderr=dev_null)
+ command,
+ stdout=subprocess.PIPE,
+ stderr=sys.stdout.fileno())
shutil.copyfileobj(encoder_process.stdout, self.wfile)
- except:
- logger.info('Connection from {} closed.'.format(client_address))
- logger.debug(traceback.format_exc())
+ except IOError as e:
+ if e.errno == errno.EPIPE:
+ logger.info(
+ 'Connection from {} closed.'.format(client_address))
+ else:
+ traceback.print_exc()
+ except Exception:
+ traceback.print_exc()
finally:
pid = encoder_process.pid
logger.info('Terminating process {}'.format(pid))
try:
os.kill(pid, signal.SIGKILL)
- except:
+ except Exception:
pass
def log_request(self, code='-', size='-'):
@@ -463,7 +495,7 @@ def get_external_ip():
# Local pulseaudio-dlna installations running in a virutalenv should run this
# script as module:
-# python -m scripts/chromecast-beam 192.168.1.10 ~/videos/test.mkv
+# python3 -m scripts.chromecast-beam 192.168.1.10 ~/videos/test.mkv
if __name__ == "__main__":
@@ -531,7 +563,7 @@ def get_external_ip():
try:
http_server.serve_forever()
except KeyboardInterrupt:
- pass
- finally:
chromecast_thread.stop()
+ finally:
+ chromecast_thread.stop(immediate=True)
chromecast_thread.join()
diff --git a/scripts/fritzbox-device-sharing.py b/scripts/fritzbox-device-sharing.py
index a6cd6cd5..f1de3bba 100755
--- a/scripts/fritzbox-device-sharing.py
+++ b/scripts/fritzbox-device-sharing.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# -*- coding: utf-8 -*-
# This file is part of pulseaudio-dlna.
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
+
import requests
import sys
@@ -94,7 +94,7 @@ def discover_devices(self):
def _discover_devices(self):
holder = pulseaudio_dlna.holder.Holder(self.PLUGINS)
holder.search(ttl=self.TIMEOUT)
- return holder.devices.values()
+ return list(holder.devices.values())
ip_detector = IPDetector()
@@ -113,5 +113,5 @@ def _discover_devices(self):
for device in dlna_discover.devices:
link = device.upnp_device.access_url.replace(
device.ip, ip_detector.public_ip)
- print(SHARE_PATTERN.format(
- name=device.name, ip=device.ip, port=device.port, link=link))
+ print((SHARE_PATTERN.format(
+ name=device.name, ip=device.ip, port=device.port, link=link)))
diff --git a/scripts/radio.py b/scripts/radio.py
index 2483e073..2f6d4b20 100755
--- a/scripts/radio.py
+++ b/scripts/radio.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# -*- coding: utf-8 -*-
# This file is part of pulseaudio-dlna.
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with pulseaudio-dlna. If not, see .
-from __future__ import unicode_literals
+
import requests
import logging
@@ -101,7 +101,7 @@ def _get_device(self, name, flavour=None):
return None
def _get_codec(self, url):
- for identifier, _type in pulseaudio_dlna.codecs.CODECS.iteritems():
+ for identifier, _type in pulseaudio_dlna.codecs.CODECS.items():
codec = _type()
if url.endswith(codec.suffix):
return codec
@@ -118,14 +118,14 @@ def _discover_devices(self):
holder = pulseaudio_dlna.holder.Holder(self.PLUGINS)
holder.search(ttl=5)
logger.info('Found the following devices:')
- for udn, device in holder.devices.items():
+ for udn, device in list(holder.devices.items()):
logger.info(' - "{name}" ({flavour})'.format(
name=device.name, flavour=device.flavour))
- return holder.devices.values()
+ return list(holder.devices.values())
# Local pulseaudio-dlna installations running in a virutalenv should run this
# script as module:
-# python -m scripts/radio [--list | --stop]
+# python3 -m scripts/radio [--list | --stop]
args = sys.argv[1:]
rl = RadioLauncher()
diff --git a/setup.py b/setup.py
index 8edc44da..40c82ac0 100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# This file is part of pulseaudio-dlna.
@@ -29,27 +29,27 @@
platforms="Debian GNU/Linux",
classifiers=[
"Development Status :: 4 - Beta",
- "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
"Environment :: Console",
"Topic :: Multimedia :: Sound/Audio",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
],
- version='0.5.2',
+ version='0.6.0',
py_modules=[],
packages=setuptools.find_packages(),
install_requires=[
"docopt >= 0.6.1",
"requests >= 2.2.1",
- "setproctitle >= 1.0.1",
- "protobuf >= 2.5.0",
+ "setproctitle >= 1.1.10",
"notify2 >= 0.3",
- "psutil >= 1.2.1",
- "futures >= 2.1.6",
- "chardet >= 2.0.1",
+ "psutil >= 5.4.7",
+ "chardet >= 3.0.4",
"pyroute2 >= 0.3.5",
- "netifaces >= 0.8",
+ "netifaces >= 0.10.0",
"lxml >= 3",
- "zeroconf >= 0.17.4",
+ "pychromecast >= 2.3.0",
+ "PyGObject >= 3.3.0",
+ "dbus-python >= 1.0.0",
],
entry_points={
"console_scripts": [