+++ /dev/null
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-
-import sys
-import os
-import logging
-from aiorun import run
-import asyncio
-
-logger = logging.getLogger()
-logformat = logging.Formatter('%(asctime)-15s %(levelname)-5s %(name)s.%(module)s -- %(message)s')
-consolehandler = logging.StreamHandler()
-consolehandler.setFormatter(logformat)
-logger.addHandler(consolehandler)
-
-
-def get_config():
- ''' start config handling '''
- data_dir = ''
- debug = False
- update_latest = False
- # prefer command line args
- if len(sys.argv) > 1:
- data_dir = sys.argv[1]
- if len(sys.argv) > 2:
- debug = sys.argv[2] == "debug"
- if len(sys.argv) > 3:
- update_latest = sys.argv[3] == "update"
- # fall back to environment variables (for plain docker)
- if os.environ.get('mass_datadir'):
- data_dir = os.environ['mass_datadir']
- if os.environ.get('mass_debug'):
- debug = os.environ['mass_debug'].lower() != 'false'
- if os.environ.get('mass_update'):
- update_latest = os.environ['mass_update'].lower() != 'false'
- # hassio config file found
- conf_file = '/data/options.json'
- if os.path.isfile(conf_file):
- try:
- import json
- with open(conf_file) as f:
- conf = json.loads(f.read())
- data_dir = conf['data_dir']
- debug = conf['debug_messages']
- update_latest = conf['use_nightly']
- except:
- logger.exception('could not load options.json')
- return data_dir, debug, update_latest
-
-def do_update():
- ''' auto update to latest git version '''
- base_dir = os.path.dirname(os.path.abspath(__file__))
- if os.path.isdir(".git") or os.path.isdir("%s/.git" % base_dir):
- # dev environment
- return
- logger.info("Updating to latest Git version!")
- import subprocess
- # TODO: handle this properly
- args = """
- cd %s
- curl -LOks "https://github.com/marcelveldt/musicassistant/archive/master.zip"
- unzip -q master.zip
- pip install -r musicassistant-master/requirements.txt
- cp -rf musicassistant-master/music_assistant .
- cp -rf musicassistant-master/mass.py .
- rm -R musicassistant-master
- """ % (base_dir, )
- if subprocess.call(args, shell=True) == 0:
- logger.info("Update succesfull")
- else:
- logger.error("Update failed - do you have curl and zip installed ?")
-
-
-if __name__ == "__main__":
- # get config
- data_dir, debug, update_latest = get_config()
- if update_latest:
- do_update()
- # create event_loop with uvloop
- event_loop = asyncio.get_event_loop()
- try:
- import uvloop
- uvloop.install()
- except ImportError:
- # uvloop is not available on Windows so safe to ignore this
- logger.warning("uvloop support is disabled")
- # config debug settings if needed
- if debug:
- event_loop.set_debug(True)
- logger.setLevel(logging.DEBUG)
- logging.getLogger('aiosqlite').setLevel(logging.INFO)
- logging.getLogger('asyncio').setLevel(logging.WARNING)
- else:
- logger.setLevel(logging.INFO)
- # start music assistant!
- from music_assistant import MusicAssistant
- mass = MusicAssistant(data_dir, event_loop)
- run(mass.start(), loop=event_loop)
-
\ No newline at end of file
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-
-import asyncio
-import re
-import os
-import shutil
-import slugify as unicode_slug
-import uuid
-import json
-import time
-import logging
-import threading
-
-from .database import Database
-from .config import MassConfig
-from .utils import run_periodic, LOGGER, try_parse_bool, serialize_values
-from .metadata import MetaData
-from .cache import Cache
-from .music_manager import MusicManager
-from .player_manager import PlayerManager
-from .http_streamer import HTTPStreamer
-from .homeassistant import HomeAssistant
-from .web import Web
-
-
-class MusicAssistant():
-
- def __init__(self, datapath, event_loop):
- '''
- Create an instance of MusicAssistant
- :param datapath: file location to store the data
- :param event_loop: asyncio event_loop
- '''
- self.event_loop = event_loop
- self.event_loop.set_exception_handler(self.handle_exception)
- self.datapath = datapath
- self.event_listeners = {}
- self.config = MassConfig(self)
- # init modules
- self.db = Database(self)
- self.cache = Cache(self)
- self.metadata = MetaData(self)
- self.web = Web(self)
- self.hass = HomeAssistant(self)
- self.music = MusicManager(self)
- self.players = PlayerManager(self)
- self.http_streamer = HTTPStreamer(self)
-
- async def start(self):
- ''' start running the music assistant server '''
- await self.db.setup()
- await self.cache.setup()
- await self.metadata.setup()
- await self.hass.setup()
- await self.music.setup()
- await self.players.setup()
- await self.web.setup()
- await self.http_streamer.setup()
- # wait for exit
- try:
- while True:
- await asyncio.sleep(3600)
- except asyncio.CancelledError:
- LOGGER.info("Application shutdown")
- await self.signal_event("shutdown")
- self.config.save()
- await self.db.close()
- await self.cache.close()
-
- def handle_exception(self, loop, context):
- ''' global exception handler '''
- LOGGER.debug(f"Caught exception: {context}")
- loop.default_exception_handler(context)
-
- async def signal_event(self, msg, msg_details=None):
- ''' signal (systemwide) event '''
- if not (msg_details == None or isinstance(msg_details, (str, dict))):
- msg_details = serialize_values(msg_details)
- listeners = list(self.event_listeners.values())
- for callback, eventfilter in listeners:
- if not eventfilter or eventfilter in msg:
- if msg == 'shutdown':
- # the shutdown event should be awaited
- await callback(msg, msg_details)
- else:
- self.event_loop.create_task(callback(msg, msg_details))
-
- async def add_event_listener(self, cb, eventfilter=None):
- ''' add callback to our event listeners '''
- cb_id = str(uuid.uuid4())
- self.event_listeners[cb_id] = (cb, eventfilter)
- return cb_id
-
- async def remove_event_listener(self, cb_id):
- ''' remove callback from our event listeners '''
- self.event_listeners.pop(cb_id, None)
-
- def run_task(self, corofcn, wait_for_result=False, ignore_exception=None):
- ''' helper to run a task on the main event loop from another thread '''
- if threading.current_thread() is threading.main_thread():
- raise Exception("Can not be called from main event loop!")
- future = asyncio.run_coroutine_threadsafe(corofcn, self.event_loop)
- if wait_for_result:
- try:
- return future.result()
- except Exception as exc:
- if ignore_exception and isinstance(exc, ignore_exception):
- return None
- raise exc
- return future
+"""Init file for Music Assistant."""
\ No newline at end of file
--- /dev/null
+"""Start Music Assistant."""
+import argparse
+import platform
+import sys
+import os
+import logging
+import asyncio
+from aiorun import run
+
+from music_assistant.mass import MusicAssistant
+
+
+def get_arguments():
+ """Arguments handling."""
+ parser = argparse.ArgumentParser(description="MusicAssistant")
+
+ data_dir = os.getenv("APPDATA") if os.name == "nt" else os.path.expanduser("~")
+ data_dir = os.path.join(data_dir, ".musicassistant")
+ if not os.path.isdir(data_dir):
+ os.makedirs(data_dir)
+
+ parser.add_argument(
+ "-c",
+ "--config",
+ metavar="path_to_config_dir",
+ default=data_dir,
+ help="Directory that contains the MusicAssistant configuration",
+ )
+ parser.add_argument(
+ "--debug", default=False, help="Start MusicAssistant with verbose debug logging"
+ )
+ arguments = parser.parse_args()
+ return arguments
+
+
+def main():
+ """Start MusicAssistant."""
+ # setup logger
+ logger = logging.getLogger()
+ logformat = logging.Formatter(
+ "%(asctime)-15s %(levelname)-5s %(name)s.%(module)s -- %(message)s"
+ )
+ consolehandler = logging.StreamHandler()
+ consolehandler.setFormatter(logformat)
+ logger.addHandler(consolehandler)
+
+ # parse arguments
+ args = get_arguments()
+ data_dir = args.config
+ # create event_loop with uvloop
+ event_loop = asyncio.get_event_loop()
+ try:
+ import uvloop
+
+ uvloop.install()
+ except ImportError:
+ # uvloop is not available on Windows so safe to ignore this
+ logger.warning("uvloop support is disabled")
+ # config debug settings if needed
+ if args.debug:
+ event_loop.set_debug(True)
+ logger.setLevel(logging.DEBUG)
+ logging.getLogger("aiosqlite").setLevel(logging.INFO)
+ logging.getLogger("asyncio").setLevel(logging.WARNING)
+ else:
+ logger.setLevel(logging.INFO)
+
+ mass = MusicAssistant(data_dir, event_loop)
+
+ # run UI in browser on windows and macos only
+ if platform.system() in ["Windows", "Darwin"]:
+ import webbrowser
+
+ webbrowser.open(f"http://localhost:{mass.web.http_port}")
+
+ run(mass.start(), loop=event_loop)
+
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding:utf-8 -*-
+
+import asyncio
+import re
+import os
+import shutil
+import slugify as unicode_slug
+import uuid
+import json
+import time
+import logging
+import threading
+
+from .database import Database
+from .config import MassConfig
+from .utils import run_periodic, LOGGER, try_parse_bool, serialize_values
+from .metadata import MetaData
+from .cache import Cache
+from .music_manager import MusicManager
+from .player_manager import PlayerManager
+from .http_streamer import HTTPStreamer
+from .homeassistant import HomeAssistant
+from .web import Web
+
+
+class MusicAssistant():
+
+ def __init__(self, datapath, event_loop):
+ '''
+ Create an instance of MusicAssistant
+ :param datapath: file location to store the data
+ :param event_loop: asyncio event_loop
+ '''
+ self.event_loop = event_loop
+ self.event_loop.set_exception_handler(self.handle_exception)
+ self.datapath = datapath
+ self.event_listeners = {}
+ self.config = MassConfig(self)
+ # init modules
+ self.db = Database(self)
+ self.cache = Cache(self)
+ self.metadata = MetaData(self)
+ self.web = Web(self)
+ self.hass = HomeAssistant(self)
+ self.music = MusicManager(self)
+ self.players = PlayerManager(self)
+ self.http_streamer = HTTPStreamer(self)
+
+ async def start(self):
+ ''' start running the music assistant server '''
+ await self.db.setup()
+ await self.cache.setup()
+ await self.metadata.setup()
+ await self.hass.setup()
+ await self.music.setup()
+ await self.players.setup()
+ await self.web.setup()
+ await self.http_streamer.setup()
+ # wait for exit
+ try:
+ while True:
+ await asyncio.sleep(3600)
+ except asyncio.CancelledError:
+ LOGGER.info("Application shutdown")
+ await self.signal_event("shutdown")
+ self.config.save()
+ await self.db.close()
+ await self.cache.close()
+
+ def handle_exception(self, loop, context):
+ ''' global exception handler '''
+ LOGGER.debug(f"Caught exception: {context}")
+ loop.default_exception_handler(context)
+
+ async def signal_event(self, msg, msg_details=None):
+ ''' signal (systemwide) event '''
+ if not (msg_details == None or isinstance(msg_details, (str, dict))):
+ msg_details = serialize_values(msg_details)
+ listeners = list(self.event_listeners.values())
+ for callback, eventfilter in listeners:
+ if not eventfilter or eventfilter in msg:
+ if msg == 'shutdown':
+ # the shutdown event should be awaited
+ await callback(msg, msg_details)
+ else:
+ self.event_loop.create_task(callback(msg, msg_details))
+
+ async def add_event_listener(self, cb, eventfilter=None):
+ ''' add callback to our event listeners '''
+ cb_id = str(uuid.uuid4())
+ self.event_listeners[cb_id] = (cb, eventfilter)
+ return cb_id
+
+ async def remove_event_listener(self, cb_id):
+ ''' remove callback from our event listeners '''
+ self.event_listeners.pop(cb_id, None)
+
+ def run_task(self, corofcn, wait_for_result=False, ignore_exception=None):
+ ''' helper to run a task on the main event loop from another thread '''
+ if threading.current_thread() is threading.main_thread():
+ raise Exception("Can not be called from main event loop!")
+ future = asyncio.run_coroutine_threadsafe(corofcn, self.event_loop)
+ if wait_for_result:
+ try:
+ return future.result()
+ except Exception as exc:
+ if ignore_exception and isinstance(exc, ignore_exception):
+ return None
+ raise exc
+ return future
+argparse
cytoolz
aiohttp[speedups]
requests
spotify_token
protobuf
pychromecast
-uvloop
asyncio_throttle
aiocometd
aiosqlite
soco
pillow
aiohttp_cors
-unidecode
\ No newline at end of file
+unidecode
+webbrowser
\ No newline at end of file
--- /dev/null
+# Upload to PyPI Live
+# sudo python3 setup.py sdist bdist_wheel
+# sudo python3 -m twine upload dist/*
+
+import setuptools
+import os
+
+VERSION = "0.0.20"
+NAME = "music_assistant"
+
+with open("README.md", "r") as fh:
+ LONG_DESC = fh.read()
+
+with open('requirements.txt') as f:
+ INSTALL_REQUIRES = f.read().splitlines()
+if os.name != "nt":
+ INSTALL_REQUIRES.append("uvloop")
+
+setuptools.setup(
+ name=NAME,
+ version=VERSION,
+ author='Marcel van der Veldt',
+ author_email='marcelveldt@users.noreply.github.com',
+ description='Music library manager and player based on sox.',
+ long_description=LONG_DESC,
+ long_description_content_type="text/markdown",
+ url = 'https://github.com/marcelveldt/musicassistant.git',
+ packages=['music_assistant'],
+ classifiers=(
+ "Programming Language :: Python :: 3",
+ "Operating System :: OS Independent",
+ ),
+ install_requires=INSTALL_REQUIRES,
+ )
\ No newline at end of file