From f92e1076d912e248f95667f6bf5caf1715d4502a Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:31:53 +0200 Subject: [PATCH 01/10] remove old project structure --- LICENSE => LICENSE.txt | 0 __init__.py | 0 acestream_engine.py | 215 --------------------------------------- acestream_launcher.py | 221 ----------------------------------------- acestream_player.py | 59 ----------- install.sh | 21 ---- 6 files changed, 516 deletions(-) rename LICENSE => LICENSE.txt (100%) delete mode 100644 __init__.py delete mode 100644 acestream_engine.py delete mode 100755 acestream_launcher.py delete mode 100644 acestream_player.py delete mode 100755 install.sh diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/acestream_engine.py b/acestream_engine.py deleted file mode 100644 index 4febcc7..0000000 --- a/acestream_engine.py +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- - -"""AceStream Engine: Communicate with the AceStream Engine HTTP API""" - -import os -import json -import time -import signal -import hashlib -import threading -import subprocess - -try: - import urllib.request as request -except ImportError: - import urllib as request - - -class AcestreamEngine(object): - """AceStream Engine""" - - _events = [] - - def __init__(self, host='127.0.0.1', port='6878'): - self.host = host - self.port = port - self.live = False - self.poll = True - - @property - - def running(self): - """Check if AceStream Engine is running""" - - status_url = self.get_url('webui/api/service', method='get_version', format='json') - req_output = self.request(status_url) - output_res = req_output.get('result', False) - - return output_res is not False - - def connect(self, event_name, callback_fn): - """Register event and callback function""" - - self._events.append({ 'event_name': event_name, 'callback_fn': callback_fn }) - - def emit(self, event_name, *callback_args): - """Emit event and execute callback function""" - - for event in self._events: - if event['event_name'] == event_name: - event['callback_fn'](*callback_args) - - def timeout(self, timeout, attribute, message): - """Wait for attribute to become true in given time""" - - while timeout > 0 and not getattr(self, attribute): - time.sleep(1) - timeout = timeout - 1 - - if timeout == 0: - self.emit('message', message) - self.emit('error') - - def request(self, url): - """Send engine API request""" - - try: - response = request.urlopen(url) - return json.loads(response.read()) - except (IOError, ValueError): - return {} - - def get_url(self, query_path, **params): - """Get engine API url""" - - query_params = ['%s=%s' % (i, params[i]) for i in params.keys()] - query_params = '&'.join(query_params) - - return 'http://%s:%s/%s?%s' % (self.host, self.port, query_path, query_params) - - def get_stream_url(self, url): - """Get engine API stream url""" - - if url.startswith('http'): - stream_uid = hashlib.sha1(url.encode('utf-8')).hexdigest() - query_args = { 'url': url } - else: - stream_pid = url.split('://')[-1] - stream_uid = hashlib.sha1(stream_pid.encode('utf-8')).hexdigest() - query_args = { 'id': stream_pid } - - return self.get_url('ace/getstream', format='json', sid=stream_uid, **query_args) - - def open_request(self, url): - """Open URL request on engine API""" - - req_output = self.request(self.get_stream_url(url)) - output_res = req_output.get('response', False) - output_err = req_output.get('error', False) - - if output_err or not output_res: - return - - for key in output_res.keys(): - setattr(self, key, output_res[key]) - - def close_request(self): - """Close request on engine API""" - - if hasattr(self, 'command_url'): - stop_url = self.get_url(self.command_url, method='stop') - self.request(stop_url) - - def update_stream_stats(self): - """Update stream statistics""" - - req_output = self.request(self.stat_url) - output_res = req_output.get('response', False) - output_err = req_output.get('error', False) - - if output_err or not output_res: - return - - self.available = True - - for key in output_res.keys(): - setattr(self, key, output_res[key]) - - if self.status == 'check': - return - - if self.downloaded == 0 and self.speed_down == 0: - self.status = 'prebuf' - - if not self.live and self.status == 'dl': - self.live = True - time.sleep(2) - - def poll_stream_stats(self): - """Update stream statistics""" - - while self.poll: - time.sleep(1) - self.update_stream_stats() - self.emit('stats', self) - - def poll_stats(self): - """Run stream stats watcher in thread""" - - thread = threading.Thread(target=self.poll_stream_stats) - thread.setDaemon(True) - thread.start() - - def run_engine(self, args, kwargs): - """Start AceStream Engine process""" - - try: - self.engine = subprocess.Popen(args, preexec_fn=os.setsid, **kwargs) - self.emit('message', 'running') - self.emit('running') - - self.engine.communicate() - delattr(self, 'engine') - - self.emit('message', 'exit') - self.emit('exit') - except OSError: - self.emit('message', 'noengine') - self.emit('error') - - def start_engine(self, command='acestreamengine --client-console', kwargs={}): - """Start AceStream Engine""" - - if self.running: - self.emit('message', 'running') - self.emit('running') - else: - cmargs = command.split() - thread = threading.Thread(target=self.run_engine, args=[cmargs, kwargs]) - - thread.start() - - def stop_engine(self): - """Stop AceStream Engine""" - - if hasattr(self, 'engine'): - os.killpg(os.getpgid(self.engine.pid), signal.SIGTERM) - - def open_stream(self, url, emit_stats=False, timeout=30): - """Open AceStream URL""" - - self.emit('message', 'connecting') - self.timeout(timeout, 'running', 'noconnect') - - self.emit('message', 'waiting') - self.open_request(url) - - if not hasattr(self, 'playback_url'): - self.emit('message', 'unavailable') - self.emit('error') - - self.available = False - self.poll_stats() - - self.poll = emit_stats - self.timeout(timeout, 'available', 'unavailable') - - self.emit('message', 'playing') - self.emit('playing', self.playback_url) - - def close_stream(self): - """Close current stream""" - - self.poll = False - self.close_request() diff --git a/acestream_launcher.py b/acestream_launcher.py deleted file mode 100755 index eac5076..0000000 --- a/acestream_launcher.py +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Acestream Launcher: Open acestream links with any media player""" - -import os -import sys -import time -import argparse -import subprocess - -try: - import configparser -except ImportError: - import ConfigParser as configparser - -from acestream_engine import AcestreamEngine -from acestream_player import AcestreamPlayer - - -class AcestreamLauncher(object): - """Acestream Launcher""" - - engine = None - player = None - exiting = False - - def __init__(self): - self.atty = sys.stdin.isatty() - self.opts = self.read_config() - - parser = argparse.ArgumentParser( - prog='acestream-launcher', - description='Open acestream links with any media player' - ) - parser.add_argument( - 'url', - metavar='URL', - help='the acestream url to play' - ) - parser.add_argument( - '-e', '--engine', - help='the engine command to use (default: acestreamengine --client-console)', - default=self.get_option('engine') - ) - parser.add_argument( - '-p', '--player', - help='the media player command to use (default: mpv)', - default=self.get_option('player') - ) - parser.add_argument( - '-t', '--timeout', - help='time in seconds to wait for stream playback (default: 30)', - default=self.get_option('timeout', 'getint') - ) - parser.add_argument( - '-v', '--verbose', - help='show engine and media player output in console', - action='store_true', - default=self.get_option('verbose', 'getboolean') - ) - - self.args = parser.parse_args() - self.stdo = { 'stdout': self.output, 'stderr': self.output } - - @property - - def output(self): - return None if self.args.verbose else subprocess.PIPE - - @property - - def notifier(self): - """Check if libnotify is available""" - - if hasattr(self, 'libnotify'): - return self.libnotify - - try: - subprocess.call(['notify-send', '-v'], **self.stdo) - self.libnotify = True - except OSError: - self.libnotify = False - - return self.libnotify - - def read_config(self): - """Read configuration file""" - - config = configparser.RawConfigParser({ - 'engine': 'acestreamengine --client-console', - 'player': 'mpv', - 'timeout': '30', - 'verbose': 'False' - }) - - config.add_section('tty') - config.add_section('browser') - config.read(os.path.expanduser('~/.config/acestream-launcher/config')) - - return config - - def get_option(self, option, method='get'): - """Get configuration option""" - - section = 'tty' if self.atty else 'browser' - return getattr(self.opts, method)(section, option) - - def write(self, message): - """Write message to stdout""" - - sys.stdout.write("\x1b[2K\r%s" % message) - sys.stdout.flush() - - def notify(self, message): - """Show player status notifications""" - - messages = { - 'running': 'Acestream engine running...', - 'noengine': 'Acestream engine not found in provided path!', - 'connecting': 'Connecting to Acestream engine...', - 'noconnect': 'Cannot connect to Acestream engine!', - 'noplayer': 'Media player not found in provided path!', - 'waiting': 'Waiting for stream response...', - 'playing': 'Streaming started, launching player...', - 'unavailable': 'Stream unavailable!' - } - - message = messages.get(message, None) - - if not message or self.exiting: - return - - if self.atty: - self.write(message) - - if not self.atty and self.notifier: - name = 'Acestream Launcher' - icon = self.args.player.split()[0] - args = ['notify-send', '-h', 'int:transient:1', '-i', icon, name, message] - - subprocess.call(args, **self.stdo) - - def stats(self, engine): - """Print stream statistics""" - - if self.exiting: - return - - labels = { 'dl': 'playing', 'prebuf': 'buffering' } - status = labels[engine.status] - - labels = 'down: %.1f kb/s up: %.1f kb/s peers: %d' - sstats = labels % (engine.speed_down, engine.speed_up, engine.peers) - - if engine.is_live: - label = 'LIVE - status: %s %s' - stats = (status, sstats) - else: - label = 'VOD - status: %s total: %.2f%% %s' - stats = (status, engine.total_progress, sstats) - - self.write(label % stats) - - def start_stream(self): - """Strart streaming""" - - self.engine.open_stream(self.args.url, self.atty, int(self.args.timeout)) - - def start_player(self, url): - """Start media player""" - - self.player = AcestreamPlayer() - - self.player.connect('message', self.notify) - self.player.connect('error', self.quit) - self.player.connect('exit', self.quit) - - self.player.start_player(url=url, command=self.args.player, kwargs=self.stdo) - - def run(self): - """Start acestream and media player""" - - self.engine = AcestreamEngine() - - self.engine.connect('message', self.notify) - self.engine.connect('stats', self.stats) - self.engine.connect('error', self.quit) - self.engine.connect('exit', self.quit) - - self.engine.connect('running', self.start_stream) - self.engine.connect('playing', self.start_player) - - self.engine.start_engine(command=self.args.engine, kwargs=self.stdo) - - while self.engine or self.player: - time.sleep(1) - - def quit(self): - """Stop acestream and media player""" - - if not self.exiting: - self.exiting = True - print('\n\nExiting...') - - if self.player: - self.player.stop_player() - self.player = None - - if self.engine: - self.engine.close_stream() - self.engine.stop_engine() - self.engine = None - - -if __name__ == '__main__': - try: - launcher = AcestreamLauncher() - launcher.run() - except KeyboardInterrupt: - launcher.quit() diff --git a/acestream_player.py b/acestream_player.py deleted file mode 100644 index 42b589a..0000000 --- a/acestream_player.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- - -"""AceStream Player: Play stream URL with external media player""" - -import os -import signal -import threading -import subprocess - - -class AcestreamPlayer(object): - """AceStream Player""" - - _events = [] - - def connect(self, event_name, callback_fn): - """Register event and callback function""" - - self._events.append({ 'event_name': event_name, 'callback_fn': callback_fn }) - - def emit(self, event_name, *callback_args): - """Emit event and execute callback function""" - - for event in self._events: - if event['event_name'] == event_name: - event['callback_fn'](*callback_args) - - def run_player(self, args, kwargs): - """Start media player process""" - - try: - self.player = subprocess.Popen(args, preexec_fn=os.setsid, **kwargs) - self.emit('message', 'start') - self.emit('start') - - self.player.communicate() - delattr(self, 'player') - - self.emit('message', 'exit') - self.emit('exit') - except OSError: - self.emit('message', 'noplayer') - self.emit('error') - - def start_player(self, url, command='mpv', kwargs={}): - """Start media player""" - - cmargs = command.split() - cmargs.append(url) - - thread = threading.Thread(target=self.run_player, args=[cmargs, kwargs]) - thread.setDaemon(True) - thread.start() - - def stop_player(self): - """Stop media player""" - - if hasattr(self, 'player'): - os.killpg(os.getpgid(self.player.pid), signal.SIGTERM) diff --git a/install.sh b/install.sh deleted file mode 100755 index 7f4c20a..0000000 --- a/install.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -pkgname=acestream-launcher -pkgfile=acestream_launcher - -install() { - mkdir -p "/opt/$pkgname" - - cp *.py /opt/$pkgname - cp $pkgname.desktop /opt/$pkgname/$pkgname.desktop - - update-desktop-database "/opt/$pkgname" - - ln -s "/opt/$pkgname/$pkgfile.py" "/usr/bin/$pkgname" - mv "/opt/$pkgname/$pkgname.desktop" "/usr/share/applications/$pkgname.desktop" -} - -if [ "$EUID" -ne 0 ] - then echo "Please run as root" - else install -fi From 3f2a2fe7f8b4f0fda6269f35bc3b02e6b47c6bea Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:33:16 +0200 Subject: [PATCH 02/10] create pypi folder structure and add setup files --- acestream_launcher/__init__.py | 1 + setup.cfg | 5 +++++ setup.py | 35 ++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 acestream_launcher/__init__.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/acestream_launcher/__init__.py b/acestream_launcher/__init__.py new file mode 100644 index 0000000..4055aa8 --- /dev/null +++ b/acestream_launcher/__init__.py @@ -0,0 +1 @@ +name = 'acestream_launcher' diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8bab7e3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[metadata] +license_file = LICENSE.txt + +[bdist_wheel] +universal=1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..470c62a --- /dev/null +++ b/setup.py @@ -0,0 +1,35 @@ +import setuptools + +with open('README.md', 'r') as fh: + long_description = fh.read() + +setuptools.setup( + name='acestream-launcher', + version='2.0.0', + author='Jonian Guveli', + author_email='jonian@hardpixel.eu', + description='Open AceStream links with a Media Player of your choice', + long_description=long_description, + long_description_content_type='text/markdown', + url='https://github.com/jonian/acestream-launcher', + packages=setuptools.find_packages(), + data_files=[ + ('share/applications', ['acestream-launcher.desktop']) + ], + install_requires=[ + 'acestream>=0.1.3' + ], + classifiers=[ + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Operating System :: POSIX :: Linux' + ], + project_urls={ + 'Bug Reports': 'https://github.com/jonian/acestream-launcher/issues', + 'Source': 'https://github.com/jonian/acestream-launcher', + }, + entry_points={ + 'console_scripts': ['acestream-launcher = acestream_launcher.launcher:main'] + } +) From 3ababf32995e635ad4bc7b0385c5324bf1c47a26 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:33:40 +0200 Subject: [PATCH 03/10] add config handler class --- acestream_launcher/config.py | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 acestream_launcher/config.py diff --git a/acestream_launcher/config.py b/acestream_launcher/config.py new file mode 100644 index 0000000..d56cb56 --- /dev/null +++ b/acestream_launcher/config.py @@ -0,0 +1,39 @@ +import os +import sys + +try: + import configparser +except ImportError: + import ConfigParser as configparser + + +class ConfigHandler(object): + + def __init__(self): + self.isatty = sys.stdin.isatty() + self.config = configparser.RawConfigParser({ + 'engine': 'acestreamengine --client-console', + 'player': 'mpv', + 'timeout': '30', + 'verbose': 'False' + }) + + self._read_config() + + def get(self, option): + return self._getoption(option, 'get') + + def getint(self, option): + return self._getoption(option, 'getint') + + def getboolean(self, option): + return self._getoption(option, 'getboolean') + + def _getoption(self, option, method): + section = 'tty' if self.isatty else 'browser' + return getattr(self.config, method)(section, option) + + def _read_config(self): + self.config.add_section('tty') + self.config.add_section('browser') + self.config.read(os.path.expanduser('~/.config/acestream-launcher/config')) From 9cdff59bdffe9228a650b8420c3935ce7f9b1da7 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:33:52 +0200 Subject: [PATCH 04/10] add notify handler class --- acestream_launcher/notify.py | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 acestream_launcher/notify.py diff --git a/acestream_launcher/notify.py b/acestream_launcher/notify.py new file mode 100644 index 0000000..52b8192 --- /dev/null +++ b/acestream_launcher/notify.py @@ -0,0 +1,47 @@ +import sys +import subprocess + + +class NotifyHandler(object): + + def __init__(self, player): + self.player = player + self.isatty = sys.stdin.isatty() + self.notify = self._notify_send(['-v']) + + def print_message(self, message): + if message and self.isatty: + sys.stdout.write("\x1b[2K\r%s" % message) + sys.stdout.flush() + + def send_message(self, message): + if message and self.notify and not self.isatty: + name = 'Acestream Launcher' + icon = self.player.split()[0] + args = ['-h', 'int:transient:1', '-i', icon, name, message] + + self._notify_send(args) + + def print_stats(self, stream, stats): + labels = { 'dl': 'playing', 'prebuf': 'buffering' } + status = labels[stream.status] + + labels = 'down: %.1f kb/s up: %.1f kb/s peers: %d' + sstats = labels % (stats.speed_down, stats.speed_up, stats.peers) + + if stream.is_live: + label = 'LIVE - status: %s %s' + stats = (status, sstats) + else: + label = 'VOD - status: %s total: %.2f%% %s' + stats = (status, stats.total_progress, sstats) + + self.print_message(label % stats) + + def _notify_send(self, args): + try: + command = ['notify-send'] + args + subprocess.call(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except OSError: + return False From 63ee23f8348ea38957032e5c6474dbb881d2564f Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:34:10 +0200 Subject: [PATCH 05/10] add player handler class --- acestream_launcher/player.py | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 acestream_launcher/player.py diff --git a/acestream_launcher/player.py b/acestream_launcher/player.py new file mode 100644 index 0000000..39eb77c --- /dev/null +++ b/acestream_launcher/player.py @@ -0,0 +1,42 @@ +import os +import signal +import subprocess + +from threading import Thread +from acestream.object import Observable + + +class PlayerHandler(Observable): + + process = None + + def __init__(self, bin='mpv'): + self.args = bin.split() + + def start(self, url, **kwargs): + cmargs = self.args + cmargs.append(url) + + thread = Thread(target=self._start_process, args=(cmargs), kwargs=kwargs) + thread.setDaemon(True) + thread.start() + + def stop(self): + if bool(self.process): + os.killpg(os.getpgid(self.process.pid), signal.SIGTERM) + + self.process = None + self.emit('terminated') + + def _start_process(self, *args, **kwargs): + try: + self.process = subprocess.Popen(args, preexec_fn=os.setsid, **kwargs) + self.emit('started') + self.process.communicate() + + self.process = None + self.emit('terminated') + except OSError: + self.process = None + self.emit('notify', 'Media player not found in provided path!') + self.emit('error') From 566666e508af0b84b2281337333293bf67f19473 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:35:13 +0200 Subject: [PATCH 06/10] add stream handler class, use python-acestream for HTTP API --- acestream_launcher/stream.py | 95 ++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 acestream_launcher/stream.py diff --git a/acestream_launcher/stream.py b/acestream_launcher/stream.py new file mode 100644 index 0000000..44001c2 --- /dev/null +++ b/acestream_launcher/stream.py @@ -0,0 +1,95 @@ +import time + +from acestream.object import Observable +from acestream.server import Server +from acestream.engine import Engine +from acestream.stream import Stream + + +class StreamHandler(Observable): + + engine = None + stream = None + params = None + timeout = 30 + playing = False + available = False + + def __init__(self, bin, host='127.0.0.1', port=6878, timeout=30): + self.timeout = int(timeout) + self.server = Server(host=host, port=port) + self.engine = Engine(bin=bin) + + def start(self, param, **kwargs): + self.params = self._parse_stream_param(param) + + self.emit('notify', 'Connecting to Acestream engine...') + self._start_engine(**kwargs) + + def stop(self): + self.engine.stop() + + def _start_engine(self, **kwargs): + if not self.server.available: + self.engine.connect('started', self._on_engine_started) + self.engine.connect('terminated', self._on_engine_terminated) + self.engine.connect('error', self._on_engine_error) + + self.engine.start(daemon=True, **kwargs) + else: + self._on_engine_started() + + def _start_stream(self): + self.stream = Stream(self.server, **self.params) + + self.stream.connect('status::changed', self._on_stream_status_changed) + self.stream.connect('stats::updated', self._on_stream_stats_updated) + self.stream.connect('error', self._on_stream_error) + + self.stream.start() + + def _parse_stream_param(self, param): + if param.startswith('http'): + return { 'url': param } + else: + return { 'id': param.split('://')[-1] } + + def _timeout(self, object, attribute, message): + while self.timeout > 0 and not getattr(object, attribute): + self.timeout = self.timeout - 1 + time.sleep(1) + + if self.timeout == 0: + self.emit('notify', message) + self.emit('error') + + def _on_engine_started(self): + self._timeout(self.server, 'available', 'Cannot connect to Acestream engine!') + self.emit('notify', 'Waiting for stream response...') + + self._start_stream() + self._timeout(self, 'available', 'Stream unavailable!') + + def _on_engine_terminated(self): + self.emit('terminated') + + def _on_engine_error(self, _message=None): + self.emit('notify', 'Acestream engine not found in provided path!') + self.emit('error') + + def _on_stream_status_changed(self): + if self.stream.status and not self.available: + self.available = True + + if self.stream.status == 'dl' and not self.playing: + self.playing = True + + self.emit('notify', 'Streaming started, launching player...') + self.emit('playing', self.stream.playback_url) + + def _on_stream_stats_updated(self): + self.emit('stats::updated', self.stream, self.stream.stats) + + def _on_stream_error(self, _message=None): + self.emit('notify', 'Stream unavailable!') + self.emit('error') From 4e2f97e5829aa1c16651d77e28af1ad0194481d2 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:35:36 +0200 Subject: [PATCH 07/10] add stream launcher class --- acestream_launcher/launcher.py | 116 +++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 acestream_launcher/launcher.py diff --git a/acestream_launcher/launcher.py b/acestream_launcher/launcher.py new file mode 100644 index 0000000..97f9a6a --- /dev/null +++ b/acestream_launcher/launcher.py @@ -0,0 +1,116 @@ +import time +import argparse +import subprocess + +from acestream_launcher.config import ConfigHandler +from acestream_launcher.notify import NotifyHandler + +from acestream_launcher.stream import StreamHandler +from acestream_launcher.player import PlayerHandler + + +class StreamLauncher(object): + + stream = None + player = None + exiting = False + + def __init__(self): + config = ConfigHandler() + + parser = argparse.ArgumentParser( + prog='acestream-launcher', + description='Open acestream links with any media player' + ) + parser.add_argument( + 'url', + metavar='URL', + help='the acestream url to play' + ) + parser.add_argument( + '-e', '--engine', + help='the engine command to use (default: acestreamengine --client-console)', + default=config.get('engine') + ) + parser.add_argument( + '-p', '--player', + help='the media player command to use (default: mpv)', + default=config.get('player') + ) + parser.add_argument( + '-t', '--timeout', + help='time in seconds to wait for stream playback (default: 30)', + default=config.getint('timeout') + ) + parser.add_argument( + '-v', '--verbose', + help='show engine and media player output in console', + action='store_true', + default=config.getboolean('verbose') + ) + + self.args = parser.parse_args() + self.noty = NotifyHandler(self.args.player) + + @property + + def output(self): + output = None if self.args.verbose else subprocess.PIPE + return { 'stdout': output, 'stderr': output } + + def notify(self, message): + if message and not self.exiting: + self.noty.print_message(message) + self.noty.send_message(message) + + def stats(self, stream, stats): + if not self.exiting: + self.noty.print_stats(stream, stats) + + def play(self, url): + self.player = PlayerHandler(bin=self.args.player) + + self.player.connect('notify', self.notify) + self.player.connect('error', self.quit) + self.player.connect('terminated', self.quit) + + self.player.start(url=url, **self.output) + + def run(self): + self.stream = StreamHandler(bin=self.args.engine, timeout=self.args.timeout) + + self.stream.connect('notify', self.notify) + self.stream.connect('stats::updated', self.stats) + self.stream.connect('playing', self.play) + self.stream.connect('error', self.quit) + self.stream.connect('terminated', self.quit) + + self.stream.start(param=self.args.url, **self.output) + + while self.stream or self.player: + time.sleep(1) + + def quit(self): + if not self.exiting: + self.exiting = True + print('\n\nExiting...') + + if self.stream: + self.stream.stop() + self.stream = None + + if self.player: + self.player.stop() + self.player = None + + +def main(): + try: + launcher = StreamLauncher() + launcher.run() + except KeyboardInterrupt: + launcher.quit() + + +if __name__ == '__main__': + main() From 275cedf04c3abd34574fded248cd055c162ff3c5 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:35:48 +0200 Subject: [PATCH 08/10] update desktop file --- acestream-launcher.desktop | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acestream-launcher.desktop b/acestream-launcher.desktop index 78f8b6f..eed543a 100644 --- a/acestream-launcher.desktop +++ b/acestream-launcher.desktop @@ -1,10 +1,10 @@ [Desktop Entry] Version=1.0 -Name=Acestream Launcher +Name=AceStream Launcher GenericName=Media player -Comment=Open Acestream links with MPV Media Player -Exec=/usr/bin/acestream-launcher %u -TryExec=/usr/bin/acestream-launcher +Comment=Open AceStream links with any Media Player +Exec=acestream-launcher %u +TryExec=acestream-launcher Icon=multimedia-video-player Terminal=false Type=Application From 00e690225e1acb392fd69884684a24c011ab9d30 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:35:57 +0200 Subject: [PATCH 09/10] update readme --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1bbe3c6..01f022b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Acestream Launcher allows you to open Acestream links with a Media Player of you ## Dependencies ```text -python, libnotify, acestream-engine +python, pyhon-acestream, libnotify, acestream-engine ``` Since `v1.0.0` acestream-launcher uses [Acestream Engine HTTP API](http://wiki.acestream.org/wiki/index.php/Engine_HTTP_API) that is available on acestream-engine `v3.1` or later. @@ -48,7 +48,7 @@ verbose = false Install required dependencies (compatible with python 2 and 3): ```shell -sudo apt-get install python +sudo apt-get install python python-pip ``` Install optional dependencies (support for desktop notifications): @@ -81,15 +81,12 @@ sudo snap install acestreamplayer ``` ## Installation +Install the package with the Python Package Index using `pip` command. ```shell -git clone "https://github.com/jonian/acestream-launcher.git" -cd acestream-launcher -sudo ./install.sh +pip install acestream-launcher ``` -The script will install acestream-launcher in `/opt` directory. - ## Packages Arch Linux: [AUR Package](https://aur.archlinux.org/packages/acestream-launcher) OpenSUSE: [Build Service](https://build.opensuse.org/project/show/home:drommer:p2pstreams) by [@Drommer](https://github.com/Drommer) From e49a983857b006d0c11ea9e0007af12114c1d951 Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Tue, 13 Nov 2018 20:38:44 +0200 Subject: [PATCH 10/10] update gitignore --- .gitignore | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c871da9..f71fa3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -*test* -*sample* +build +dist + +*.egg-info *pycache* *.pyc*