commit 1b7933187e2a57b973d6f4178a5ff543a3930f38 Author: Peder Bergebakken Sundt Date: Mon Feb 26 22:55:01 2018 +0100 Initial commit and design diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27476c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/default_config.py +__pycache__/ +*.pyc diff --git a/api.py b/api.py new file mode 100644 index 0000000..5155fc4 --- /dev/null +++ b/api.py @@ -0,0 +1,68 @@ +import requests, urllib +from functools import wraps +from pathlib import Path + +# (TODO)Move to config? +BASE_URL = Path('http://bokhylle.pvv.ntnu.no:8080/api') + +# Exceptions: +class APIError(Exception): pass + +# decorator: +def request_post(func): + @wraps(func) + def new_func(*args, **kwargs): + url, data = func(*args, **kwargs) + response = requests.post(url, data=data) + json = json.loads(response.text) + if "error" not in json or json["error"] != False: + raise APIError(json["error_msg"]) + return json["success"] + return new_func +def request_get(func): + @wraps(func) + def new_func(*args, **kwargs): + url = func(*args, **kwargs) + response = requests.get(url) + json = json.loads(response.text) + if "error" not in json or json["error"] != False: + raise APIError(json["error_msg"]) + return json["value"] + return new_func + +# methods: + +@request_post +def is_playing(path:str): + args = urllib.urlencode(locals()) + return BASE_URL / f"play?{args}", None + +@request_get +def is_playing(): + return BASE_URL / f"play" + +@request_post +def set_playing(play:bool): + args = urllib.urlencode(locals()) + return BASE_URL / f"play?{args}", None + +@request_get +def get_volume(): + return BASE_URL / f"volume" + +@request_post +def set_volume(volume:int):# between 0 and 100 (you may also exceed 100) + args = urllib.urlencode(locals()) + return BASE_URL / f"volume?{args}", None + +@request_get +def get_playlist(): + return BASE_URL / f"playlist" + +@request_post +def playlist_next(): + return BASE_URL / f"playlist/next", None + +@request_post +def playlist_previous(): + return BASE_URL / f"playlist/previous", None diff --git a/config.py b/config.py new file mode 100644 index 0000000..37c448b --- /dev/null +++ b/config.py @@ -0,0 +1,4 @@ +host = "0.0.0.0" +port = 8080 +start_browser = False +multiple_instance = True diff --git a/main.py b/main.py new file mode 100755 index 0000000..aaa411d --- /dev/null +++ b/main.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +import random, os, time, shutil, sys +from threading import Timer +import remi.gui as gui +from remi import start, App +from utils import Namespace + +#globals: +COLOR_BLUE = "rgb(33, 150, 243)" +COLOR_BLUE_SHADOW = "rgba(33, 150, 243, 0.75)" + +class MyApp(App): + def __init__(self, *args): + res_path = os.path.join(os.path.dirname(__file__), 'res') + super(MyApp, self).__init__(*args, static_file_path=res_path) + + def main(self): + container = gui.VBox(width=512) + container.style["margin-left"] = "auto" + container.style["margin-right"] = "auto" + + #logo: + container.append(gui.Image('/res/logo.jpg', width=512)) + + #playback controls + playbackContainer = gui.HBox()#; container.append(playbackContainer) + + self.playback = Namespace() + for i in ("previous", "play", "next"): + button = gui.Button(i.capitalize(), margin="5px") + setattr(self.playback, i, button) + playbackContainer.append(button) + button.set_on_click_listener(getattr(self,'playback_%s' % i)) + + self.playback.playing = gui.Label("Now playing: None") + self.playback.slider = gui.Slider(0, 0, 100, 1, width="85%", height=20, margin='10px') + + container.append(self.playback.playing) + container.append(playbackContainer) + container.append(self.playback.slider) + + #playlist + self.playlist = Namespace() + self.playlist.table = gui.Table(width="100%", margin="10px") + self.playlist.table.append_from_list([['#', 'Name', "length"]], fill_title=True) + + container.append(self.playlist.table) + + self.playlist.queue = []#[i] = [source, name, length] + + #input + container.append(gui.Label("Add songs:")) + inputContainer = gui.HBox(width=512) + self.input = Namespace() + self.input.field = gui.TextInput(single_line=True, height="20px", margin="5px") + self.input.field.style["border"] = "1px solid %s" % COLOR_BLUE + self.input.field.style["box-shadow"] = "0px 0px 5px 0px %s" % COLOR_BLUE_SHADOW + self.input.submit = gui.Button("Submit!", margin="5px") + self.input.field.set_on_enter_listener(self.input_submit) + self.input.submit.set_on_click_listener(self.input_submit) + + inputContainer.append(self.input.field) + inputContainer.append(self.input.submit) + container.append(inputContainer) + + #return the container + self.mainLoop() + return container + def mainLoop(self): + #self.playback.slider.get_value() + + self.playback_update() + + + self.playlist.table.empty(keep_title=True) + self.playlist.table.append_from_list(self.playlist_update()) + + Timer(0.7, self.mainLoop).start() + + # events: + def playback_previous(self, widget): pass + def playback_play(self, widget):# toggle playblack + pass + def playback_next(self, widget): + source, name, length = self.playlist.queue.pop(0) + + pass + def input_submit(self, widget, value=None): + if not value: + value = self.input.field.get_text() + self.input.field.set_text("") + + title, length = get_youtube_metadata(value) + + self.playlist.queue.append([value, title, length]) + + # playback steps: + def playback_update(self): + #talk to mpv, see wether the song is being played still + if 0:#if done: + self.playback_next() + self.playback.slider.set_value(0) + else: + self.playback.slider.set_value(100) + + return + def playlist_update(self): + #out = [['#', 'Name', "length"]] + out = [] + for i, (source, name, length) in enumerate(self.playlist.queue): + out.append([str(i+1), name, length]) + + return out + + +# config must be a object with the attributes:: +# config.host: str +# config.port: str +# config.start_browser: bool +# config.multiple_instance: bool +def main(config): + assert hasattr(config, "host") + assert hasattr(config, "port") + assert hasattr(config, "start_browser") + assert hasattr(config, "multiple_instance") + + # start the webserver: + start( + MyApp, + title = "Gregorz", + address = config.host, + port = config.port, + start_browser = config.start_browser, + multiple_instance = config.multiple_instance, + enable_file_cache = True + ) + +if __name__ == "__main__": + if not os.path.exists("config.py"): + shutil.copy("default_config.py", "config.py") + import config + main(config) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dcd67fa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +youtube-dl +https://github.com/dddomodossola/remi/archive/v1.0.tar.gz diff --git a/res/logo.jpg b/res/logo.jpg new file mode 100644 index 0000000..19373e2 Binary files /dev/null and b/res/logo.jpg differ diff --git a/res/pvv logo.svg b/res/pvv logo.svg new file mode 100644 index 0000000..54a4113 --- /dev/null +++ b/res/pvv logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PVV + + diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..27751ee --- /dev/null +++ b/utils.py @@ -0,0 +1,27 @@ +import threading +import youtube_dl + +class Namespace(object): pass + +def get_youtube_metadata(url, ydl = youtube_dl.YoutubeDL()): + #todo: check if url is valid + + #todo, stop it from doung the whole playlist + resp = ydl.extract_info(url, download=False) + #print resp.keys() + + title = resp.get('title') + length = resp.get('duration') + + #print( title, "%i:%.2i" % (length//60, length%60)) + return title, "%i:%.2i" % (length//60, length%60) + +# decorator: +def call_as_thread(func): + def new_func(*args, **kwargs): + threading.Thread( + target = func, + args = args, + kwargs = kwargs + ).start() + return new_func