diff --git a/code/piplayer3.py b/code/piplayer3.py index f6d2adf..ced9a63 100644 --- a/code/piplayer3.py +++ b/code/piplayer3.py @@ -6,7 +6,10 @@ import time import subprocess import select # for polling zbarcam, see http://stackoverflow.com/a/10759061/3761783 import os +import requests +logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s - %(message)s') +logging.info('Initializing') # Configuration MUSIC_BASE_DIRECTORY = "/home/pi/music/" @@ -16,7 +19,8 @@ SOUND_SCAN_FAIL = "/home/pi/piplayer2/sounds/fail-05.mp3" SOUND_PLAYBACK_ERROR = "/home/pi/piplayer2/sounds/error.mp3" QR_SCANNER_TIMEOUT = 3 -MUSIC_EXTENSIONS = ['.aac', '.ac3', '.aiff', '.amr', '.au', '.flac', '.m4a', '.mid', '.mka', '.mp3', '.ogg', '.ra', '.voc', '.wav', '.wma'] +MOPIDY_URL = 'http://127.0.0.1:6680/mopidy/rpc' +MUSIC_EXTENSIONS = ('.aac', '.ac3', '.aiff', '.amr', '.au', '.flac', '.m4a', '.mid', '.mka', '.mp3', '.ogg', '.ra', '.voc', '.wav', '.wma') # photo sensor on PIN 17 PIN_SENSOR = 17 @@ -33,9 +37,6 @@ PIN_PLAY = 10 PIN_NEXT = 11 -logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s - %(message)s') -logging.info('Initializing') - GPIO.setmode(GPIO.BCM) GPIO.setup(PIN_SENSOR, GPIO.IN) @@ -50,61 +51,72 @@ GPIO.setup(PIN_PREV, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(PIN_PLAY, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(PIN_NEXT, GPIO.IN, pull_up_down=GPIO.PUD_UP) - -position = 0 - -def btn1_callback(channel): - logging.debug('PREV_CALLBACK') - out, err = subprocess.Popen(['mocp','-Q', '%state'], stdout=subprocess.PIPE).communicate() - if (out.decode("utf-8").startswith('PLAY')): - out, err = subprocess.Popen(['mocp','-Q', '%cs'], stdout=subprocess.PIPE).communicate() - position = int(out.decode("utf-8")) - if (position < 4): - logging.debug('mocp --previous') - subprocess.call(["mocp", "--previous"]) - else: - logging.debug('mocp --jump 0s') - subprocess.call(["mocp", "--jump", "0s"]) - -def btn2_callback(channel): - logging.debug('mocp --toggle-pause') - subprocess.call(["mocp", "--toggle-pause"]) - -def btn3_callback(channel): - logging.debug('mocp --next') - subprocess.call(["mocp", "--next"]) - -def clear_playlist(): - return True - -def add_file(filename): - return True - -def start_playing(): - return True - -def play_sound(filename): - clear_playlist() - add_file(filename) - start_playing() - - GPIO.add_event_detect(PIN_PREV, GPIO.FALLING, callback=btn1_callback, bouncetime=400) GPIO.add_event_detect(PIN_PLAY, GPIO.FALLING, callback=btn2_callback, bouncetime=400) GPIO.add_event_detect(PIN_NEXT, GPIO.FALLING, callback=btn3_callback, bouncetime=400) + + +def call(method, params=None): + if (params is None): + json = '{"jsonrpc": "2.0", "id": 1, "method": "' + method + '"}' + else: + json = '{"jsonrpc": "2.0", "id": 1, "method": "' + method + '" , "params": ' + params + '}' + + response = requests.post(MOPIDY_URL, data=json) + logging.debug(response.text) + return response + +def add_uri(filename): + logging.debug('Adding ' + filename) + call('core.tracklist.add', '{"uri": "' + filename + '"}') + +def play_sound(filename): + call('core.tracklist.clear') + add_uri(filename) + call('core.playback.play') + +def list_files(dir): + r = [] + for path, dirs, files in os.walk(dir): + dirs.sort() + files.sort() + for name in files: + r.append(os.path.join(path, name)) + return r + + + +def btn1_callback(channel): + json = call('core.playback.get_time_position').json() + position = json['result'] + if (position < 4): + call('core.playback.previous') + else: + call('core.playback.seek', '{ "time_position": 0 }') + +def btn2_callback(channel): + json = call('core.playback.get_time_position').json() + state = json['result'] + if (state == 'playing'): + call('core.playback.pause') + else: + call('core.playback.play') + +def btn3_callback(channel): + call('core.playback.next') + + + try: - logging.info('Start moc server') - subprocess.call(["mocp", "--server"]) - subprocess.call(["mocp", "--clear"]) - subprocess.call(["mocp", "-l", SOUND_OK]) + play_sound(SOUND_OK) while True: logging.debug('Wait for photo sensor') GPIO.wait_for_edge(PIN_SENSOR, GPIO.RISING) logging.debug('Photo sensor active, activating light and camera') - subprocess.call(["mocp", "-l", SOUND_SCANNING]) + play_sound(SOUND_SCANNING) # turn LED on GPIO.output(PIN_LED, GPIO.HIGH) @@ -124,66 +136,50 @@ try: if (poll_result): try: - qr_code = zbarcam.stdout.readline().rstrip() - qr_code = qr_code.decode("utf-8") # python3 - logging.info("QR Code: {}".format(qr_code)) + # play confirmation sound + play_sound(SOUND_OK) - if (not qr_code.startswith("http://")): - # create full path - full_path = MUSIC_BASE_DIRECTORY + qr_code - logging.debug("Full Path {}".format(full_path)) - - if (not os.path.isfile(full_path)): - logging.debug("not a file, add as directory") - directory = full_path - logging.debug("Directory {}".format(directory)) - else: - logging.debug("it's a file") - directory = os.path.dirname(os.path.realpath(full_path)) - filename = os.path.basename(full_path) - logging.debug("Directory {}".format(directory)) - logging.debug("Filename {}".format(filename)) - - os.chdir(directory) - - else: - logging.debug("URL, open as stream") - stream_url = qr_code - - subprocess.call(["mocp", "--clear"]) - subprocess.call(["mocp", "--stop"]) - + call('core.tracklist.clear') logging.debug("Stopped") - # play confirmation sound - subprocess.call(["mocp", "-l", SOUND_OK]) + qr_code = zbarcam.stdout.readline().rstrip() + qr_code = qr_code.decode("utf-8") # python3 + logging.info("QR Code: " + qr_code) - if ('stream_url' in locals()): - logging.debug("Add stream") - subprocess.check_call(["mocp", "-a", stream_url]) - del stream_url - elif ('filename' in locals()): - logging.debug("Add file {}".format(filename)) - subprocess.check_call(["mocp", "-a", filename]) - del filename + if (not qr_code.startswith("http://", "spotify:")): + # create full path + full_path = MUSIC_BASE_DIRECTORY + qr_code + + if (not os.path.isfile(full_path)): + logging.debug("Not a file, recursively add all audio files in directory") + filenames = list_files(full_path) + for filename in filenames: + if filename.lower().endswith(MUSIC_EXTENSIONS): + file_uri = 'file://' + filename + add_uri(file_uri) + + else: + logging.debug("It's a file") + add_uri("file://" + full_path) + else: - logging.debug("Add directory {}".format(directory)) - subprocess.check_call(["mocp", "-a", "."]) + logging.debug("URL, open directly") + add_uri(qr_code) logging.debug("Start playback") - subprocess.check_call(["mocp", "-p"]) + call('core.playback.play') except subprocess.CalledProcessError as e: logging.error("Error starting playback, mocp returned {}".format(e.returncode)) logging.error(e.output) - subprocess.call(["mocp", "-l", SOUND_PLAYBACK_ERROR]) + play_sound(SOUND_PLAYBACK_ERROR) except FileNotFoundError as e: logging.error("Could not open directory {}".format(directory)) - subprocess.call(["mocp", "-l", SOUND_PLAYBACK_ERROR]) + play_sound(SOUND_PLAYBACK_ERROR) else: logging.warning('Timeout on zbarcam') - subprocess.call(["mocp", "-l", SOUND_SCAN_FAIL]) + play_sound(SOUND_SCAN_FAIL) zbarcam.terminate() @@ -201,10 +197,5 @@ except KeyboardInterrupt: logging.info('Shutdown') finally: - logging.info('Close moc server') - subprocess.call(["mocp", "--exit"]) logging.info('Reset GPIO configuration and close') GPIO.cleanup() - - - diff --git a/code/test/call_mopidy.py b/code/test/call_mopidy.py index 697a059..82164a9 100644 --- a/code/test/call_mopidy.py +++ b/code/test/call_mopidy.py @@ -31,7 +31,7 @@ def start_playing(): def play_sound(filename): clear_playlist() - add_file(filename) + add_uri(filename) start_playing() def list_files(dir): @@ -56,7 +56,7 @@ for filename in filenames: start_playing() -time.sleep(3) +time.sleep(2) json = call('core.playback.get_time_position').json() print(json['result']) diff --git a/notes.md b/notes.md index 5e03365..7ffab28 100644 --- a/notes.md +++ b/notes.md @@ -21,6 +21,10 @@ https://docs.mopidy.com/en/latest/api/core/#mopidy.core.mopidy.core.LibraryContr https://docs.mopidy.com/en/latest/ext/file/ +ncmpcpp +https://docs.mopidy.com/en/latest/service/ + + curl -d '{"jsonrpc": "2.0", "id": 1, "method": "core.describe"}' http://127.0.0.1:6680/mopidy/rpc curl -d '{"jsonrpc": "2.0", "id": 1, "method": "core.playback.pause"}' http://127.0.0.1:6680/mopidy/rpc @@ -32,23 +36,17 @@ curl -X POST -H Content-Type:application/json -d '{"jsonrpc": "2.0", "id": 1, "m curl -X POST -H Content-Type:application/json -d '{"jsonrpc": "2.0", "id": 1, "method": "core.tracklist.add" , "params": {"uri": "spotify:track:5tf1VVWniHgryyumXyJM7w"}}' http://127.0.0.1:6680/mopidy/rpc curl -X POST -H Content-Type:application/json -d '{"jsonrpc": "2.0", "id": 1, "method": "core.tracklist.add" , "params": {"uri": "file:///home/tilman/Musik/04%20-%20The%20Funeral.mp3"}}' http://127.0.0.1:6680/mopidy/rpc curl -X POST -H Content-Type:application/json -d '{"jsonrpc": "2.0", "id": 1, "method": "core.tracklist.add" , "params": {"uri": "http://www.wdr.de/wdrlive/media/kiraka.m3u"}}' http://127.0.0.1:6680/mopidy/rpc -curl -X POST -H Content-Type:application/json -d '{"jsonrpc": "2.0", "id": 1, "method": "core.tracklist.add" , "params": {"uri": "/home/tilman/Musik/04 - The Funeral.mp3"}}' http://127.0.0.1:6680/mopidy/rpc curl -d '{"jsonrpc": "2.0", "id": 1, "method": "core.playback.play"}' http://127.0.0.1:6680/mopidy/rpc - - - - -https://docs.mopidy.com/en/latest/service/ - https://open.spotify.com/track/3jQyadhLTxpxadQlkFh2b8 https://open.spotify.com/episode/1o8HWRR0mx5TfyxLlnKv8e https://open.spotify.com/show/4zQKHBLkM3puG5n3jAU2H4 https://open.spotify.com/album/0BajiiFeEZv9eEx07Hw2C0 -ncmpcpp + + ## Hardware - PHAT DAC installieren - http://www.instructables.com/id/Multiroom-Client-With-Raspberry-Pi-ZERO-and-PHAT-D/?ALLSTEPS