diff --git a/bundles/de.properties b/bundles/de.properties index 1e713ce8..b290a6ef 100644 --- a/bundles/de.properties +++ b/bundles/de.properties @@ -22,6 +22,7 @@ delay.off = Ausschalten clock = Uhr logo = Logo slideshow = Slideshow +vumeter = VU-Meter news = Nachrichten culture = Kultur diff --git a/bundles/en_us.properties b/bundles/en_us.properties index a8a646c4..d050d513 100644 --- a/bundles/en_us.properties +++ b/bundles/en_us.properties @@ -1,4 +1,4 @@ -home = Home +home = Home genre = Genre radio = Radio @@ -22,6 +22,7 @@ delay.off = Off clock = Clock logo = Logo slideshow = Slideshow +vumeter = VU Meter news = News culture = Culture diff --git a/bundles/fr.properties b/bundles/fr.properties index eee1d4c5..4c0af564 100644 --- a/bundles/fr.properties +++ b/bundles/fr.properties @@ -22,6 +22,7 @@ delay.off = Éteindre clock = Horloge logo = Logo slideshow = Diaporama +vumeter = VU-mètre news = Nouvelles culture = Culture diff --git a/bundles/ru.properties b/bundles/ru.properties index 7a0a83e7..1415cf7b 100644 --- a/bundles/ru.properties +++ b/bundles/ru.properties @@ -22,6 +22,7 @@ delay.off = Отключить clock = Часы logo = Логотип slideshow = Слайд-шоу +vumeter = Индикатор news = Новости culture = Культура diff --git a/config.txt b/config.txt index 0c1ba293..e9ada9e5 100644 --- a/config.txt +++ b/config.txt @@ -1,30 +1,30 @@ [screen.info] -width = 480 -height = 320 +width = 320 +height = 240 depth = 32 frame.rate = 30 [usage] +use.touchscreen = True +use.mouse = False use.lirc = True -use.rotary.encoders = True -use.mpc.player = True -use.mpd.player = False +use.rotary.encoders = False use.web = True use.logging = False +[audio] +server.folder = C:\\Temp\\audio\\mpd-0.17.4-win32\\bin +server.command = mpd mpd.conf +client.name = mpc + [current] mode = radio language = en_us -playlist = news +playlist = culture station = 0 -screensaver = slideshow +screensaver = vumeter screensaver.delay = delay.1 - -[music.server] -folder = C:\\Temp\\audio\\mpd-0.17.4-win32\\bin -command = mpd mpd.conf -host = localhost -port = 6600 +volume = 25 [web.server] http.port = 8000 @@ -41,14 +41,14 @@ color.logo = 20,190,160 font.name = FiraSans.ttf [previous] -news = 0 -culture = 0 -retro = 0 -children = 0 +news = 1 +culture = 8 +retro = 12 +children = 6 classical = 0 -pop = 0 -jazz = 0 -rock = 0 +pop = 16 +jazz = 11 +rock = 10 contemporary = 0 [order.home.menu] @@ -80,6 +80,7 @@ contemporary = 9 clock = 1 logo = 2 slideshow = 3 +vumeter = 4 [order.screensaver.delay.menu] delay.1 = 1 diff --git a/event/dispatcher.py b/event/dispatcher.py index 2f974b3a..4c123c79 100644 --- a/event/dispatcher.py +++ b/event/dispatcher.py @@ -16,8 +16,8 @@ # along with Peppy Player. If not, see . import logging -from pygame.time import Clock +from pygame.time import Clock from ui.menu.stationmenu import StationMenu from ui.screen.station import StationScreen from util.config import USAGE, USE_LIRC, USE_ROTARY_ENCODERS @@ -30,6 +30,7 @@ "0" : pygame.K_SPACE, "1" : pygame.K_SPACE, "return" : pygame.K_RETURN, + "ok" : pygame.K_RETURN, "left" : pygame.K_LEFT, "right" : pygame.K_RIGHT, "up" : pygame.K_UP, @@ -42,14 +43,15 @@ "back" : pygame.K_ESCAPE} class EventDispatcher(object): - """ Event Dispatcher + """ Event Dispatcher This class runs two separate event loops: - Main event loop which handles mouse, keyboard and user events - LIRC event loop which handles LIRC events """ def __init__(self, screensaver_dispatcher, util): - """ Initializer + """ Initializer + :param screensaver_dispatcher: reference to screensaver dispatcher used for forwarding events :param util: utility object which keeps configuration settings and utility methods """ @@ -65,9 +67,10 @@ def __init__(self, screensaver_dispatcher, util): self.screensaver_was_running = False def set_current_screen(self, current_screen): - """ Set current screen. + """ Set current screen. All events are applicable for the current screen only. Logo screensaver needs current screen to get the current logo. + :param current_screen: reference to the current screen """ self.current_screen = current_screen @@ -75,7 +78,8 @@ def set_current_screen(self, current_screen): def init_lirc(self): """ LIRC initializer. - It's not executed if disabled in config.txt. Starts new thread for IR events handling. + It's not executed if IR remote was disabled in config.txt. + Starts new thread for IR events handling. """ if not self.config[USAGE][USE_LIRC]: return @@ -106,6 +110,8 @@ def handle_lirc_event(self, code): """ LIRC event handler. To simplify event handling it wraps IR events into user event with keyboard sub-type. For one IR event it generates two events - one for key down and one for key up. + + :param code: IR code """ if self.screensaver_dispatcher.saver_running: self.screensaver_dispatcher.cancel_screensaver() @@ -133,19 +139,19 @@ def handle_lirc_event(self, code): if d[KEY_KEYBOARD_KEY]: event = pygame.event.Event(USER_EVENT_TYPE, **d) pygame.event.post(event) - if not self.screensaver_dispatcher.saver_running: - d[KEY_ACTION] = pygame.KEYUP - event = pygame.event.Event(USER_EVENT_TYPE, **d) - pygame.event.post(event) + d[KEY_ACTION] = pygame.KEYUP + event = pygame.event.Event(USER_EVENT_TYPE, **d) + pygame.event.post(event) def handle_keyboard_event(self, event): """ Keyboard event handler. Wraps keyboard events into user event. Exits upon Ctrl-C. - Distinguishes key up and key down. + Distinguishes key up and key down. + :param event: event to handle """ keys = pygame.key.get_pressed() - if keys[pygame.K_LCTRL] and event.key == pygame.K_c: + if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and event.key == pygame.K_c: self.shutdown(event) elif event.type == pygame.KEYDOWN or event.type == pygame.KEYUP: if self.screensaver_dispatcher.saver_running: @@ -160,7 +166,8 @@ def handle_keyboard_event(self, event): pygame.event.post(event) def handle_event(self, event): - """ Forward event to the current screen and screensaver dispatcher + """ Forward event to the current screen and screensaver dispatcher + :param event: event to handle """ self.screensaver_dispatcher.handle_event(event) @@ -170,7 +177,8 @@ def init_volume(self, volume): """ Volume initializer This method will be executed only once upon system startup. It assumes that current screen is Station Screen. - After initialization it removes itself as a listener + After initialization it removes itself as a listener + :param volume: initial volume level """ if volume == -1 or self.volume_initialized: @@ -182,6 +190,7 @@ def init_volume(self, volume): v.set_position(int(volume)) v.update_position() self.volume_initialized = True + self.screensaver_dispatcher.change_volume(int(volume)) self.player.remove_volume_listener(self.init_volume) except: pass @@ -193,7 +202,8 @@ def dispatch(self, player, shutdown): - Quit event - when user closes window (Windows only) - Keyboard events - Mouse events - - User Events + - User Events + :param player: reference to player object "param shutdown: shutdown method to use when user exits """ diff --git a/event/rotary.py b/event/rotary.py index 0901706e..c9b9be62 100644 --- a/event/rotary.py +++ b/event/rotary.py @@ -44,6 +44,7 @@ class RotaryEncoder(object): def __init__(self, pinA, pinB, button, key_increment, key_decrement, key_select): """ Initializer + :param pinA: GPIO pin number to increment :param pinA: GPIO pin number to decrement :param button: GPIO pin number for push-button @@ -79,6 +80,7 @@ def __init__(self, pinA, pinB, button, key_increment, key_decrement, key_select) def handle_rotation_event(self, p): """ Callback method for rotation RE events. Makes required calculations and calls event handler with event defining rotation direction + :param p: pin """ if self.gpio.input(self.pinA): @@ -113,7 +115,9 @@ def handle_rotation_event(self, p): def handle_button_event(self, button): """ Callback method for push-button event. - Calls event handler with event defining button Up or Down state + Calls event handler with event defining button Up or Down state + + :param button: pin number of push-button """ if self.gpio.input(button): event = self.BUTTONUP @@ -126,7 +130,8 @@ def handle_event(self, event): """ Event handler for rotation and button events. Generates two Pygame user event for each RE event. One button down and one button up events. - :param event: the event + + :param event: the event to handle """ d = {} d[KEY_SUB_TYPE] = SUB_TYPE_KEYBOARD diff --git a/icons/large/vumeter-on.png b/icons/large/vumeter-on.png new file mode 100644 index 00000000..2b8f97fa Binary files /dev/null and b/icons/large/vumeter-on.png differ diff --git a/icons/large/vumeter.png b/icons/large/vumeter.png new file mode 100644 index 00000000..d743dc60 Binary files /dev/null and b/icons/large/vumeter.png differ diff --git a/icons/medium/vumeter-on.png b/icons/medium/vumeter-on.png new file mode 100644 index 00000000..8d3f6c60 Binary files /dev/null and b/icons/medium/vumeter-on.png differ diff --git a/icons/medium/vumeter.png b/icons/medium/vumeter.png new file mode 100644 index 00000000..64abb562 Binary files /dev/null and b/icons/medium/vumeter.png differ diff --git a/icons/small/vumeter-on.png b/icons/small/vumeter-on.png new file mode 100644 index 00000000..68b32659 Binary files /dev/null and b/icons/small/vumeter-on.png differ diff --git a/icons/small/vumeter.png b/icons/small/vumeter.png new file mode 100644 index 00000000..27a7445b Binary files /dev/null and b/icons/small/vumeter.png differ diff --git a/peppy.ico b/peppy.ico new file mode 100644 index 00000000..bdb3e076 Binary files /dev/null and b/peppy.ico differ diff --git a/peppy.py b/peppy.py index e6ab5d40..d75960f7 100644 --- a/peppy.py +++ b/peppy.py @@ -20,10 +20,11 @@ import subprocess import threading import pygame +import importlib +from subprocess import Popen from event.dispatcher import EventDispatcher -from player.mpd.mpcplayer import MpcPlayer -from player.mpd.mpdproxy import MpdProxy +from player.proxy import Proxy from screensaver.screensaverdispatcher import ScreensaverDispatcher from ui.screen.genre import GenreScreen from ui.screen.home import HomeScreen @@ -31,8 +32,8 @@ from ui.screen.saver import SaverScreen from ui.screen.about import AboutScreen from ui.screen.station import StationScreen -from util.config import USAGE, USE_WEB, USE_MPD_PLAYER, USE_MPC_PLAYER, MUSIC_SERVER, HOST, \ - PORT, LINUX_PLATFORM, FOLDER, COMMAND, CURRENT, LANGUAGE, STATION +from util.config import USAGE, USE_WEB, AUDIO, SERVER_FOLDER, SERVER_COMMAND, CLIENT_NAME, \ + LINUX_PLATFORM, CURRENT, LANGUAGE, STATION, VOLUME from util.util import Util, LABELS class Peppy(object): @@ -41,10 +42,10 @@ class Peppy(object): lock = threading.RLock() def __init__(self): """ Initializer """ + self.util = Util() self.config = self.util.config self.use_web = self.config[USAGE][USE_WEB] - self.music_server = None if self.use_web: f = open(os.devnull, 'w') @@ -56,16 +57,7 @@ def __init__(self): about.add_listener(self.go_home) self.screens = {"about" : about} - if not self.config[LINUX_PLATFORM]: - self.music_server = self.start_audio_server() - - if self.config[USAGE][USE_MPD_PLAYER]: - host = self.config[MUSIC_SERVER][HOST] - port = int(self.config[MUSIC_SERVER][PORT]) - from player.mpd.mpdclient import MpdClient - self.player = MpdClient(host, port) - elif self.config[USAGE][USE_MPC_PLAYER]: - self.player = MpcPlayer(self.config[LINUX_PLATFORM]) + self.start_audio() self.screensaver_dispatcher = ScreensaverDispatcher(self.util) if self.use_web: @@ -75,18 +67,26 @@ def __init__(self): self.current_screen = None self.go_stations() - def start_audio_server(self): - """ Starts audio server + def start_audio(self): + """ Starts audio server and client """ - :return: audio server - """ - folder = self.config[MUSIC_SERVER][FOLDER] - command = self.config[MUSIC_SERVER][COMMAND] + folder = self.config[AUDIO][SERVER_FOLDER] + cmd = self.config[AUDIO][SERVER_COMMAND] + client_name = self.config[AUDIO][CLIENT_NAME] linux = self.config[LINUX_PLATFORM] - music_server = MpdProxy(linux, folder, command) - music_server.start() - return music_server - + + if folder != None and cmd != None: + proxy = Proxy(linux, folder, cmd, self.config[CURRENT][VOLUME]) + self.audio_server = proxy.start() + + p = "player.client." + client_name + m = importlib.import_module(p) + n = client_name.title() + self.player = getattr(m, n)() + self.player.set_platform(linux) + self.player.set_proxy(self.audio_server) + self.player.start_client() + def set_current_screen_visible(self, flag): """ Set current screen visibility flag @@ -235,6 +235,8 @@ def go_stations(self, state=None): listeners["play"] = self.play_pause listeners["play-pause"] = self.play_pause listeners["set volume"] = self.player.set_volume + listeners["set config volume"] = self.set_config_volume + listeners["set screensaver volume"] = self.screensaver_dispatcher.change_volume listeners["mute"] = self.player.mute listeners["play"] = self.player.play station_screen = StationScreen(listeners, self.util) @@ -250,11 +252,17 @@ def go_stations(self, state=None): station_screen.station_menu.set_station(current_station) self.screensaver_dispatcher.change_image(station_screen.station_menu.station_button.state) station_screen.station_menu.add_listener(self.screensaver_dispatcher.change_image) - self.player.add_player_listener(station_screen.screen_title.set_text) if self.use_web: self.web_server.add_station_screen_web_listeners(station_screen) + + def set_config_volume(self, volume): + """ Listener for volume change events + + :param volume: new volume value + """ + self.config[CURRENT][VOLUME] = str(int(volume)) def go_genres(self, state): """ Go to the Genre Screen @@ -312,16 +320,17 @@ def shutdown(self, event): stations = self.screens["stations"] stations.screen_title.shutdown() - pygame.quit() + pygame.quit() if self.config[LINUX_PLATFORM]: subprocess.call("sudo poweroff", shell=True) else: - self.music_server.stop() + Popen("taskkill /F /T /PID {pid}".format(pid=self.audio_server.pid)) os._exit(0) def main(): - """ Main method """ + """ Main method """ + peppy = Peppy() peppy.event_dispatcher.dispatch(peppy.player, peppy.shutdown) diff --git a/peppy.pyw b/peppy.pyw new file mode 100644 index 00000000..d635478e --- /dev/null +++ b/peppy.pyw @@ -0,0 +1,336 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +import os +import sys +import subprocess +import threading +import pygame +import importlib + +from subprocess import Popen +from event.dispatcher import EventDispatcher +from player.proxy import Proxy +from screensaver.screensaverdispatcher import ScreensaverDispatcher +from ui.screen.genre import GenreScreen +from ui.screen.home import HomeScreen +from ui.screen.language import LanguageScreen +from ui.screen.saver import SaverScreen +from ui.screen.about import AboutScreen +from ui.screen.station import StationScreen +from util.config import USAGE, USE_WEB, AUDIO, SERVER_FOLDER, SERVER_COMMAND, CLIENT_NAME, \ + LINUX_PLATFORM, CURRENT, LANGUAGE, STATION, VOLUME +from util.util import Util, LABELS + +class Peppy(object): + """ Main class """ + + lock = threading.RLock() + def __init__(self): + """ Initializer """ + self.util = Util() + self.config = self.util.config + self.use_web = self.config[USAGE][USE_WEB] + + if self.use_web: + f = open(os.devnull, 'w') + sys.stdout = sys.stderr = f + from web.server.webserver import WebServer + self.web_server = WebServer(self.util, self) + + about = AboutScreen(self.util) + about.add_listener(self.go_home) + self.screens = {"about" : about} + + self.start_audio() + + self.screensaver_dispatcher = ScreensaverDispatcher(self.util) + if self.use_web: + self.web_server.add_screensaver_web_listener(self.screensaver_dispatcher) + + self.event_dispatcher = EventDispatcher(self.screensaver_dispatcher, self.util) + self.current_screen = None + self.go_stations() + + def start_audio(self): + """ Starts audio server and client """ + + folder = self.config[AUDIO][SERVER_FOLDER] + cmd = self.config[AUDIO][SERVER_COMMAND] + client_name = self.config[AUDIO][CLIENT_NAME] + linux = self.config[LINUX_PLATFORM] + + if folder != None and cmd != None: + proxy = Proxy(linux, folder, cmd, self.config[CURRENT][VOLUME]) + self.audio_server = proxy.start() + + p = "player.client." + client_name + m = importlib.import_module(p) + n = client_name.title() + self.player = getattr(m, n)() + self.player.set_platform(linux) + self.player.set_proxy(self.audio_server) + self.player.start_client() + + def set_current_screen_visible(self, flag): + """ Set current screen visibility flag + + :param flag: visibility flag + """ + with self.lock: + cs = self.current_screen + if cs and self.screens and self.screens[cs]: + self.screens[cs].set_visible(flag) + + def set_mode(self, state): + """ Set current mode (e.g. Radio, Language etc) + + :param state: button state + """ + if state.name == "radio": self.go_stations(state) + elif state.name == "music": self.go_hard_drive(state) + elif state.name == "language": self.go_language(state) + elif state.name == "stream": self.go_stream(state) + elif state.name == "screensaver": self.go_savers(state) + elif state.name == "about": self.go_about(state) + + def go_home(self, state): + """ Go to the Home Screen + + :param state: button state + """ + self.set_current_screen_visible(False) + try: + if self.screens and self.screens["home"]: + self.set_current_screen("home") + return + except KeyError: + pass + + home_screen = HomeScreen(self.util, self.set_mode) + self.screens["home"] = home_screen + self.set_current_screen("home") + + if self.use_web: + self.web_server.add_home_screen_web_listeners(home_screen) + + def go_language(self, state): + """ Go to the Language Screen + + :param state: button state + """ + self.set_current_screen_visible(False) + try: + if self.screens["language"]: + self.set_current_screen("language") + return + except KeyError: + pass + + language_screen = LanguageScreen(self.util, self.change_language) + self.screens["language"] = language_screen + self.set_current_screen("language") + + if self.use_web: + self.web_server.add_language_screen_web_listeners(language_screen) + + def change_language(self, state): + """ Change current language and go to the Home Screen + + :param state: button state + """ + if state.name != self.config[CURRENT][LANGUAGE]: + self.config[LABELS].clear() + try: + stations = self.screens["stations"] + if stations: + self.player.remove_player_listener(stations.screen_title.set_text) + except KeyError: + pass + self.config[CURRENT][LANGUAGE] = state.name + self.config[LABELS] = self.util.get_labels() + self.screens = {k : v for k, v in self.screens.items() if k == 'about'} + self.current_screen = None + self.go_home(state) + + def go_hard_drive(self, state): + """ Go to the Hard Drive Screen + + :param state: button state + """ + pass + + def go_stream(self, state): + """ Go to the Stream Screen + + :param state: button state + """ + pass + + def go_savers(self, state): + """ Go to the Screensavers Screen + + :param state: button state + """ + self.set_current_screen_visible(False) + try: + if self.screens["saver"]: + self.set_current_screen("saver") + return + except KeyError: + pass + + saver_screen = SaverScreen(self.util, self.go_home) + saver_screen.saver_menu.add_listener(self.screensaver_dispatcher.change_saver_type) + saver_screen.delay_menu.add_listener(self.screensaver_dispatcher.change_saver_delay) + self.screens["saver"] = saver_screen + self.set_current_screen("saver") + + if self.use_web: + self.web_server.add_saver_screen_web_listeners(saver_screen) + + def go_about(self, state): + """ Go to the About Screen + + :param state: button state + """ + self.set_current_screen_visible(False) + self.set_current_screen("about") + if self.use_web: + self.web_server.add_about_screen_web_listeners(self.screens["about"]) + + def go_stations(self, state=None): + """ Go to the Stations Screen + + :param state: button state + """ + self.set_current_screen_visible(False) + try: + if self.screens["stations"]: + self.set_current_screen("stations") + return + except KeyError: + pass + + listeners = {} + listeners["go home"] = self.go_home + listeners["go genres"] = self.go_genres + listeners["shutdown"] = self.shutdown + listeners["go config"] = self.go_savers + listeners["play"] = self.play_pause + listeners["play-pause"] = self.play_pause + listeners["set volume"] = self.player.set_volume + listeners["set config volume"] = self.set_config_volume + listeners["set screensaver volume"] = self.screensaver_dispatcher.change_volume + listeners["mute"] = self.player.mute + listeners["play"] = self.player.play + station_screen = StationScreen(listeners, self.util) + self.screens["stations"] = station_screen + v = self.player.get_volume() + if not v: + v = "0" + station_screen.volume.set_position(int(v)) + station_screen.volume.update_position() + self.set_current_screen("stations") + + current_station = self.config[CURRENT][STATION] + station_screen.station_menu.set_station(current_station) + self.screensaver_dispatcher.change_image(station_screen.station_menu.station_button.state) + station_screen.station_menu.add_listener(self.screensaver_dispatcher.change_image) + self.player.add_player_listener(station_screen.screen_title.set_text) + + if self.use_web: + self.web_server.add_station_screen_web_listeners(station_screen) + + def set_config_volume(self, volume): + """ Listener for volume change events + + :param volume: new volume value + """ + self.config[CURRENT][VOLUME] = str(int(volume)) + + def go_genres(self, state): + """ Go to the Genre Screen + + :param state: button state + """ + self.set_current_screen_visible(False) + try: + if self.screens["genres"]: + self.set_current_screen("genres") + return + except KeyError: + pass + + genre_screen = GenreScreen(self.util, self.go_stations) + self.screens["genres"] = genre_screen + self.set_current_screen("genres") + + if self.use_web: + self.web_server.add_genre_screen_web_listeners(genre_screen) + + def play_pause(self, state=None): + """ Handle Play/Pause + + :param state: button state + """ + self.player.play_pause() + + def set_current_screen(self, name): + """ Set current screen defined by its name + + :param name: screen name + """ + with self.lock: + self.current_screen = name + cs = self.screens[self.current_screen] + cs.set_visible(True) + cs.set_current() + cs.clean_draw_update() + self.event_dispatcher.set_current_screen(cs) + + def shutdown(self, event): + """ System shutdown handler + + :param event: the event + """ + self.util.config_class.save_config() + self.player.shutdown() + + if self.use_web: + try: + self.web_server.shutdown() + except: + pass + + stations = self.screens["stations"] + stations.screen_title.shutdown() + + if self.config[LINUX_PLATFORM]: + subprocess.call("sudo poweroff", shell=True) + else: + Popen("taskkill /F /T /PID {pid}".format(pid=self.audio_server.pid)) + pygame.quit() + os._exit(0) + +def main(): + """ Main method """ + peppy = Peppy() + peppy.event_dispatcher.dispatch(peppy.player, peppy.shutdown) + +if __name__ == "__main__": + main() diff --git a/player/mpd/__init__.py b/player/client/__init__.py similarity index 100% rename from player/mpd/__init__.py rename to player/client/__init__.py diff --git a/player/mpd/mpcplayer.py b/player/client/mpc.py similarity index 88% rename from player/mpd/mpcplayer.py rename to player/client/mpc.py index 8103d17b..0cd95f29 100644 --- a/player/mpd/mpcplayer.py +++ b/player/client/mpc.py @@ -15,28 +15,26 @@ # You should have received a copy of the GNU General Public License # along with Peppy Player. If not, see . -from player.player import Player +from player.client.player import Player import subprocess from subprocess import Popen, PIPE import os -from player.mpd.mpdproxy import MPC, CLEAR, ADD, PLAY, STOP, PAUSE, RESUME, CURRENT,\ +from player.client.mpdcommands import MPC, CLEAR, ADD, PLAY, STOP, PAUSE, RESUME, CURRENT,\ SET_VOLUME_1, GET_VOLUME, MUTE_1, STATUS, IDLELOOP, PLAYER import threading import logging import time -class MpcPlayer(Player): +class Mpc(Player): """ This class extends abstract Player and implements its methods. It serves as a wrapper for MPC process """ lock = threading.RLock() use_shell = True - def __init__(self, linux): - """ Initializer. Starts new thread which listens to the player events - :param linux: True - cureent platform is Linux, False - Current platform is Windows - """ - self.linux = linux + def __init__(self): + """ Initializer. Starts new thread which listens to the player events """ + self.items = None self.current_track = None self.volume = None @@ -44,26 +42,40 @@ def __init__(self, linux): self.volume_listeners = [] self.player_listeners = [] self.current_url = None - self.playing = True + self.playing = True + + def set_platform(self, linux): + """ Set platform flag + + :param linux: True - current platform is Linux, False - Current platform is Windows + """ + self.linux = linux + + def set_proxy(self, proxy): + """ mpc client doesn't use proxy """ + + pass + + def start_client(self): + """ This method starts new thread for listening MPC events """ + thread = threading.Thread(target = self.mpd_event_listener) thread.start() def mpd_event_listener(self): - """ This method starts new thread for listening MPC events. - The communication with MPC process done through the pipe. - Empty pipe causes thread blocking. New message in the pipe unblock the thread. - The message should contain 'player' all other events are ignored. + """ The communication with MPC process done through the pipe. + Empty pipe causes thread blocking. New message in the pipe unblocks the thread. + The message should contain 'player'. All other events are ignored. Upon new message all corresponding listeners will be notified. There are two types of listeners which will be notified: - volume listeners - notified when volume changes - - status listeners - notified when player status changes (e.g. song title changes) + - player listeners - notified when player status changes (e.g. song title changes) """ self.mpc_process = None while self.playing: if not self.mpc_process: try: - logging.debug("starting mpc process...") - self.mpc_process = Popen([MPC.strip(), IDLELOOP], stdout=PIPE, bufsize=1, universal_newlines=True) + self.mpc_process = Popen([MPC.strip(), IDLELOOP], stdout=PIPE, universal_newlines=True) time.sleep(0.2) except Exception as e: logging.error(str(e)) @@ -84,7 +96,8 @@ def mpd_event_listener(self): self.mpc_process = None def add_volume_listener(self, listener): - """ Add volume listener + """ Add volume listener + :param listener: volume event listener """ with self.lock: @@ -92,7 +105,8 @@ def add_volume_listener(self, listener): self.volume_listeners.append(listener) def remove_volume_listener(self, listener): - """ Remove volume listener + """ Remove volume listener + :param listener: volume event listener """ with self.lock: @@ -101,6 +115,7 @@ def remove_volume_listener(self, listener): def add_player_listener(self, listener): """ Add player status listener + :param listener: player status listener """ with self.lock: @@ -109,6 +124,7 @@ def add_player_listener(self, listener): def remove_player_listener(self, listener): """ Remove player status listener + :param listener: player status listener """ with self.lock: @@ -117,6 +133,7 @@ def remove_player_listener(self, listener): def notify_volume_listeners(self, volume): """ Notify volume listeners about new volume level + :param volume: new volume level """ for listener in self.volume_listeners: @@ -124,6 +141,7 @@ def notify_volume_listeners(self, volume): def notify_player_listeners(self, status): """ Notify player listeners about player status change + :param volume: new player status """ for listener in self.player_listeners: @@ -131,6 +149,7 @@ def notify_player_listeners(self, status): def call(self, arg): """ Call MPC process with specified arguments + :param args: arguments for call """ try: @@ -140,6 +159,7 @@ def call(self, arg): def call_return(self, arg): """ Call MPC process with specified arguments, decode and return the output string + :param args: arguments for call :return: string returned from MPC call """ @@ -156,6 +176,7 @@ def call_return(self, arg): def status(self): """ Call 'mpc status' command and parse the output + :return: dictionary representing MPC status """ s = self.call_return(STATUS) @@ -192,6 +213,7 @@ def status(self): def current(self): """ Return info about current track + :return: result of the command """ s = self.call_return(CURRENT) @@ -204,7 +226,8 @@ def current(self): return None def get_volume(self): - """ Return current volume level + """ Return current volume level + :return: volume level or -1 if not available """ volume = self.call_return(GET_VOLUME) @@ -219,6 +242,7 @@ def get_volume(self): def set_volume(self, level): """ Set volume level + :param level: new volume level """ self.call(SET_VOLUME_1 + str(level)) @@ -227,7 +251,7 @@ def set_volume(self, level): def mute(self): """ Mute """ - + self.mute_flag = not self.mute_flag if self.mute_flag: @@ -240,7 +264,8 @@ def mute(self): def play(self, state): """ Start playing specified station. First it cleans the playlist then adds new station to the list and then starts playing that station - :param state: button state which cantains the station to play + + :param state: button state which contains the station to play """ self.call(CLEAR) self.current_url = state.url @@ -249,7 +274,7 @@ def play(self, state): def pause(self): """ Pause playback if playing. Resume playback if paused already """ - + status = self.status() state = status.get(Player.STATE) @@ -260,7 +285,7 @@ def pause(self): def play_pause(self): """ Play/pause playback """ - + status = self.status() state = status.get(Player.STATE) @@ -271,12 +296,12 @@ def play_pause(self): def stop(self): """ Stop playback """ - + self.call(STOP) def shutdown(self): """ Shutdown the player """ - + if not self.linux: subprocess.Popen("taskkill /F /T /PID {pid}".format(pid=self.mpc_process.pid)) self.playing = False diff --git a/player/client/mpdcommands.py b/player/client/mpdcommands.py new file mode 100644 index 00000000..36edd177 --- /dev/null +++ b/player/client/mpdcommands.py @@ -0,0 +1,35 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +MPC = "mpc " +GET_VOLUME = "volume" +SET_VOLUME_1 = "volume " +SET_VOLUME_2 = "setvol " +RESUME = "play" +PLAY = "play " +PAUSE = "pause" +STOP = "stop" +MUTE_1 = "volume 0" +MUTE_2 = "setvol 0" +CLEAR = "clear" +ADD = "add " +STATUS = "status" +IDLE = "idle" +IDLELOOP = "idleloop" +PLAYER = "player" +CURRENT = "current" +CURRENT_SONG = "currentsong" \ No newline at end of file diff --git a/player/mpd/mpdconnection.py b/player/client/mpdconnection.py similarity index 96% rename from player/mpd/mpdconnection.py rename to player/client/mpdconnection.py index 94e2dd92..33d5ae21 100644 --- a/player/mpd/mpdconnection.py +++ b/player/client/mpdconnection.py @@ -20,10 +20,11 @@ import logging class MpdConnection(object): - """ Handles TCP/IP communication with MPD process """ + """ Handles TCP/IP communication with MPD server """ def __init__(self, host, port, reader_flags='rb', writer_flags='w', encoding='utf-8'): """ Initializer + :param host: host where MPD process is running :param port: port at which MPD process is listening :param reader_flags: flags used for creating reader @@ -80,7 +81,8 @@ def disconnect(self): if self.socket: self.socket.close() def write(self, line): - """ Send the message to MPD + """ Send the message to MPD + :param line: message """ if self.writer == None: return @@ -89,6 +91,7 @@ def write(self, line): def read_line(self): """ Read one line message from MPD + :return: message """ if self.reader == None: return None @@ -100,6 +103,7 @@ def read_line(self): def read_dictionary(self, cmd): """ Send command to MPD and read the output messages until it's terminated by OK + :param cmd: command for MPD :return: dictionary representing MPD process output for the specified input command """ @@ -108,7 +112,7 @@ def read_dictionary(self, cmd): self.write(cmd) line = self.read_line() - while line != self.OK: + while line and line != self.OK: index = line.find(": ") key = line[0:index] value = line[index + 1:] diff --git a/player/mpd/mpdclient.py b/player/client/mpdsocket.py similarity index 84% rename from player/mpd/mpdclient.py rename to player/client/mpdsocket.py index 366c37ac..daecdc23 100644 --- a/player/mpd/mpdclient.py +++ b/player/client/mpdsocket.py @@ -17,35 +17,51 @@ import threading -from player.mpd.mpdconnection import MpdConnection -from player.mpd.mpdproxy import CLEAR, ADD, PLAY, STOP, PAUSE, RESUME, \ +from player.client.mpdconnection import MpdConnection +from player.client.mpdcommands import CLEAR, ADD, PLAY, STOP, PAUSE, RESUME, \ SET_VOLUME_2, GET_VOLUME, MUTE_2, STATUS, CURRENT_SONG, IDLE -from player.player import Player +from player.client.player import Player -class MpdClient(Player): +class Mpdsocket(Player): """ Communicate with MPD process directly using TCP/IP socket. - Due to some weird timeout issue it's not used right now. + Due to some weird timeout issue 'mpc' client is in use by default rather than this one. """ lock = threading.RLock() - def __init__(self, host, port): + def __init__(self): """ Initializer. Starts separate thread for listening MPD events. + :param host: host where MPD process is running :param port: port to which MPD process is listening to """ - self.host = host - self.port = port - self.conn = MpdConnection(host, port) + self.host = "localhost" + self.port = 6600 self.mute_flag = False self.volume_listeners = [] self.player_listeners = [] - self.playing = True + self.playing = True + self.conn = None + + def set_platform(self, linux): + """ Set platform flag """ + + self.linux = linux + + def set_proxy(self, proxy): + """ mpd socket client doesn't use proxy """ + + pass + + def start_client(self): + """ Start client thread """ + + self.conn = MpdConnection(self.host, self.port) thread = threading.Thread(target = self.mpd_event_listener) thread.start() def mpd_event_listener(self): """ Starts the loop for listening MPD events """ - + while self.playing: c = MpdConnection(self.host, self.port, reader_flags='r', encoding=None) c.connect() @@ -71,7 +87,8 @@ def mpd_event_listener(self): self.notify_player_listeners(current_title) def add_volume_listener(self, listener): - """ Add volume listener + """ Add volume listener + :param listener: volume event listener """ with self.lock: @@ -79,7 +96,8 @@ def add_volume_listener(self, listener): self.volume_listeners.append(listener) def remove_volume_listener(self, listener): - """ Remove volume listener + """ Remove volume listener + :param listener: volume event listener """ with self.lock: @@ -88,6 +106,7 @@ def remove_volume_listener(self, listener): def add_player_listener(self, listener): """ Add player status listener + :param listener: player status listener """ with self.lock: @@ -96,6 +115,7 @@ def add_player_listener(self, listener): def remove_player_listener(self, listener): """ Remove player status listener + :param listener: player status listener """ with self.lock: @@ -104,6 +124,7 @@ def remove_player_listener(self, listener): def notify_volume_listeners(self, volume): """ Notify volume listeners about new volume level + :param volume: new volume level """ for listener in self.volume_listeners: @@ -111,6 +132,7 @@ def notify_volume_listeners(self, volume): def notify_player_listeners(self, status): """ Notify player listeners about player status change + :param volume: new player status """ for listener in self.player_listeners: @@ -119,7 +141,8 @@ def notify_player_listeners(self, status): def play(self, state): """ Start playing specified station. First it cleans the playlist then adds new station to the list and then starts playing that station - :param state: button state which cantains the station to play + + :param state: button state which contains the station to play """ self.conn.command(CLEAR) self.conn.command(ADD + state.url) @@ -132,6 +155,7 @@ def stop(self): def play_pause(self): """ Play/pause playback """ + status = self.status() state = status.get(Player.STATE) @@ -142,6 +166,7 @@ def play_pause(self): def set_volume(self, level): """ Set volume level + :param level: new volume level """ self.conn.command(SET_VOLUME_2 + str(level)) @@ -150,6 +175,7 @@ def set_volume(self, level): def get_volume(self): """ Return current volume level + :return: volume level or -1 if not available """ with self.lock: diff --git a/player/client/mplayer.py b/player/client/mplayer.py new file mode 100644 index 00000000..04de54fc --- /dev/null +++ b/player/client/mplayer.py @@ -0,0 +1,204 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +from player.client.player import Player +import threading +import logging +import codecs + +class Mplayer(Player): + """ This class extends abstract Player and implements its methods. + It serves as a wrapper for 'mplayer' process + """ + lock = threading.RLock() + use_shell = True + + def __init__(self): + """ Initializer. Starts new thread which listens to the player events """ + + self.items = None + self.current_track = None + self.volume = None + self.volume_listeners = [] + self.player_listeners = [] + self.current_url = None + self.playing = True + self.proxy = None + + def set_platform(self, linux): + """ Set platform flag + + :param linux: True - current platform is Linux, False - Current platform is Windows + """ + self.linux = linux + + def set_proxy(self, proxy): + """ Set proxy process. + + :param proxy: reference to the proxy process + """ + self.proxy = proxy + + def start_client(self): + """ This method starts new thread for listening mplayer events """ + + thread = threading.Thread(target = self.mplayer_event_listener) + thread.start() + + def mplayer_event_listener(self): + """ The communication with mplayer process done through stdin and stdout pipes. + Commands are sent to stdin pipe and messages are retrieved from the stdout pipe. + Empty pipe causes thread blocking. New message in the pipe unblock the thread. + The message should contain either 'ICY Info:' or 'ANS_volume'. All other messages + are ignored. Upon new message all corresponding listeners will be notified. + There are two types of listeners which will be notified: + - volume listeners - notified when volume changes + - player listeners - notified when player status changes (e.g. song title changes) + """ + self.proxy.stdout = codecs.getwriter("utf-8")(self.proxy.stdout.detach()) + while self.playing: + try: + line = self.proxy.stdout.readline().decode("UTF-8").rstrip() + logging.debug(str(line)) + try: + if self.volume_listeners: + self.get_volume() + except: + pass + + if "ICY Info:" in line: + current_title = line.split("'")[1] + if current_title: + self.notify_player_listeners(current_title) + elif "ANS_volume" in line: + volume = float(line.split("=")[1]) + self.notify_volume_listeners(volume) + except Exception as e: + logging.debug(str(e)) + + def add_volume_listener(self, listener): + """ Add volume listener + + :param listener: volume event listener + """ + with self.lock: + if listener not in self.volume_listeners: + self.volume_listeners.append(listener) + + def remove_volume_listener(self, listener): + """ Remove volume listener + + :param listener: volume event listener + """ + with self.lock: + if listener in self.volume_listeners: + self.volume_listeners.remove(listener) + + def add_player_listener(self, listener): + """ Add player status listener + + :param listener: player status listener + """ + with self.lock: + if listener not in self.player_listeners: + self.player_listeners.append(listener) + + def remove_player_listener(self, listener): + """ Remove player status listener + + :param listener: player status listener + """ + with self.lock: + if listener in self.player_listeners: + self.player_listeners.remove(listener) + + def notify_volume_listeners(self, volume): + """ Notify volume listeners about new volume level + + :param volume: new volume level + """ + for listener in self.volume_listeners: + listener(volume) + + def notify_player_listeners(self, status): + """ Notify player listeners about player status change + + :param volume: new player status + """ + for listener in self.player_listeners: + listener(status) + + def call(self, arg): + """ Call mplayer process with specified arguments + + :param args: arguments for call + """ + try: + self.proxy.stdin.write(arg + "\n") + self.proxy.stdin.flush() + except Exception as e: + logging.debug(str(e)) + + def get_volume(self): + """ Issues 'get_property volume' command. The result will be handled in the player thread """ + + self.call("get_property volume") + + def set_volume(self, level): + """ Set volume level + + :param level: new volume level + """ + self.call("volume " + str(level) + " 100") + pass + + def mute(self): + """ Mute """ + + self.call("mute") + + def play(self, state): + """ Start playing specified station. + + :param state: button state which contains station url + """ + self.stop() + self.current_url = state.url + logging.debug("load: " + state.url) + self.call("loadfile " + state.url) + + def pause(self): + """ Pause playback if playing. Resume playback if paused already """ + + self.call("pause") + + def play_pause(self): + """ Play/pause playback """ + + self.call("pause") + + def stop(self): + """ Stop playback """ + + self.call("stop") + + def shutdown(self): + """ Shutdown the player and client """ + + self.playing = False + self.call("quit") + diff --git a/player/player.py b/player/client/player.py similarity index 74% rename from player/player.py rename to player/client/player.py index 972fce14..d07f2f03 100644 --- a/player/player.py +++ b/player/client/player.py @@ -18,7 +18,7 @@ from abc import ABCMeta, abstractmethod class Player(metaclass=ABCMeta): - """ Serve as interface and establishes common API for different audio player implementations. + """ Serve as interface and establish common API for different audio player implementations. So that all players (mpd, mplayer etc.) would be controlled using the same API. """ STATE = "state" @@ -29,10 +29,12 @@ class Player(metaclass=ABCMeta): TRACK = "track" def __init__(self): - """ Initialize player """ + """ Initialize player """ + self.items = None self.current_track = None - self.volume = None + self.volume = None + self.linux = None @abstractmethod def set_volume(self, volume): @@ -44,73 +46,85 @@ def set_volume(self, volume): @abstractmethod def get_volume(self): - """ Volume getter """ + """ Volume getter """ + return self.volume @abstractmethod def play(self): - """ Start playback """ + """ Start playback """ + pass @abstractmethod def play_pause(self): - """ Play/Pause playback """ + """ Play/Pause playback """ + pass @abstractmethod def mute(self): - """ Mute """ + """ Mute """ + pass @abstractmethod def stop(self): - """ Stop playback """ + """ Stop playback """ + pass @abstractmethod def add_volume_listener(self, listener): - """ Add volume listener to the player + """ Add volume listener to the player + :param listener: listener to add """ pass @abstractmethod def remove_volume_listener(self, listener): - """ Remove volume listener from the player + """ Remove volume listener from the player + :param listener: listener to remove """ pass @abstractmethod def add_player_listener(self, listener): - """ Add playback listener + """ Add playback listener + :param listener: listener to add """ pass @abstractmethod def remove_player_listener(self, listener): - """ Remove playback listener + """ Remove playback listener + :param listener: listener to remove """ pass @abstractmethod def notify_volume_listeners(self, volume): - """ Notify volume listeners + """ Notify volume listeners + :param volume: volume level for notification """ pass @abstractmethod def notify_player_listeners(self, status): - """ Notify playback listeners + """ Notify playback listeners + :param status: current player status for notification """ pass @abstractmethod def shutdown(self): - """ Shutdown the player gracefully """ + """ Shutdown the player gracefully """ + pass diff --git a/player/mpd/mpdproxy.py b/player/mpd/mpdproxy.py deleted file mode 100644 index 1fdca4e5..00000000 --- a/player/mpd/mpdproxy.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright 2016 Peppy Player peppy.player@gmail.com -# -# This file is part of Peppy Player. -# -# Peppy Player 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. -# -# Peppy Player 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 Peppy Player. If not, see . - -import subprocess -import os - -MPC = "mpc " -GET_VOLUME = "volume" -SET_VOLUME_1 = "volume " -SET_VOLUME_2 = "setvol " -RESUME = "play" -PLAY = "play " -PAUSE = "pause" -STOP = "stop" -MUTE_1 = "volume 0" -MUTE_2 = "setvol 0" -CLEAR = "clear" -ADD = "add " -STATUS = "status" -IDLE = "idle" -IDLELOOP = "idleloop" -PLAYER = "player" -CURRENT = "current" -CURRENT_SONG = "currentsong" - -class MpdProxy(object): - """ This class serves as a proxy object for MPD process. - Linux version of the MPC starts MPD if it's not running. It knows how to do that. - Windows version doesn't do that. So this class is for Windows platform. - """ - def __init__(self, linux, folder, command): - """ Initializer - :param linux: flag defining platform True - Linux, False - windows - :param folder: folder where MPD process is located - :param command: command which starts MPD process - """ - self.linux = linux - self.folder = folder - self.command = command - - def start(self): - """ Start MPD process """ - - current_folder = os.getcwd() - os.chdir(self.folder) - self.music_server = subprocess.Popen(self.command, shell=True) - os.chdir(current_folder) - - def stop(self): - """ Stop MPD process """ - - if not self.linux: - subprocess.Popen("taskkill /F /T /PID {pid}".format(pid=self.music_server.pid)) diff --git a/player/playlist.py b/player/playlist.py index 120577b2..20ac9fbe 100644 --- a/player/playlist.py +++ b/player/playlist.py @@ -67,14 +67,16 @@ def set_current_station(self, index): self.current_station = page[index_in_page] def next_station(self): - """ Move to next station in the list """ + """ Move to the next station in the list """ + self.current_station_index += 1 self.current_station_index_in_page = self.current_station_index self.playing_station_page_index = int(self.current_station_index/self.stations_per_page) def get_current_page(self): - """ Get the the current page for current station - :return: list of stations representing the page where the current station belongs to + """ Get the current page for the current station + + :return: list of the stations representing the page where the current station belongs to """ start = self.current_page_index * self.stations_per_page stop = start + self.stations_per_page @@ -85,7 +87,8 @@ def get_current_page(self): def next_page(self): """ Move to the next page of stations. Move to the first page if the current page is the last one. - :return: list of stations representing the next page + + :return: list of the stations representing the next page """ if self.current_page_index + 1 == self.total_pages: self.current_page_index = 0 @@ -95,12 +98,12 @@ def next_page(self): def previous_page(self): """ Move to the previous page of stations. Move to the last page if the current page is the first one. + :return: list of stations representing the previous page """ if self.current_page_index == 0: self.current_page_index = self.total_pages - 1 else: self.current_page_index -= 1 - return self.get_current_page() - + return self.get_current_page() diff --git a/player/proxy.py b/player/proxy.py new file mode 100644 index 00000000..a8fb9878 --- /dev/null +++ b/player/proxy.py @@ -0,0 +1,60 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +from subprocess import Popen, PIPE +import os + +class Proxy(object): + """ This class serves as a proxy object for music servers. + The examples of configuration settings (folder names are specific for each user): + + mplayer: + server.folder = C:\\Temp\\audio\\mplayer-svn-37551-2 + server.command = mplayer -af volnorm=2:0.75 -idle -slave -nofontconfig -quiet + client.name = mplayer + + mpd: + server.folder = C:\\Temp\\audio\\mpd-0.17.4-win32\\bin + server.command = mpd mpd.conf + client.name = mpc + """ + + def __init__(self, linux, folder, command, volume): + """ Initializer + + :param linux: flag defining platform True - Linux, False - windows + :param folder: folder where the music server program is located + :param command: command which starts the process + """ + self.linux = linux + self.folder = folder + self.command = command + self.volume = volume + self.proxy = None + + def start(self): + """ Start server process """ + + if self.linux and "mpd" in self.command: + return None + current_folder = os.getcwd() + os.chdir(self.folder) + if "mplayer" in self.command: + self.command += " -volume " + str(self.volume) + self.proxy = Popen(self.command, stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=True, universal_newlines=True, bufsize=1) + os.chdir(current_folder) + return self.proxy diff --git a/screensaver/clock/clock.py b/screensaver/clock/clock.py index abb70a5e..758ce01f 100644 --- a/screensaver/clock/clock.py +++ b/screensaver/clock/clock.py @@ -28,7 +28,6 @@ class Clock(Component, Screensaver): The clock periodically changes on-screen position. The period is defined by the variable update_period """ - def __init__(self, util): """ Initializer @@ -44,10 +43,12 @@ def __init__(self, util): def refresh(self): """ Draw digital clock on screen """ + current_time = time.strftime(self.TIME_FORMAT) clock_size = self.f.size(current_time) r = pygame.Rect(0, 0, clock_size[0], clock_size[1]) - self.content = self.f.render(current_time, 1, self.config[COLORS][COLOR_CONTRAST]) + img = self.f.render(current_time, 1, self.config[COLORS][COLOR_CONTRAST]) + self.content = ("img", img) w = self.config[SCREEN_INFO][WIDTH] h = self.config[SCREEN_INFO][HEIGHT] self.content_x = randrange(1, w - r.w) diff --git a/screensaver/logo/logo.py b/screensaver/logo/logo.py index d03b3a1e..fa0aa258 100644 --- a/screensaver/logo/logo.py +++ b/screensaver/logo/logo.py @@ -26,8 +26,7 @@ class Logo(Component, Screensaver): After delay it displays the logo image of the current station. The logo periodically changes on-screen position. The period is defined by the variable update_period - """ - + """ def __init__(self, util): """ Initializer @@ -46,10 +45,12 @@ def set_image(self, logo): :param logo: new station logo image """ a = pygame.Surface((self.logo_size, self.logo_size), flags=pygame.SRCALPHA) - self.content = pygame.transform.smoothscale(logo[1], (self.logo_size, self.logo_size), a) + img = pygame.transform.smoothscale(logo[1], (self.logo_size, self.logo_size), a) + self.content = ("img", img) def refresh(self): """ Draw station logo image on screen """ + w = self.config[SCREEN_INFO][WIDTH] h = self.config[SCREEN_INFO][HEIGHT] self.content_x = randrange(1, w - self.r.w) diff --git a/screensaver/screensaver.py b/screensaver/screensaver.py index 1b7e0c47..3efc0b63 100644 --- a/screensaver/screensaver.py +++ b/screensaver/screensaver.py @@ -19,11 +19,13 @@ class Screensaver(): """ Parent class for all screensaver plug-ins """ def __init__(self): - """ Initializer. Sets the default update period - 1 second """ + """ Initializer. The default update period = 1 second """ + self.update_period = 1 def get_update_period(self): - """ Return screensaver update period """ + """ Return screensaver update period """ + return self.update_period def set_image(self, image): @@ -33,6 +35,24 @@ def set_image(self, image): """ pass + def set_volume(self, volume): + """ Set volume level. The method can be used by plug-ins which use volume level + + :param volume: new volume level + """ + pass + def refresh(self): """ Refresh the screensaver. Used for animation """ + + pass + + def start(self): + """ Start screensaver """ + + pass + + def stop(self): + """ Stop screensaver """ + pass \ No newline at end of file diff --git a/screensaver/screensaverdispatcher.py b/screensaver/screensaverdispatcher.py index f27b4f9b..d76b50b0 100644 --- a/screensaver/screensaverdispatcher.py +++ b/screensaver/screensaverdispatcher.py @@ -16,9 +16,9 @@ # along with Peppy Player. If not, see . import pygame + from ui.component import Component -from event.dispatcher import USER_EVENT_TYPE -from util.keys import CURRENT, KEY_SCREENSAVER, KEY_SCREENSAVER_DELAY, \ +from util.keys import CURRENT, KEY_SCREENSAVER, KEY_SCREENSAVER_DELAY, USER_EVENT_TYPE, \ KEY_SCREENSAVER_DELAY_1, KEY_SCREENSAVER_DELAY_3, SCREEN_INFO, FRAME_RATE DELAY_1 = 60 @@ -37,6 +37,7 @@ def __init__(self, util): self.config = util.config Component.__init__(self, util, None, None, False) self.current_image = None + self.current_volume = 0 self.start_listeners = [] self.stop_listeners = [] self.current_screensaver = self.get_screensaver() @@ -57,9 +58,12 @@ def set_current_screen(self, current_screen): def start_screensaver(self): """ Starts screensaver """ + if self.current_screen.visible: self.notify_start_listeners(None) + self.current_screen.clean() self.current_screen.set_visible(False) + self.current_screensaver.start() self.current_screensaver.refresh() self.counter = 0 self.delay_counter = 0 @@ -67,10 +71,12 @@ def start_screensaver(self): def cancel_screensaver(self): """ Stop currently running screensaver. Show current screen. """ + + self.current_screensaver.stop() self.current_screen.set_visible(True) self.current_screen.clean_draw_update() self.notify_stop_listeners(None) - self.saver_running = False + self.saver_running = False def change_saver_type(self, state): """ Change the screensaver type @@ -90,16 +96,19 @@ def change_saver_delay(self, state): def get_screensaver(self): """ Return current screensaver """ + name = self.config[CURRENT][KEY_SCREENSAVER] saver = self.util.load_screensaver(name) try: - saver.set_logo(self.current_image) + saver.set_image(self.current_image) + saver.set_volume(self.current_volume) except: pass return saver def get_delay(self): """ Return current delay """ + delay = DELAY_OFF delay_setting = self.config[CURRENT][KEY_SCREENSAVER_DELAY] if delay_setting == KEY_SCREENSAVER_DELAY_1: @@ -109,6 +118,8 @@ def get_delay(self): return delay def refresh(self): + """ Refresh screensaver """ + if self.saver_running: self.counter = self.counter + 1 if int(self.counter * self.one_cycle_period) == self.update_period * 1000: @@ -128,6 +139,14 @@ def change_image(self, state): """ self.current_image = state.icon_base self.current_screensaver.set_image(self.current_image) + + def change_volume(self, volume): + """ Set new volume level + + :param volume: new volume level + """ + self.current_volume = volume + self.current_screensaver.set_volume(self.current_volume) def handle_event(self, event): """ Handle user input events. Stop screensaver if it's running or restart dispatcher. diff --git a/screensaver/slideshow/slides/adam.png b/screensaver/slideshow/slides/adam.png new file mode 100644 index 00000000..b8e8109a Binary files /dev/null and b/screensaver/slideshow/slides/adam.png differ diff --git a/screensaver/slideshow/slides/david.png b/screensaver/slideshow/slides/david.png new file mode 100644 index 00000000..4a29384a Binary files /dev/null and b/screensaver/slideshow/slides/david.png differ diff --git a/screensaver/slideshow/slides/lady.png b/screensaver/slideshow/slides/lady.png deleted file mode 100644 index be34f180..00000000 Binary files a/screensaver/slideshow/slides/lady.png and /dev/null differ diff --git a/screensaver/slideshow/slides/lisa.png b/screensaver/slideshow/slides/lisa.png deleted file mode 100644 index 882ede7a..00000000 Binary files a/screensaver/slideshow/slides/lisa.png and /dev/null differ diff --git a/screensaver/slideshow/slides/litta.png b/screensaver/slideshow/slides/litta.png deleted file mode 100644 index 25023a63..00000000 Binary files a/screensaver/slideshow/slides/litta.png and /dev/null differ diff --git a/screensaver/slideshow/slides/pieta.png b/screensaver/slideshow/slides/pieta.png new file mode 100644 index 00000000..40b1b66a Binary files /dev/null and b/screensaver/slideshow/slides/pieta.png differ diff --git a/screensaver/slideshow/slideshow.py b/screensaver/slideshow/slideshow.py index c1be23a7..eced9278 100644 --- a/screensaver/slideshow/slideshow.py +++ b/screensaver/slideshow/slideshow.py @@ -25,8 +25,7 @@ class Slideshow(Component, Screensaver): After delay it displays the image from the 'slides' folder. The images will be displayed in cycle. The delay period between images is defined by the variable update_period - """ - + """ def __init__(self, util): """ Initializer @@ -67,12 +66,13 @@ def __init__(self, util): self.update_period = 6 def refresh(self): - """ Draw image on screen """ + """ Draw image on screen """ + i = next(self.indexes) slide = self.slides[i] - self.content = slide[1] + self.content = (slide[0], slide[1]) self.image_filename = slide[0] - size = self.content.get_size() + size = self.content[1].get_size() if size[0] != self.w or size[1] != self.h: self.content_x = int((self.w - size[0])/2) self.content_y = int((self.h - size[1])/2) diff --git a/screensaver/vumeter/__init__.py b/screensaver/vumeter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/screensaver/vumeter/circular.py b/screensaver/vumeter/circular.py new file mode 100644 index 00000000..ed362504 --- /dev/null +++ b/screensaver/vumeter/circular.py @@ -0,0 +1,100 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +import time +import math +import pygame + +from threading import Thread + +class CircularAnimator(Thread): + """ Provides needle circular animation in a separate thread """ + + def __init__(self, channel, component, base, ui_refresh_period, needle_rects): + """ Initializer + + :param channel: number of channels + :param component: UI component + :param base: meter base + :param ui_refresh_period: animation interval + :param needle_rects: list of sprite rectangles + """ + Thread.__init__(self) + self.channel = channel + self.component = component + self.run_flag = True + self.base = base + self.ui_refresh_period = ui_refresh_period + self.previous_index = 0 + self.needle_rects = needle_rects + self.mask = None + if base.rect.w != base.meter_bounding_box.x: + mask_w = 130 + mask_h = 52 + mask_x = base.rect.w/2 - mask_w/2 + mask_y = base.rect.h - (base.rect.h - base.meter_bounding_box.h)/2 + self.mask = pygame.Rect(mask_x, mask_y, mask_w, mask_h) + + def run(self): + """ Thread method. Converts volume value into the needle angle and displays corresponding sprite. """ + + while self.run_flag: + volume = self.channel.get() + self.channel.task_done() + n = (volume * self.base.max_volume * self.base.incr) / 100.0 + if n >= len(self.base.needle_sprites): n = len(self.base.needle_sprites) - 1 + + if self.previous_index == int(n): + time.sleep(self.ui_refresh_period) + continue + + diff = n - self.previous_index + sub_steps = range(int(abs(diff)) * self.base.steps_per_degree) + sign = int(math.copysign(1, diff)) + for m in sub_steps: + if not self.run_flag: + break + + previous_rect = self.component.bounding_box + self.base.draw_bgr_fgr(previous_rect, self.base.bgr) + next_index = (self.previous_index * self.base.steps_per_degree) + (m * sign) + if next_index >= len(self.base.needle_sprites): next_index = len(self.base.needle_sprites) - 1 + + sprite = self.base.needle_sprites[next_index] + self.component.content = ("", sprite) + r = self.needle_rects[next_index] + self.component.content_x = r.x + self.component.content_y = r.y + r.x = 0 + r.y = 0 + self.component.bounding_box = r + self.component.draw() + r.x = self.component.content_x + r.y = self.component.content_y + + a = previous_rect.union(r) + if self.base.fgr: + self.base.draw_bgr_fgr(a, self.base.fgr) + + if self.mask: + pygame.draw.rect(self.component.screen, (0, 0, 0), self.mask) + a = a.union(self.mask) + + self.base.update_rectangle(a) + time.sleep(self.ui_refresh_period) + self.previous_index = int(n) + diff --git a/screensaver/vumeter/configfileparser.py b/screensaver/vumeter/configfileparser.py new file mode 100644 index 00000000..afa5b7e3 --- /dev/null +++ b/screensaver/vumeter/configfileparser.py @@ -0,0 +1,193 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +import os + +from configparser import ConfigParser +from util.keys import SCREEN_RECT + +CURRENT = "current" +METER = "meter" +DATA_SOURCE = "data.source" +TYPE = "type" +POLLING_INTERVAL = "polling.interval" +PIPE_NAME = "pipe.name" +PIPE_SIZE = "pipe.size" +VOLUME_CONSTANT = "volume.constant" +VOLUME_MIN = "volume.min" +VOLUME_MAX = "volume.max" +STEP = "step" +MONO_ALGORITHM = "mono.algorithm" +STEREO_ALGORITHM = "stereo.algorithm" +METER_TYPE = "meter.type" +CHANNELS = "channels" +UI_REFRESH_PERIOD = "ui.refresh.period" +BGR_FILENAME = "bgr.filename" +FGR_FILENAME = "fgr.filename" +INDICATOR_FILENAME = "indicator.filename" +LEFT_X = "left.x" +LEFT_Y = "left.y" +RIGHT_X = "right.x" +RIGHT_Y = "right.y" +MONO_X = "mono.x" +MONO_Y = "mono.y" +POSITION_REGULAR = "position.regular" +POSITION_OVERLOAD = "position.overload" +STEP_WIDTH_REGULAR = "step.width.regular" +STEP_WIDTH_OVERLOAD = "step.width.overload" +STEPS_PER_DEGREE = "steps.per.degree" +START_ANGLE = "start.angle" +STOP_ANGLE = "stop.angle" +DISTANCE = "distance" +MONO_ORIGIN_X = "mono.origin.x" +MONO_ORIGIN_Y = "mono.origin.y" +LEFT_CENTER_X = "left.center.x" +LEFT_CENTER_Y = "left.center.y" +LEFT_ORIGIN_X = "left.origin.x" +LEFT_ORIGIN_Y = "left.origin.y" +RIGHT_CENTER_X = "right.center.x" +RIGHT_CENTER_Y = "right.center.y" +RIGHT_ORIGIN_X = "right.origin.x" +RIGHT_ORIGIN_Y = "right.origin.y" +METER_NAMES = "meter.names" +RANDOM_METER_INTERVAL = "random.meter.interval" +IMAGE_FOLDER_NAME = "image.folder.name" + +FOLDER_SCREENSAVER = "screensaver" +FOLDER_VUMETER = "vumeter" +FILE_METER_CONFIG = "meters.txt" + +TYPE_LINEAR = "linear" +TYPE_CIRCULAR = "circular" + +class ConfigFileParser(object): + """ Configuration file parser """ + + def __init__(self, util): + """ Initializer """ + + self.meter_config = {} + self.rect = util.config[SCREEN_RECT] + + if self.rect.w < 350: + self.meter_config[IMAGE_FOLDER_NAME] = "small" + else: + self.meter_config[IMAGE_FOLDER_NAME] = "medium" + + path = os.path.join(FOLDER_SCREENSAVER, FOLDER_VUMETER, self.meter_config[IMAGE_FOLDER_NAME], FILE_METER_CONFIG) + + c = ConfigParser() + c.read(path) + meter_names = list() + + for section in c.sections(): + if section == CURRENT: + self.meter_config[METER] = c.get(section, METER) + self.meter_config[RANDOM_METER_INTERVAL] = c.getint(section, RANDOM_METER_INTERVAL) + elif section == DATA_SOURCE: + self.meter_config[section] = self.get_data_source_section(c, section) + else: + meter_names.append(section) + meter_type = c.get(section, METER_TYPE) + if meter_type == TYPE_LINEAR: + self.meter_config[section] = self.get_linear_section(c, section, meter_type) + elif meter_type == TYPE_CIRCULAR: + self.meter_config[section] = self.get_circular_section(c, section, meter_type) + self.meter_config[METER_NAMES] = meter_names + + def get_data_source_section(self, config_file, section): + """ Parser for data source section + + :param config_file: configuration file + :param section: section name + """ + d = {} + d[TYPE] = config_file.get(section, TYPE) + d[POLLING_INTERVAL] = config_file.getfloat(section, POLLING_INTERVAL) + d[PIPE_NAME] = config_file.get(section, PIPE_NAME) + d[PIPE_SIZE] = config_file.getint(section, PIPE_SIZE) + d[VOLUME_CONSTANT] = config_file.getfloat(section, VOLUME_CONSTANT) + d[VOLUME_MIN] = config_file.getfloat(section, VOLUME_MIN) + d[VOLUME_MAX] = config_file.getfloat(section, VOLUME_MAX) + d[MONO_ALGORITHM] = config_file.get(section, MONO_ALGORITHM) + d[STEREO_ALGORITHM] = config_file.get(section, STEREO_ALGORITHM) + d[STEP] = config_file.getint(section, STEP) + return d + + def get_linear_section(self, config_file, section, meter_type): + """ Parser for linear meter + + :param config_file: configuration file + :param section: section name + :param meter_type: type of the meter + """ + d = {} + self.get_common_options(d, config_file, section, meter_type) + d[LEFT_X] = config_file.getint(section, LEFT_X) + d[LEFT_Y] = config_file.getint(section, LEFT_Y) + d[RIGHT_X] = config_file.getint(section, RIGHT_X) + d[RIGHT_Y] = config_file.getint(section, RIGHT_Y) + d[POSITION_REGULAR] = config_file.getint(section, POSITION_REGULAR) + d[STEP_WIDTH_REGULAR] = config_file.getint(section, STEP_WIDTH_REGULAR) + try: + d[POSITION_OVERLOAD] = config_file.getint(section, POSITION_OVERLOAD) + d[STEP_WIDTH_OVERLOAD] = config_file.getint(section, STEP_WIDTH_OVERLOAD) + except: + pass + return d + + def get_circular_section(self, config_file, section, meter_type): + """ Parser for circular meter + + :param config_file: configuration file + :param section: section name + :param meter_type: type of the meter + """ + d = {} + self.get_common_options(d, config_file, section, meter_type) + d[STEPS_PER_DEGREE] = config_file.getint(section, STEPS_PER_DEGREE) + d[START_ANGLE] = config_file.getint(section, START_ANGLE) + d[STOP_ANGLE] = config_file.getint(section, STOP_ANGLE) + d[DISTANCE] = config_file.getint(section, DISTANCE) + if d[CHANNELS] == 1: + d[MONO_ORIGIN_X] = config_file.getint(section, MONO_ORIGIN_X) + d[MONO_ORIGIN_Y] = config_file.getint(section, MONO_ORIGIN_Y) + else: + d[LEFT_ORIGIN_X] = config_file.getint(section, LEFT_ORIGIN_X) + d[LEFT_ORIGIN_Y] = config_file.getint(section, LEFT_ORIGIN_Y) + d[RIGHT_ORIGIN_X] = config_file.getint(section, RIGHT_ORIGIN_X) + d[RIGHT_ORIGIN_Y] = config_file.getint(section, RIGHT_ORIGIN_Y) + return d + + def get_common_options(self, d, config_file, section, meter_type): + """ Parser for the common section of the configuration file + + :param d: common section dictionary + :param config_file: configuration file + :param section: section name + :param meter_type: type of the meter + """ + d[METER_TYPE] = meter_type + d[CHANNELS] = config_file.getint(section, CHANNELS) + d[UI_REFRESH_PERIOD] = config_file.getfloat(section, UI_REFRESH_PERIOD) + d[BGR_FILENAME] = config_file.get(section, BGR_FILENAME) + try: + d[FGR_FILENAME] = config_file.get(section, FGR_FILENAME) + except: + d[FGR_FILENAME] = None + d[INDICATOR_FILENAME] = config_file.get(section, INDICATOR_FILENAME) + diff --git a/screensaver/vumeter/datasource.py b/screensaver/vumeter/datasource.py new file mode 100644 index 00000000..989744f4 --- /dev/null +++ b/screensaver/vumeter/datasource.py @@ -0,0 +1,234 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +import math +import audioop +import time +import logging +import statistics + +from queue import Queue +from random import uniform +from threading import Thread +from screensaver.vumeter.configfileparser import * + +SOURCE_CONSTANT = "constant" +SOURCE_NOISE = "noise" +SOURCE_SAW = "saw" +SOURCE_TRIANGLE = "triangle" +SOURCE_SINE = "sine" +SOURCE_PIPE = "pipe" + +MONO_ALGORITHM_MAXIMUM = "maximum" +MONO_ALGORITHM_AVERAGE = "average" + +STEREO_ALGORITHM_NEW = "new" +STEREO_ALGORITHM_LOGARITHM = "logarithm" +STEREO_ALGORITHM_AVERAGE = "average" + +LEFT = (1, 0) +RIGHT = (0, 1) + +class DataSource(object): + """ Provides methods to generate different types of audio signal. """ + + def __init__(self, c): + """ Initializer + + :param c: configuration dictionary + """ + config = c[DATA_SOURCE] + self.left_channel = Queue(maxsize=1) + self.right_channel = Queue(maxsize=1) + self.mono_channel = Queue(maxsize=1) + self.mono_algorithm = config[MONO_ALGORITHM] + self.stereo_algorithm = config[STEREO_ALGORITHM] + self.ds_type = config[TYPE] + self.const = config[VOLUME_CONSTANT] + self.bits = 16 + self.pipe_name = config[PIPE_NAME] + self.min = config[VOLUME_MIN] + self.max = config[VOLUME_MAX] + self.v = 0 + self.step = config[STEP] + self.pipe_size = config[PIPE_SIZE] + self.rng = list(range(int(self.min), int(self.max))) + self.double_rng = self.rng + self.double_rng.extend(range(int(self.max) - 1, int(self.min), -1)) + self.pipe = None + if self.ds_type == SOURCE_PIPE: + try: + self.pipe = os.open(self.pipe_name, os.O_RDONLY) + except: + logging.debug("cannot open pipe") + self.k = int(math.pow(2, self.bits)//2 - 1) + self.previous_left = self.previous_right = self.previous_mono = 0.0 + self.run_flag = True + self.polling_interval = config[POLLING_INTERVAL] + self.prev_time = None + + def start_data_source(self): + """ Start data source thread. """ + + self.run_flag = True + thread = Thread(target=self.get_data) + thread.start() + + def stop_data_source(self): + """ Stop data source thread. """ + + self.run_flag = False + + def get_data(self): + """ Thread method. Get data and put it into the corresponding queue. """ + + while self.run_flag: + d = self.get_value() + + with self.left_channel.mutex: self.left_channel.queue.clear() + self.left_channel.put(d[0]) + + with self.right_channel.mutex: self.right_channel.queue.clear() + self.right_channel.put(d[1]) + + with self.mono_channel.mutex: self.mono_channel.queue.clear() + self.mono_channel.put(d[2]) + + time.sleep(self.polling_interval) + + def get_value(self): + """ Get data depending on the data source type. """ + + d = () + if self.ds_type == SOURCE_CONSTANT: + d = self.get_constant_value() + elif self.ds_type == SOURCE_NOISE: + d = self.get_noise_value() + elif self.ds_type == SOURCE_SAW: + d = self.get_saw_value() + elif self.ds_type == SOURCE_TRIANGLE: + d = self.get_triangle_value() + elif self.ds_type == SOURCE_SINE: + d = self.get_sine_value() + elif self.ds_type == SOURCE_PIPE: + d = self.get_pipe_value() + return d + + def get_constant_value(self): + """ Returns constant value for all channels. The value is defined in the configuration file. """ + + return (self.const, self.const, self.const) + + def get_noise_value(self): + """ Generate random value for all channels. """ + + new_left = uniform(self.min, self.max) + new_right = uniform(self.min, self.max) + new_mono = self.get_mono(new_left, new_right) + + left = self.get_channel(self.previous_left, new_left) + right = self.get_channel(self.previous_right, new_right) + mono = self.get_channel(self.previous_mono, new_mono) + + self.previous_left = new_left + self.previous_right = new_right + self.previous_mono = new_mono + + return (left, right, mono) + + def get_saw_value(self): + """ Generate saw shape signal. """ + + s = (self.rng[self.v], self.rng[self.v], self.rng[self.v]) + self.v = (self.v + self.step) % self.max + return s + + def get_triangle_value(self): + """ Generate triangle shape signal. """ + + t = (self.double_rng[self.v], self.double_rng[self.v], self.double_rng[self.v]) + self.v = (self.v + self.step) % (int(self.max * 2 - 1)) + return t + + def get_sine_value(self): + """ Generate sine shape signal. """ + + a = int(self.max * ((1 + math.sin(math.radians(-90 + self.v)))/2)) + s = (a, a, a) + self.v = (self.v + self.step * 6) % 360 + return s + + def get_pipe_value(self): + """ Get signal from the named pipe. """ + + data = None + try: + data = os.read(self.pipe, self.pipe_size) + new_left = self.get_pipe_channel(data, LEFT) + new_right = self.get_pipe_channel(data, RIGHT) + new_mono = self.get_mono(new_left, new_right) + + left = self.get_channel(self.previous_left, new_left) + right = self.get_channel(self.previous_right, new_right) + mono = self.get_channel(self.previous_mono, new_mono) + + self.previous_left = new_left + self.previous_right = new_right + self.previous_mono = new_mono + except: + logging.debug("cannot get data from pipe") + + if not data: + return (2.0, 2.0, 2.0) + else: + return (left, right, mono) + + def get_pipe_channel(self, data, channel): + """ Retrieve data for particular channel """ + + ch = audioop.tomono(data, 2, channel[0], channel[1]) + ch_max = audioop.max(ch, 2) + return int(self.max * (ch_max / self.k)) + + def get_mono(self, left, right): + """ Create mono signal from stereo using particular algorithm """ + + if self.mono_algorithm == MONO_ALGORITHM_MAXIMUM: + mono = max(left, right) + elif self.mono_algorithm == MONO_ALGORITHM_AVERAGE: + mono = statistics.mean([left, right]) + return mono + + def get_channel(self, previous_value, new_value): + """ Prepares signal value depending on the previous one and algorithm. """ + + if self.stereo_algorithm == STEREO_ALGORITHM_NEW: + channel_value = new_value + elif self.stereo_algorithm == STEREO_ALGORITHM_LOGARITHM: + if previous_value == 0.0: + channel_value = 0.0 + else: + channel_value = 20 * math.log10(new_value/previous_value) + if channel_value < -20: + channel_value = -20 + if channel_value > 3: + channel_value = 3 + channel_value = (channel_value + 20) * (100/23) + elif self.stereo_algorithm == STEREO_ALGORITHM_AVERAGE: + channel_value = statistics.mean([previous_value, new_value]) + return channel_value + diff --git a/screensaver/vumeter/linear.py b/screensaver/vumeter/linear.py new file mode 100644 index 00000000..5b43e228 --- /dev/null +++ b/screensaver/vumeter/linear.py @@ -0,0 +1,78 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +import time +from threading import Thread + +class LinearAnimator(Thread): + """ Provides linear animation in a separate thread """ + + def __init__(self, channel, component, base, ui_refresh_period): + """ Initializer + + :param channel: number of channels + :param component: UI component + :param base: meter base + :param ui_refresh_period: animation interval + """ + Thread.__init__(self) + self.indicator_height = 30 + self.index = 0 + self.channel = channel + self.component = component + self.run_flag = True + self.base = base + self.ui_refresh_period = ui_refresh_period + self.bgr = self.base.bgr + self.fgr = self.base.fgr + self.previous_volume = 0.0 + + def run(self): + """ Thread method. Converts volume value into the mask width and displays corresponding mask. """ + + previous_rect = self.component.bounding_box.copy() + + while self.run_flag: + volume = self.channel.get() + self.channel.task_done() + if self.previous_volume == volume: + time.sleep(self.ui_refresh_period) + continue + + self.base.draw_bgr_fgr(previous_rect, self.base.bgr) + + n = int((volume * self.base.max_volume) / (self.base.step * 100)) + if n >= len(self.base.masks): n = len(self.base.masks) - 1 + w = self.base.masks[n] + if w == 0: w = 1 + + self.component.bounding_box.w = w + self.component.bounding_box.x = 0 + self.component.bounding_box.y = 0 + self.component.draw() + + r = self.component.bounding_box.copy() + r.x = self.component.content_x + r.y = self.component.content_y + u = previous_rect.union(r) + if self.base.fgr: + self.base.draw_bgr_fgr(u.copy(), self.base.fgr) + + self.base.update_rectangle(u) + previous_rect = r.copy() + self.previous_volume = volume + time.sleep(self.ui_refresh_period) diff --git a/screensaver/vumeter/maskfactory.py b/screensaver/vumeter/maskfactory.py new file mode 100644 index 00000000..49cffbd8 --- /dev/null +++ b/screensaver/vumeter/maskfactory.py @@ -0,0 +1,44 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +class MaskFactory(object): + """ Factory to prepare masks for linear animator """ + + def __init__(self): + """ Initializer """ + + pass + + def create_masks(self, positions_regular, positions_overload, step_width_regular, step_width_overload): + """ Create linear animator masks + + :param positions_regular: number of regular steps + :param positions_overload: number of overload steps + :param step_width_regular: the width in pixels of the regular step + :param step_width_overload: the width in pixels of the overload step + """ + steps = positions_regular + positions_overload + 1 + self.step = 100/steps + masks = list() + masks.append(0) + + for n in range(1, positions_regular + 1): + masks.append(n * step_width_regular) + for n in range(1, positions_overload + 1): + masks.append(positions_regular * step_width_regular + n * step_width_overload) + + return masks diff --git a/screensaver/vumeter/medium/bar-bgr.png b/screensaver/vumeter/medium/bar-bgr.png new file mode 100644 index 00000000..d8d48dd1 Binary files /dev/null and b/screensaver/vumeter/medium/bar-bgr.png differ diff --git a/screensaver/vumeter/medium/bar-fgr.png b/screensaver/vumeter/medium/bar-fgr.png new file mode 100644 index 00000000..975933b4 Binary files /dev/null and b/screensaver/vumeter/medium/bar-fgr.png differ diff --git a/screensaver/vumeter/medium/bar-indicator.png b/screensaver/vumeter/medium/bar-indicator.png new file mode 100644 index 00000000..5bef4814 Binary files /dev/null and b/screensaver/vumeter/medium/bar-indicator.png differ diff --git a/screensaver/vumeter/medium/blue-bgr.png b/screensaver/vumeter/medium/blue-bgr.png new file mode 100644 index 00000000..6fc27c99 Binary files /dev/null and b/screensaver/vumeter/medium/blue-bgr.png differ diff --git a/screensaver/vumeter/medium/blue-needle.png b/screensaver/vumeter/medium/blue-needle.png new file mode 100644 index 00000000..c126eb97 Binary files /dev/null and b/screensaver/vumeter/medium/blue-needle.png differ diff --git a/screensaver/vumeter/medium/compass-bgr.png b/screensaver/vumeter/medium/compass-bgr.png new file mode 100644 index 00000000..f5cd8b6d Binary files /dev/null and b/screensaver/vumeter/medium/compass-bgr.png differ diff --git a/screensaver/vumeter/medium/compass-fgr.png b/screensaver/vumeter/medium/compass-fgr.png new file mode 100644 index 00000000..db168361 Binary files /dev/null and b/screensaver/vumeter/medium/compass-fgr.png differ diff --git a/screensaver/vumeter/medium/compass-needle.png b/screensaver/vumeter/medium/compass-needle.png new file mode 100644 index 00000000..52ff6a86 Binary files /dev/null and b/screensaver/vumeter/medium/compass-needle.png differ diff --git a/screensaver/vumeter/medium/dash-bgr.png b/screensaver/vumeter/medium/dash-bgr.png new file mode 100644 index 00000000..d84aed2c Binary files /dev/null and b/screensaver/vumeter/medium/dash-bgr.png differ diff --git a/screensaver/vumeter/medium/dash-indicator.png b/screensaver/vumeter/medium/dash-indicator.png new file mode 100644 index 00000000..6d7574c4 Binary files /dev/null and b/screensaver/vumeter/medium/dash-indicator.png differ diff --git a/screensaver/vumeter/medium/gas-bgr.png b/screensaver/vumeter/medium/gas-bgr.png new file mode 100644 index 00000000..fd45379d Binary files /dev/null and b/screensaver/vumeter/medium/gas-bgr.png differ diff --git a/screensaver/vumeter/medium/gas-indicator.png b/screensaver/vumeter/medium/gas-indicator.png new file mode 100644 index 00000000..a163ee38 Binary files /dev/null and b/screensaver/vumeter/medium/gas-indicator.png differ diff --git a/screensaver/vumeter/medium/grunge-bgr.png b/screensaver/vumeter/medium/grunge-bgr.png new file mode 100644 index 00000000..abe4f030 Binary files /dev/null and b/screensaver/vumeter/medium/grunge-bgr.png differ diff --git a/screensaver/vumeter/medium/grunge-fgr.png b/screensaver/vumeter/medium/grunge-fgr.png new file mode 100644 index 00000000..91c46952 Binary files /dev/null and b/screensaver/vumeter/medium/grunge-fgr.png differ diff --git a/screensaver/vumeter/medium/grunge-needle.png b/screensaver/vumeter/medium/grunge-needle.png new file mode 100644 index 00000000..038cd3a7 Binary files /dev/null and b/screensaver/vumeter/medium/grunge-needle.png differ diff --git a/screensaver/vumeter/medium/meters.txt b/screensaver/vumeter/medium/meters.txt new file mode 100644 index 00000000..88845206 --- /dev/null +++ b/screensaver/vumeter/medium/meters.txt @@ -0,0 +1,147 @@ +[current] +meter = random +random.meter.interval = 30 + +[data.source] +type = noise +polling.interval = 0.01 +pipe.name = /home/pi/mpd.fifo +pipe.size = 4096 +volume.constant = 50.0 +volume.min = 0.0 +volume.max = 100.0 +step = 6 +mono.algorithm = maximum +stereo.algorithm = average + +[bar] +meter.type = linear +channels = 2 +ui.refresh.period = 0.08 +bgr.filename = bar-bgr.png +fgr.filename = bar-fgr.png +indicator.filename = bar-indicator.png +left.x = 130 +left.y = 113 +right.x = 130 +right.y = 178 +position.regular = 11 +position.overload = 3 +step.width.regular = 19 +step.width.overload = 25 + +[blue] +meter.type = circular +channels = 1 +ui.refresh.period = 0.003 +bgr.filename = blue-bgr.png +indicator.filename = blue-needle.png +steps.per.degree = 3 +start.angle = 45 +stop.angle = -45 +distance = 183 +mono.origin.x = 238 +mono.origin.y = 380 + +[vintage] +meter.type = circular +channels = 2 +ui.refresh.period = 0.002 +bgr.filename = vintage-bgr.png +fgr.filename = vintage-fgr.png +indicator.filename = vintage-needle.png +steps.per.degree = 3 +start.angle = 45 +stop.angle = -45 +distance = 54 +left.origin.x = 129 +left.origin.y = 217 +right.origin.x = 349 +right.origin.y = 217 + +[dash] +meter.type = linear +channels = 2 +ui.refresh.period = 0.1 +bgr.filename = dash-bgr.png +indicator.filename = dash-indicator.png +left.x = 29 +left.y = 134 +right.x = 29 +right.y = 173 +position.regular = 9 +position.overload = 4 +step.width.regular = 34 +step.width.overload = 34 + +[gas] +meter.type = linear +channels = 2 +ui.refresh.period = 0.08 +bgr.filename = gas-bgr.png +indicator.filename = gas-indicator.png +left.x = 46 +left.y = 132 +right.x = 46 +right.y = 169 +position.regular = 69 +step.width.regular = 6 + +[rainbow] +meter.type = circular +channels = 1 +ui.refresh.period = 0.006 +bgr.filename = rainbow-bgr.png +indicator.filename = rainbow-needle.png +steps.per.degree = 3 +start.angle = 39 +stop.angle = -39 +distance = 165 +mono.origin.x = 242 +mono.origin.y = 364 + +[grunge] +meter.type = circular +channels = 2 +ui.refresh.period = 0.002 +bgr.filename = grunge-bgr.png +fgr.filename = grunge-fgr.png +indicator.filename = grunge-needle.png +steps.per.degree = 3 +start.angle = 45 +stop.angle = -45 +distance = 28 +left.origin.x = 125 +left.origin.y = 195 +right.origin.x = 357 +right.origin.y = 195 + +[royal] +meter.type = circular +channels = 1 +ui.refresh.period = 0.004 +bgr.filename = royal-bgr.png +fgr.filename = royal-fgr.png +indicator.filename = royal-needle.png +steps.per.degree = 3 +start.angle = 52 +stop.angle = -51 +distance = 82 +mono.origin.x = 240 +mono.origin.y = 243 + +[compass] +meter.type = circular +channels = 2 +ui.refresh.period = 0.002 +bgr.filename = compass-bgr.png +fgr.filename = compass-fgr.png +indicator.filename = compass-needle.png +steps.per.degree = 3 +start.angle = 55 +stop.angle = -55 +distance = 0 +left.origin.x = 128 +left.origin.y = 162 +right.origin.x = 351 +right.origin.y = 162 \ No newline at end of file diff --git a/screensaver/vumeter/medium/rainbow-bgr.png b/screensaver/vumeter/medium/rainbow-bgr.png new file mode 100644 index 00000000..8e62e487 Binary files /dev/null and b/screensaver/vumeter/medium/rainbow-bgr.png differ diff --git a/screensaver/vumeter/medium/rainbow-needle.png b/screensaver/vumeter/medium/rainbow-needle.png new file mode 100644 index 00000000..6ad174d1 Binary files /dev/null and b/screensaver/vumeter/medium/rainbow-needle.png differ diff --git a/screensaver/vumeter/medium/royal-bgr.png b/screensaver/vumeter/medium/royal-bgr.png new file mode 100644 index 00000000..1fffc98e Binary files /dev/null and b/screensaver/vumeter/medium/royal-bgr.png differ diff --git a/screensaver/vumeter/medium/royal-fgr.png b/screensaver/vumeter/medium/royal-fgr.png new file mode 100644 index 00000000..f8e23e8e Binary files /dev/null and b/screensaver/vumeter/medium/royal-fgr.png differ diff --git a/screensaver/vumeter/medium/royal-needle.png b/screensaver/vumeter/medium/royal-needle.png new file mode 100644 index 00000000..fca89123 Binary files /dev/null and b/screensaver/vumeter/medium/royal-needle.png differ diff --git a/screensaver/vumeter/medium/vintage-bgr.png b/screensaver/vumeter/medium/vintage-bgr.png new file mode 100644 index 00000000..1ca060f7 Binary files /dev/null and b/screensaver/vumeter/medium/vintage-bgr.png differ diff --git a/screensaver/vumeter/medium/vintage-fgr.png b/screensaver/vumeter/medium/vintage-fgr.png new file mode 100644 index 00000000..48b7aa0a Binary files /dev/null and b/screensaver/vumeter/medium/vintage-fgr.png differ diff --git a/screensaver/vumeter/medium/vintage-needle.png b/screensaver/vumeter/medium/vintage-needle.png new file mode 100644 index 00000000..6d986214 Binary files /dev/null and b/screensaver/vumeter/medium/vintage-needle.png differ diff --git a/screensaver/vumeter/meter.py b/screensaver/vumeter/meter.py new file mode 100644 index 00000000..49771c62 --- /dev/null +++ b/screensaver/vumeter/meter.py @@ -0,0 +1,210 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +import os +import pygame + +from ui.component import Component +from ui.container import Container +from util.keys import SCREEN_RECT +from screensaver.vumeter.configfileparser import TYPE_LINEAR, TYPE_CIRCULAR +from screensaver.vumeter.linear import LinearAnimator +from screensaver.vumeter.circular import CircularAnimator + +class Meter(Container): + """ The base class for all meters """ + + def __init__(self, util, meter_type, image_folder, ui_refresh_period, left_channel_queue=None, right_channel_queue=None, mono_channel_queue=None): + """ Initializer + + :param util: utility class + :param meter_type: the type of the meter - linear or circular + :param image_folder: image folder + :param ui_refresh_period: refresh interval for animation + :param left_channel_queue: left channel queue + :param right_channel_queue: right channel queue + :param mono_channel_queue: mono channel queue + """ + self.util = util + self.config = util.config + self.meter_type = meter_type + self.image_folder = image_folder + self.ui_refresh_period = ui_refresh_period + self.left_channel_queue = left_channel_queue + self.right_channel_queue = right_channel_queue + self.mono_channel_queue = mono_channel_queue + self.rect = self.config[SCREEN_RECT] + Container.__init__(self, util, self.rect, (0, 0, 0)) + self.max_volume = 0.0 + self.total_steps = 100 + self.origin_x = self.origin_y = 0 + self.meter_bounding_box = None + self.bgr = None + self.fgr = None + self.left_sprites = None + self.right_sprites = None + self.needle_sprites = None + self.mono_needle_rects = None + self.left_needle_rects = None + self.right_needle_rects = None + self.masks = None + self.channels = 1 + + def add_background(self, image_name): + """ Position and add background image. + + :param image_name: the name of the background image + """ + img = self.load_image(image_name) + self.origin_x = (self.rect.w - img[1].get_size()[0])/2 + self.origin_y = (self.rect.h - img[1].get_size()[1])/2 + self.meter_bounding_box = img[1].get_rect() + self.meter_bounding_box.x = self.origin_x + self.meter_bounding_box.y = self.origin_y + self.bgr = self.add_image(img, self.origin_x, self.origin_y, self.meter_bounding_box) + + def add_foreground(self, image_name): + """ Position and add foreground image. + + :param image_name: the name of the foreground image + """ + if not image_name: return + img = self.load_image(image_name) + self.fgr = self.add_image(img, self.origin_x, self.origin_y, self.meter_bounding_box) + + def add_channel(self, image_name, x, y): + """ Position and add channel indicator image. + + :param image_name: the name of the indicator image + """ + img = self.load_image(image_name) + r = img[1].get_rect() + r.x = self.origin_x + x + r.y = self.origin_y + y + r.w = 100 + self.add_image(img, self.origin_x + x, self.origin_y + y, r) + + def load_image(self, image_name): + """ Load image + + :param image_name: the image name + """ + path = os.path.join("screensaver", "vumeter", self.image_folder, image_name) + return self.util.load_pygame_image(path) + + def add_image(self, image, x, y, rect=None): + """ Create new UI component from provided image and add it to the UI container. + + :param image: the image object + :param x: x coordinate of the image top left corner + :param y: y coordinate of the image top left corner + :param rect: bounding rectangle of the image + """ + c = Component(self.util) + c.content = image + c.content_x = x + c.content_y = y + if rect: c.bounding_box = rect + self.add_component(c) + return c + + def set_volume(self, volume): + """ Set volume level """ + + self.max_volume = volume + + def draw_bgr_fgr(self, rect, comp): + """ Draw either background or foreground component """ + + if not rect: return + comp.content_x = rect.x + comp.content_y = rect.y + + comp.bounding_box = pygame.Rect(rect.x - self.origin_x, rect.y - self.origin_y, rect.w, rect.h) + comp.draw() + + def start(self): + """ Initialize meter and start meter animation. """ + + self.reset_bgr_fgr(self.bgr) + + if self.needle_sprites: + if self.channels == 1: + self.components[1].content = self.needle_sprites[0] + self.components[1].bounding_box = self.mono_needle_rects[0] + elif self.channels == 2: + self.components[1].content = self.needle_sprites[0] + self.components[1].bounding_box = self.left_needle_rects[0] + self.components[2].content = self.needle_sprites[0] + self.components[2].bounding_box = self.right_needle_rects[0] + + if self.masks: + self.reset_mask(self.components[1]) + self.reset_mask(self.components[2]) + + if self.fgr: self.reset_bgr_fgr(self.fgr) + + super(Meter, self).draw() + self.update() + + if self.left_channel_queue: + self.left = self.start_animator(self.left_channel_queue, self.components[1], self.left_needle_rects) + + if self.right_channel_queue: + self.right = self.start_animator(self.right_channel_queue, self.components[2], self.right_needle_rects) + + if self.mono_channel_queue: + self.mono = self.start_animator(self.mono_channel_queue, self.components[1], self.mono_needle_rects) + + def reset_bgr_fgr(self, comp): + """ Reset background or foreground bounding box + + :param comp: component to reset + """ + comp.bounding_box = comp.content[1].get_rect() + comp.content_x = self.origin_x + comp.content_y = self.origin_y + + def reset_mask(self, comp): + """ Initialize linear mask. """ + + comp.bounding_box.x = comp.content_x + comp.bounding_box.y = comp.content_y + comp.bounding_box.w = 1 + + def start_animator(self, queue, component, needle_rects=None): + """ Start meter animation + + :param queue: data provider queue + :param component: component to animate + :param needle_rects: list of needle bounding boxes + """ + a = None + if self.meter_type == TYPE_LINEAR: + a = LinearAnimator(queue, component, self, self.ui_refresh_period) + elif self.meter_type == TYPE_CIRCULAR: + a = CircularAnimator(queue, component, self, self.ui_refresh_period, needle_rects) + a.setDaemon(True) + a.start() + return a + + def stop(self): + """ Stop meter animation """ + + if self.left_channel_queue: self.left.run_flag = False + if self.right_channel_queue: self.right.run_flag = False + if self.mono_channel_queue: self.mono.run_flag = False diff --git a/screensaver/vumeter/meterfactory.py b/screensaver/vumeter/meterfactory.py new file mode 100644 index 00000000..933077e5 --- /dev/null +++ b/screensaver/vumeter/meterfactory.py @@ -0,0 +1,141 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +from screensaver.vumeter.meter import Meter +from screensaver.vumeter.maskfactory import MaskFactory +from screensaver.vumeter.needlefactory import NeedleFactory +from screensaver.vumeter.configfileparser import * + +class MeterFactory(object): + """ Meter creation factory """ + + def __init__(self, util, meter_config, data_source): + """ Initializer + + :param util: utility class + :param meter_config: configuration dictionary + :param data_source: the source of audio data + """ + self.util = util + self.meter_config = meter_config + self.data_source = data_source + + def create_meter(self): + """ Dispatcher method """ + + meter_name = self.meter_config[METER] + meter_config_section = self.meter_config[meter_name] + if meter_config_section[METER_TYPE] == TYPE_LINEAR: + return self.create_linear_meter(meter_name) + elif meter_config_section[METER_TYPE] == TYPE_CIRCULAR: + return self.create_circular_meter(meter_name) + + def create_linear_meter(self, name): + """ Create linear method + + :param name: meter name + """ + config = self.meter_config[name] + + if config[CHANNELS] == 2: + left_channel_queue = self.data_source.left_channel + right_channel_queue = self.data_source.right_channel + meter = Meter(self.util, TYPE_LINEAR, self.meter_config[IMAGE_FOLDER_NAME], config[UI_REFRESH_PERIOD], left_channel_queue, right_channel_queue) + meter.channels = 2 + meter.left_x = config[LEFT_X] + meter.left_y = config[LEFT_Y] + meter.right_x = config[RIGHT_X] + meter.right_y = config[RIGHT_Y] + else: + mono_channel_queue = self.data_source.mono_channel + meter = Meter(self.util, TYPE_LINEAR, self.meter_config[IMAGE_FOLDER_NAME], config[UI_REFRESH_PERIOD], mono_channel_queue) + meter.x = config[MONO_X] + meter.y = config[MONO_Y] + + meter.positions_regular = config[POSITION_REGULAR] + meter.step_width_regular = config[STEP_WIDTH_REGULAR] + if POSITION_OVERLOAD in config: + meter.positions_overload = config[POSITION_OVERLOAD] + meter.step_width_overload = config[STEP_WIDTH_OVERLOAD] + else: + meter.positions_overload = 0 + meter.step_width_overload = 0 + meter.total_steps = meter.positions_regular + meter.positions_overload + 1 + meter.step = 100/meter.total_steps + + meter.add_background(config[BGR_FILENAME]) + + if config[CHANNELS] == 2: + meter.add_channel(config[INDICATOR_FILENAME], meter.left_x, meter.left_y) + meter.add_channel(config[INDICATOR_FILENAME], meter.right_x, meter.right_y) + else: + meter.add_channel(config[INDICATOR_FILENAME], meter.x, meter.y) + + meter.add_foreground(config[FGR_FILENAME]) + + factory = MaskFactory() + meter.masks = factory.create_masks(meter.positions_regular, meter.positions_overload, meter.step_width_regular, meter.step_width_overload) + + return meter + + def create_circular_meter(self, name): + """ Create circular meter + + :param name: meter name + """ + config = self.meter_config[name] + + if config[CHANNELS] == 2: + left_channel_queue = self.data_source.left_channel + right_channel_queue = self.data_source.right_channel + meter = Meter(self.util, TYPE_CIRCULAR, self.meter_config[IMAGE_FOLDER_NAME], config[UI_REFRESH_PERIOD], left_channel_queue, right_channel_queue) + meter.channels = 2 + else: + mono_channel_queue = self.data_source.mono_channel + meter = Meter(self.util, TYPE_CIRCULAR, self.meter_config[IMAGE_FOLDER_NAME], config[UI_REFRESH_PERIOD], mono_channel_queue=mono_channel_queue) + + meter.steps_per_degree = config[STEPS_PER_DEGREE] + start_angle = config[START_ANGLE] + stop_angle = config[STOP_ANGLE] + meter.incr = (abs(start_angle) + abs(stop_angle)) / 100 + meter.add_background(config[BGR_FILENAME]) + needle = meter.load_image(config[INDICATOR_FILENAME])[1] + + factory = NeedleFactory(needle, config, meter.origin_x, meter.origin_y) + meter.needle_sprites = factory.needle_sprites + + if config[CHANNELS] == 2: + meter.left_needle_rects = factory.left_needle_rects + meter.right_needle_rects = factory.right_needle_rects + meter.left_needle_rects = factory.left_needle_rects + s = meter.needle_sprites[0] + r = meter.left_needle_rects[0] + meter.add_image(s, 0, 0, r) + r = meter.right_needle_rects[0] + meter.add_image(s, 0, 0, r) + else: + meter.mono_needle_rects = factory.mono_needle_rects + s = meter.needle_sprites[0] + r = meter.mono_needle_rects[0] + meter.add_image(s, 0, 0, r) + + if config[FGR_FILENAME]: + meter.add_foreground(config[FGR_FILENAME]) + + return meter + + \ No newline at end of file diff --git a/screensaver/vumeter/needlefactory.py b/screensaver/vumeter/needlefactory.py new file mode 100644 index 00000000..91b96a69 --- /dev/null +++ b/screensaver/vumeter/needlefactory.py @@ -0,0 +1,78 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +import math +import pygame as pg +from screensaver.vumeter.configfileparser import * + +class NeedleFactory(object): + """ Factory to prepare needle sprites for circular animator """ + + def __init__(self, image, config, screen_x, screen_y): + """ Initializer + + :param image: base needle image + :param config: configuration dictionary + :param screen_x: x coordinate of the screen + :param screen_y: y coordinate of the screen + """ + self.image = image + self.config = config + self.screen_x = screen_x + self.screen_y = screen_y + self.needle_sprites = None + self.mono_needle_rects = None + self.left_needle_rects = None + self.right_needle_rects = None + self.needle_sprites = list() + self.angle_range = abs(self.config[STOP_ANGLE] - self.config[START_ANGLE]) + + if config[CHANNELS] == 1: + self.mono_needle_rects = list() + self.create_needle_sprites(self.needle_sprites, self.mono_needle_rects, self.config[DISTANCE], self.config[MONO_ORIGIN_X], self.config[MONO_ORIGIN_Y]) + elif config[CHANNELS] == 2: + self.left_needle_rects = list() + self.create_needle_sprites(self.needle_sprites, self.left_needle_rects, self.config[DISTANCE], self.config[LEFT_ORIGIN_X], self.config[LEFT_ORIGIN_Y]) + self.right_needle_rects = list() + self.create_needle_sprites(None, self.right_needle_rects, self.config[DISTANCE], self.config[RIGHT_ORIGIN_X], self.config[RIGHT_ORIGIN_Y]) + + def create_needle_sprites(self, needle_sprites, needle_rects, d, o_x, o_y): + """ Create needle sprites + + :param needle_sprites: list for image sprites + :param needle_rects: list for sprite bounding boxes + :param d: the distance between image center and rotation origin + :param o_x: x coordinate of the rotation origin + :param o_y: y coordinate of the rotation origin + """ + img = pg.transform.rotozoom(self.image, self.config[START_ANGLE], 1) + self.initial_angle = self.config[START_ANGLE] + start_angle = math.atan2(img.get_rect().h/2, -img.get_rect().w/2) - math.radians(self.config[START_ANGLE]) + + for _ in range(self.angle_range * self.config[STEPS_PER_DEGREE]): + self.initial_angle = (self.initial_angle - 1/self.config[STEPS_PER_DEGREE]) % 360 + new_angle = math.radians(self.initial_angle) + start_angle + new_center = (o_x + d * math.cos(new_angle), o_y - d * math.sin(new_angle)) + img = pg.transform.rotozoom(self.image, self.initial_angle, 1) + r = img.get_rect() + img = img.subsurface((r.x, r.y, r.w, r.h)) + rect = img.get_rect(center=new_center) + if needle_sprites != None: + needle_sprites.append(img) + rect.x += self.screen_x + rect.y += self.screen_y + needle_rects.append(rect) diff --git a/screensaver/vumeter/small/bar-bgr.png b/screensaver/vumeter/small/bar-bgr.png new file mode 100644 index 00000000..0e4d54f5 Binary files /dev/null and b/screensaver/vumeter/small/bar-bgr.png differ diff --git a/screensaver/vumeter/small/bar-fgr.png b/screensaver/vumeter/small/bar-fgr.png new file mode 100644 index 00000000..2d7ef550 Binary files /dev/null and b/screensaver/vumeter/small/bar-fgr.png differ diff --git a/screensaver/vumeter/small/bar-indicator.png b/screensaver/vumeter/small/bar-indicator.png new file mode 100644 index 00000000..a3cadc49 Binary files /dev/null and b/screensaver/vumeter/small/bar-indicator.png differ diff --git a/screensaver/vumeter/small/blue-bgr.png b/screensaver/vumeter/small/blue-bgr.png new file mode 100644 index 00000000..ca35196b Binary files /dev/null and b/screensaver/vumeter/small/blue-bgr.png differ diff --git a/screensaver/vumeter/small/blue-needle.png b/screensaver/vumeter/small/blue-needle.png new file mode 100644 index 00000000..0e3d68a0 Binary files /dev/null and b/screensaver/vumeter/small/blue-needle.png differ diff --git a/screensaver/vumeter/small/compass-bgr.png b/screensaver/vumeter/small/compass-bgr.png new file mode 100644 index 00000000..6c68274f Binary files /dev/null and b/screensaver/vumeter/small/compass-bgr.png differ diff --git a/screensaver/vumeter/small/compass-fgr.png b/screensaver/vumeter/small/compass-fgr.png new file mode 100644 index 00000000..295fb856 Binary files /dev/null and b/screensaver/vumeter/small/compass-fgr.png differ diff --git a/screensaver/vumeter/small/compass-needle.png b/screensaver/vumeter/small/compass-needle.png new file mode 100644 index 00000000..af0a02fd Binary files /dev/null and b/screensaver/vumeter/small/compass-needle.png differ diff --git a/screensaver/vumeter/small/dash-bgr.png b/screensaver/vumeter/small/dash-bgr.png new file mode 100644 index 00000000..22c03595 Binary files /dev/null and b/screensaver/vumeter/small/dash-bgr.png differ diff --git a/screensaver/vumeter/small/dash-indicator.png b/screensaver/vumeter/small/dash-indicator.png new file mode 100644 index 00000000..aad8adbb Binary files /dev/null and b/screensaver/vumeter/small/dash-indicator.png differ diff --git a/screensaver/vumeter/small/gas-bgr.png b/screensaver/vumeter/small/gas-bgr.png new file mode 100644 index 00000000..4e0e2e66 Binary files /dev/null and b/screensaver/vumeter/small/gas-bgr.png differ diff --git a/screensaver/vumeter/small/gas-indicator.png b/screensaver/vumeter/small/gas-indicator.png new file mode 100644 index 00000000..e3cb199c Binary files /dev/null and b/screensaver/vumeter/small/gas-indicator.png differ diff --git a/screensaver/vumeter/small/grunge-bgr.png b/screensaver/vumeter/small/grunge-bgr.png new file mode 100644 index 00000000..ca753c05 Binary files /dev/null and b/screensaver/vumeter/small/grunge-bgr.png differ diff --git a/screensaver/vumeter/small/grunge-fgr.png b/screensaver/vumeter/small/grunge-fgr.png new file mode 100644 index 00000000..9f733700 Binary files /dev/null and b/screensaver/vumeter/small/grunge-fgr.png differ diff --git a/screensaver/vumeter/small/grunge-needle.png b/screensaver/vumeter/small/grunge-needle.png new file mode 100644 index 00000000..1ec9e1a7 Binary files /dev/null and b/screensaver/vumeter/small/grunge-needle.png differ diff --git a/screensaver/vumeter/small/meters.txt b/screensaver/vumeter/small/meters.txt new file mode 100644 index 00000000..e4510331 --- /dev/null +++ b/screensaver/vumeter/small/meters.txt @@ -0,0 +1,147 @@ +[current] +meter = random +random.meter.interval = 30 + +[data.source] +type = pipe +polling.interval = 0.01 +pipe.name = /home/pi/mpd.fifo +pipe.size = 4096 +volume.constant = 0.0 +volume.min = 0.0 +volume.max = 100.0 +step = 6 +mono.algorithm = maximum +stereo.algorithm = average + +[bar] +meter.type = linear +channels = 2 +ui.refresh.period = 0.08 +bgr.filename = bar-bgr.png +fgr.filename = bar-fgr.png +indicator.filename = bar-indicator.png +left.x = 87 +left.y = 89 +right.x = 87 +right.y = 133 +position.regular = 11 +position.overload = 3 +step.width.regular = 12 +step.width.overload = 20 + +[blue] +meter.type = circular +channels = 1 +ui.refresh.period = 0.003 +bgr.filename = blue-bgr.png +indicator.filename = blue-needle.png +steps.per.degree = 3 +start.angle = 45 +stop.angle = -45 +distance = 109 +mono.origin.x = 159 +mono.origin.y = 258 + +[vintage] +meter.type = circular +channels = 2 +ui.refresh.period = 0.002 +bgr.filename = vintage-bgr.png +fgr.filename = vintage-fgr.png +indicator.filename = vintage-needle.png +steps.per.degree = 3 +start.angle = 45 +stop.angle = -45 +distance = 28 +left.origin.x = 87 +left.origin.y = 157 +right.origin.x = 234 +right.origin.y = 157 + +[dash] +meter.type = linear +channels = 2 +ui.refresh.period = 0.1 +bgr.filename = dash-bgr.png +indicator.filename = dash-indicator.png +left.x = 18 +left.y = 101 +right.x = 18 +right.y = 128 +position.regular = 9 +position.overload = 4 +step.width.regular = 22 +step.width.overload = 24 + +[gas] +meter.type = linear +channels = 2 +ui.refresh.period = 0.08 +bgr.filename = gas-bgr.png +indicator.filename = gas-indicator.png +left.x = 30 +left.y = 101 +right.x = 30 +right.y = 125 +position.regular = 69 +step.width.regular = 4 + +[rainbow] +meter.type = circular +channels = 1 +ui.refresh.period = 0.006 +bgr.filename = rainbow-bgr.png +indicator.filename = rainbow-needle.png +steps.per.degree = 3 +start.angle = 39 +stop.angle = -39 +distance = 120 +mono.origin.x = 162 +mono.origin.y = 257 + +[grunge] +meter.type = circular +channels = 2 +ui.refresh.period = 0.002 +bgr.filename = grunge-bgr.png +fgr.filename = grunge-fgr.png +indicator.filename = grunge-needle.png +steps.per.degree = 3 +start.angle = 49 +stop.angle = -46 +distance = 20 +left.origin.x = 82 +left.origin.y = 140 +right.origin.x = 237 +right.origin.y = 140 + +[royal] +meter.type = circular +channels = 1 +ui.refresh.period = 0.004 +bgr.filename = royal-bgr.png +fgr.filename = royal-fgr.png +indicator.filename = royal-needle.png +steps.per.degree = 3 +start.angle = 52 +stop.angle = -51 +distance = 52 +mono.origin.x = 160 +mono.origin.y = 174 + +[compass] +meter.type = circular +channels = 2 +ui.refresh.period = 0.002 +bgr.filename = compass-bgr.png +fgr.filename = compass-fgr.png +indicator.filename = compass-needle.png +steps.per.degree = 3 +start.angle = 55 +stop.angle = -56 +distance = 0 +left.origin.x = 85 +left.origin.y = 119 +right.origin.x = 234 +right.origin.y = 119 \ No newline at end of file diff --git a/screensaver/vumeter/small/rainbow-bgr.png b/screensaver/vumeter/small/rainbow-bgr.png new file mode 100644 index 00000000..b318356a Binary files /dev/null and b/screensaver/vumeter/small/rainbow-bgr.png differ diff --git a/screensaver/vumeter/small/rainbow-needle.png b/screensaver/vumeter/small/rainbow-needle.png new file mode 100644 index 00000000..322b2abc Binary files /dev/null and b/screensaver/vumeter/small/rainbow-needle.png differ diff --git a/screensaver/vumeter/small/royal-bgr.png b/screensaver/vumeter/small/royal-bgr.png new file mode 100644 index 00000000..a1690c44 Binary files /dev/null and b/screensaver/vumeter/small/royal-bgr.png differ diff --git a/screensaver/vumeter/small/royal-fgr.png b/screensaver/vumeter/small/royal-fgr.png new file mode 100644 index 00000000..a8cd6661 Binary files /dev/null and b/screensaver/vumeter/small/royal-fgr.png differ diff --git a/screensaver/vumeter/small/royal-needle.png b/screensaver/vumeter/small/royal-needle.png new file mode 100644 index 00000000..17ae18ae Binary files /dev/null and b/screensaver/vumeter/small/royal-needle.png differ diff --git a/screensaver/vumeter/small/vintage-bgr.png b/screensaver/vumeter/small/vintage-bgr.png new file mode 100644 index 00000000..3168d1ca Binary files /dev/null and b/screensaver/vumeter/small/vintage-bgr.png differ diff --git a/screensaver/vumeter/small/vintage-fgr.png b/screensaver/vumeter/small/vintage-fgr.png new file mode 100644 index 00000000..e5e9a608 Binary files /dev/null and b/screensaver/vumeter/small/vintage-fgr.png differ diff --git a/screensaver/vumeter/small/vintage-needle.png b/screensaver/vumeter/small/vintage-needle.png new file mode 100644 index 00000000..2d1782aa Binary files /dev/null and b/screensaver/vumeter/small/vintage-needle.png differ diff --git a/screensaver/vumeter/vumeter.py b/screensaver/vumeter/vumeter.py new file mode 100644 index 00000000..206a564d --- /dev/null +++ b/screensaver/vumeter/vumeter.py @@ -0,0 +1,111 @@ +# Copyright 2016 Peppy Player peppy.player@gmail.com +# +# This file is part of Peppy Player. +# +# Peppy Player 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. +# +# Peppy Player 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 Peppy Player. If not, see . + +from random import randrange +import time +import copy + +from util.keys import LINUX_PLATFORM +from screensaver.vumeter.datasource import DataSource +from screensaver.vumeter.meterfactory import MeterFactory +from screensaver.screensaver import Screensaver +from screensaver.vumeter.configfileparser import ConfigFileParser +from screensaver.vumeter.configfileparser import METER, METER_NAMES, RANDOM_METER_INTERVAL, DATA_SOURCE, TYPE +from screensaver.vumeter.datasource import SOURCE_NOISE +from util.config import AUDIO, SERVER_COMMAND + +class Vumeter(Screensaver): + """ VU Meter plug-in. """ + + def __init__(self, util): + """ Initializer + + :param util: utility class + """ + self.util = util + self.update_period = 1 + self.meter = None + self.USE_VOLUME = False + + parser = ConfigFileParser(util) + self.meter_config = parser.meter_config + self.meter_names = self.meter_config[METER_NAMES] + self.random_meter_interval = self.meter_config[RANDOM_METER_INTERVAL] + + # no VU Meter support for Windows and mplayer at the moment + if not util.config[LINUX_PLATFORM] or "mplayer" in util.config[AUDIO][SERVER_COMMAND]: + self.meter_config[DATA_SOURCE][TYPE] = SOURCE_NOISE + + self.data_source = DataSource(self.meter_config) + self.random_meter = False + + if self.meter_config[METER] == "random": + self.random_meter = True + self.random_meter_names = copy.copy(self.meter_names) + + self.meter = None + self.current_volume = 100.0 + self.seconds = 0 + + def get_meter(self): + """ Creates meter using meter factory. """ + + if self.meter and not self.random_meter: + return self.meter + + if self.random_meter: + if len(self.random_meter_names) == 0: + self.random_meter_names = copy.copy(self.meter_names) + i = randrange(0, len(self.random_meter_names)) + self.meter_config[METER] = self.random_meter_names[i] + del self.random_meter_names[i] + + factory = MeterFactory(self.util, self.meter_config, self.data_source) + return factory.create_meter() + + def set_volume(self, volume): + """ Set volume level + + :param volume: new volume level + """ + if self.USE_VOLUME: + self.current_volume = volume + + def start(self): + """ Start data source and meter animation. """ + + self.data_source.start_data_source() + self.meter = self.get_meter() + self.meter.set_volume(self.current_volume) + self.meter.start() + + def stop(self): + """ Stop data source and meter animation. """ + + self.data_source.stop_data_source() + self.meter.stop() + + def refresh(self): + """ Refresh meter. Used to update random meter. """ + + if self.random_meter and self.seconds == self.random_meter_interval: + self.seconds = 0 + self.stop() + time.sleep(0.2) # let threads stop + self.start() + self.seconds += 1 + pass diff --git a/stations/de/contemporary/10.png b/stations/de/contemporary/10.png index 9c199605..a9d56885 100644 Binary files a/stations/de/contemporary/10.png and b/stations/de/contemporary/10.png differ diff --git a/stations/de/contemporary/11.png b/stations/de/contemporary/11.png index e6972ee2..60b72a5c 100644 Binary files a/stations/de/contemporary/11.png and b/stations/de/contemporary/11.png differ diff --git a/stations/de/contemporary/12.png b/stations/de/contemporary/12.png index 4621f3fa..65065e4e 100644 Binary files a/stations/de/contemporary/12.png and b/stations/de/contemporary/12.png differ diff --git a/stations/de/contemporary/13.png b/stations/de/contemporary/13.png index f5532295..8e793360 100644 Binary files a/stations/de/contemporary/13.png and b/stations/de/contemporary/13.png differ diff --git a/stations/de/contemporary/14.png b/stations/de/contemporary/14.png index a9d56885..570afa79 100644 Binary files a/stations/de/contemporary/14.png and b/stations/de/contemporary/14.png differ diff --git a/stations/de/contemporary/15.png b/stations/de/contemporary/15.png index 2beb8aa8..1d2ace12 100644 Binary files a/stations/de/contemporary/15.png and b/stations/de/contemporary/15.png differ diff --git a/stations/de/contemporary/16.png b/stations/de/contemporary/16.png index 60b72a5c..7e8fc2f5 100644 Binary files a/stations/de/contemporary/16.png and b/stations/de/contemporary/16.png differ diff --git a/stations/de/contemporary/17.png b/stations/de/contemporary/17.png deleted file mode 100644 index 5246afa4..00000000 Binary files a/stations/de/contemporary/17.png and /dev/null differ diff --git a/stations/de/contemporary/18.png b/stations/de/contemporary/18.png deleted file mode 100644 index 65065e4e..00000000 Binary files a/stations/de/contemporary/18.png and /dev/null differ diff --git a/stations/de/contemporary/19.png b/stations/de/contemporary/19.png deleted file mode 100644 index 8e793360..00000000 Binary files a/stations/de/contemporary/19.png and /dev/null differ diff --git a/stations/de/contemporary/2.png b/stations/de/contemporary/2.png index b8890270..dd68c54b 100644 Binary files a/stations/de/contemporary/2.png and b/stations/de/contemporary/2.png differ diff --git a/stations/de/contemporary/20.png b/stations/de/contemporary/20.png deleted file mode 100644 index 570afa79..00000000 Binary files a/stations/de/contemporary/20.png and /dev/null differ diff --git a/stations/de/contemporary/21.png b/stations/de/contemporary/21.png deleted file mode 100644 index 1d2ace12..00000000 Binary files a/stations/de/contemporary/21.png and /dev/null differ diff --git a/stations/de/contemporary/22.png b/stations/de/contemporary/22.png deleted file mode 100644 index 7e8fc2f5..00000000 Binary files a/stations/de/contemporary/22.png and /dev/null differ diff --git a/stations/de/contemporary/3.png b/stations/de/contemporary/3.png index dd68c54b..da66ab3d 100644 Binary files a/stations/de/contemporary/3.png and b/stations/de/contemporary/3.png differ diff --git a/stations/de/contemporary/4.png b/stations/de/contemporary/4.png index da66ab3d..c4f1164d 100644 Binary files a/stations/de/contemporary/4.png and b/stations/de/contemporary/4.png differ diff --git a/stations/de/contemporary/5.png b/stations/de/contemporary/5.png index c4f1164d..fb530624 100644 Binary files a/stations/de/contemporary/5.png and b/stations/de/contemporary/5.png differ diff --git a/stations/de/contemporary/6.png b/stations/de/contemporary/6.png index fb530624..45d9d014 100644 Binary files a/stations/de/contemporary/6.png and b/stations/de/contemporary/6.png differ diff --git a/stations/de/contemporary/7.png b/stations/de/contemporary/7.png index 45d9d014..7d7b6a32 100644 Binary files a/stations/de/contemporary/7.png and b/stations/de/contemporary/7.png differ diff --git a/stations/de/contemporary/8.png b/stations/de/contemporary/8.png index 47a07940..9c199605 100644 Binary files a/stations/de/contemporary/8.png and b/stations/de/contemporary/8.png differ diff --git a/stations/de/contemporary/9.png b/stations/de/contemporary/9.png index 7d7b6a32..f5532295 100644 Binary files a/stations/de/contemporary/9.png and b/stations/de/contemporary/9.png differ diff --git a/stations/de/contemporary/contemporary.m3u b/stations/de/contemporary/contemporary.m3u index 243b6e5c..9939f896 100644 --- a/stations/de/contemporary/contemporary.m3u +++ b/stations/de/contemporary/contemporary.m3u @@ -5,65 +5,47 @@ http://stream.laut.fm/houseschuh #We Love House http://we-love-house.fm:8000/stream #2 -#NVS Radio -http://stream.laut.fm/nvsradio -#3 #RAPKINGS http://stream.laut.fm/rapkings -#4 +#3 #Just Hip-Hop http://stream.laut.fm:80/justhiphop -#5 +#4 #Veteranen Radio http://stream.laut.fm/veteranenradio -#6 +#5 #Alte Schule http://stream.laut.fm/alte-schule -#7 +#6 #NRJ. Mastermix http://adwzg3.tdf-cdn.com/8543/nrj_169437.mp3 -#8 -#I Still Love Her -http://stream.laut.fm/istillloveher -#9 +#7 #Rap Muzik http://stream.laut.fm/rapmuzik -#10 +#8 #Coucou http://stream.laut.fm:80/coucou -#11 -#NRJ. Dubstep -http://adwzg3.tdf-cdn.com/8760/nrj_169531.mp3 -#12 -#WRZL. Dance Station -http://stream.laut.fm/wrzlvibe -#13 +#9 #The Best Of Hip-Hop http://stream.laut.fm/thebestofhiphop -#14 +#10 #House Checker http://stream.laut.fm/housechecker -#15 -#Young Blood -http://stream.laut.fm/youngbloodfm -#16 +#11 #Hip-Hop 4life http://stream.laut.fm/hiphop4life -#17 -#hostFM -http://stream.laut.fm:80/hostfm -#18 +#12 #Electric Fabric http://stream.laut.fm/electricfabric -#19 +#13 #Chart Radio http://stream.laut.fm/chartradio -#20 +#14 #Rettet deutschen Hiphop http://stream.laut.fm/rettetdeutschenhiphop -#21 +#15 #RAPzTV Radio http://stream.laut.fm/rapztv -#22 +#16 #GaGa.fm http://stream.gaga.fm diff --git a/stations/de/culture/culture.m3u b/stations/de/culture/culture.m3u index 6ab07553..cca3f94d 100644 --- a/stations/de/culture/culture.m3u +++ b/stations/de/culture/culture.m3u @@ -24,4 +24,4 @@ http://stream.laut.fm/soundtalesproductions http://neu.schul-internetradio.de:8070/radio #8 #RT1. Sport -http://mp3.hitradiort1.c.nmdn.net/rt1occuseag/livestream.mp3 +http://mp3.hitradiort1.c.nmdn.net/hitradiort1hp/livestream.mp3 diff --git a/stations/de/jazz/10.png b/stations/de/jazz/10.png index a06ff5ed..a168e695 100644 Binary files a/stations/de/jazz/10.png and b/stations/de/jazz/10.png differ diff --git a/stations/de/jazz/11.png b/stations/de/jazz/11.png deleted file mode 100644 index 47419f82..00000000 Binary files a/stations/de/jazz/11.png and /dev/null differ diff --git a/stations/de/jazz/12.png b/stations/de/jazz/12.png deleted file mode 100644 index f15710cd..00000000 Binary files a/stations/de/jazz/12.png and /dev/null differ diff --git a/stations/de/jazz/13.png b/stations/de/jazz/13.png deleted file mode 100644 index a168e695..00000000 Binary files a/stations/de/jazz/13.png and /dev/null differ diff --git a/stations/de/jazz/2.png b/stations/de/jazz/2.png index 00b50dd9..9783c980 100644 Binary files a/stations/de/jazz/2.png and b/stations/de/jazz/2.png differ diff --git a/stations/de/jazz/3.png b/stations/de/jazz/3.png index 9783c980..b4b2085b 100644 Binary files a/stations/de/jazz/3.png and b/stations/de/jazz/3.png differ diff --git a/stations/de/jazz/4.png b/stations/de/jazz/4.png index 2cc262ae..659653d5 100644 Binary files a/stations/de/jazz/4.png and b/stations/de/jazz/4.png differ diff --git a/stations/de/jazz/5.png b/stations/de/jazz/5.png index a9d48ae9..32238960 100644 Binary files a/stations/de/jazz/5.png and b/stations/de/jazz/5.png differ diff --git a/stations/de/jazz/6.png b/stations/de/jazz/6.png index b4b2085b..af0403d5 100644 Binary files a/stations/de/jazz/6.png and b/stations/de/jazz/6.png differ diff --git a/stations/de/jazz/7.png b/stations/de/jazz/7.png index 659653d5..a06ff5ed 100644 Binary files a/stations/de/jazz/7.png and b/stations/de/jazz/7.png differ diff --git a/stations/de/jazz/8.png b/stations/de/jazz/8.png index 32238960..47419f82 100644 Binary files a/stations/de/jazz/8.png and b/stations/de/jazz/8.png differ diff --git a/stations/de/jazz/9.png b/stations/de/jazz/9.png index af0403d5..f15710cd 100644 Binary files a/stations/de/jazz/9.png and b/stations/de/jazz/9.png differ diff --git a/stations/de/jazz/jazz.m3u b/stations/de/jazz/jazz.m3u index 3ad25551..23e94443 100644 --- a/stations/de/jazz/jazz.m3u +++ b/stations/de/jazz/jazz.m3u @@ -5,38 +5,29 @@ http://stream3.laut.fm:8080/radiojazz #Jazztime. Nürnberg http://46.232.185.101:8000/radiof #2 -#Jazz Pages -http://stream.laut.fm/jazzpages-fm -#3 #SOUL ALLNIGHTER http://stream.laut.fm/soulallnighter -#4 -#Querbeet -http://stream.laut.fm/querbeet -#5 -#Jazzthing -http://stream.laut.fm/jazzthing -#6 +#3 #Blues in Germany http://stream.laut.fm/blues_in_germany -#7 +#4 #After Hours http://stream.laut.fm/after-hours -#8 +#5 #jRADIO http://stream.laut.fm/jradio -#9 +#6 #Smooth Jazz Buzz http://stream.laut.fm:80/smoothjazzbuzz -#10 +#7 #The Beat Goes On http://stream.laut.fm:80/the-beat-goes-on -#11 +#8 #BLUE NITE http://stream.laut.fm/bluenite -#12 +#9 #Jazz Pearls http://stream.laut.fm/jazzpearls -#13 +#10 #Funk Soul Brother http://stream.laut.fm/funksoulbrother diff --git a/stations/de/pop/10.png b/stations/de/pop/10.png index 7b1cdb43..d31626a2 100644 Binary files a/stations/de/pop/10.png and b/stations/de/pop/10.png differ diff --git a/stations/de/pop/11.png b/stations/de/pop/11.png index d31626a2..e128cc8b 100644 Binary files a/stations/de/pop/11.png and b/stations/de/pop/11.png differ diff --git a/stations/de/pop/12.png b/stations/de/pop/12.png index e128cc8b..a9bbbf51 100644 Binary files a/stations/de/pop/12.png and b/stations/de/pop/12.png differ diff --git a/stations/de/pop/13.png b/stations/de/pop/13.png index a9bbbf51..06bdb61b 100644 Binary files a/stations/de/pop/13.png and b/stations/de/pop/13.png differ diff --git a/stations/de/pop/14.png b/stations/de/pop/14.png index 06bdb61b..0ef6850b 100644 Binary files a/stations/de/pop/14.png and b/stations/de/pop/14.png differ diff --git a/stations/de/pop/15.png b/stations/de/pop/15.png index 0ef6850b..74b24223 100644 Binary files a/stations/de/pop/15.png and b/stations/de/pop/15.png differ diff --git a/stations/de/pop/16.png b/stations/de/pop/16.png index 74b24223..71999477 100644 Binary files a/stations/de/pop/16.png and b/stations/de/pop/16.png differ diff --git a/stations/de/pop/17.png b/stations/de/pop/17.png index 61dc95c7..eb3d35f5 100644 Binary files a/stations/de/pop/17.png and b/stations/de/pop/17.png differ diff --git a/stations/de/pop/18.png b/stations/de/pop/18.png index 71999477..eff4b9a1 100644 Binary files a/stations/de/pop/18.png and b/stations/de/pop/18.png differ diff --git a/stations/de/pop/19.png b/stations/de/pop/19.png index eb3d35f5..0604397f 100644 Binary files a/stations/de/pop/19.png and b/stations/de/pop/19.png differ diff --git a/stations/de/pop/20.png b/stations/de/pop/20.png index eff4b9a1..f4acca41 100644 Binary files a/stations/de/pop/20.png and b/stations/de/pop/20.png differ diff --git a/stations/de/pop/21.png b/stations/de/pop/21.png index 0604397f..38c22ad3 100644 Binary files a/stations/de/pop/21.png and b/stations/de/pop/21.png differ diff --git a/stations/de/pop/22.png b/stations/de/pop/22.png index f4acca41..736eacef 100644 Binary files a/stations/de/pop/22.png and b/stations/de/pop/22.png differ diff --git a/stations/de/pop/23.png b/stations/de/pop/23.png index 38c22ad3..4630898e 100644 Binary files a/stations/de/pop/23.png and b/stations/de/pop/23.png differ diff --git a/stations/de/pop/24.png b/stations/de/pop/24.png deleted file mode 100644 index 05b42959..00000000 Binary files a/stations/de/pop/24.png and /dev/null differ diff --git a/stations/de/pop/25.png b/stations/de/pop/25.png deleted file mode 100644 index 736eacef..00000000 Binary files a/stations/de/pop/25.png and /dev/null differ diff --git a/stations/de/pop/26.png b/stations/de/pop/26.png deleted file mode 100644 index 4630898e..00000000 Binary files a/stations/de/pop/26.png and /dev/null differ diff --git a/stations/de/pop/9.png b/stations/de/pop/9.png index acb9d9a7..7b1cdb43 100644 Binary files a/stations/de/pop/9.png and b/stations/de/pop/9.png differ diff --git a/stations/de/pop/pop.m3u b/stations/de/pop/pop.m3u index 8785ce66..950a4730 100644 --- a/stations/de/pop/pop.m3u +++ b/stations/de/pop/pop.m3u @@ -3,7 +3,7 @@ http://www.galaxyansbach.de:8000/live #1 #Fritz -http://rbb.ic.llnwd.net/stream/rbb_fritz_mp3_m_b +http://rbb-mp3-fritz-m.akacast.akamaistream.net/7/799/292093/v1/gnl.akacast.akamaistream.net/rbb_mp3_fritz_m #2 #SWR3. Popshop Lyrix http://swr-mp3-m-swr3raka01.akacast.akamaistream.net/7/29/137143/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr3raka01 @@ -26,56 +26,47 @@ http://167.114.246.177:8104/stream #Radio Emscher Lippe http://edge.live.mp3.mdn.newmedia.nacamar.net/ps-radioemscherlippe/livestream.mp3 #9 -#HitradioX -http://stream.laut.fm/hitradiox -#10 #Famous FM http://stream.laut.fm/famousfm -#11 +#10 #BluePoint Radio http://bluepoint-radio.de:9080 -#12 +#11 #CHARIVARI. Party-Hit-Mix http://rs5.stream24.net:8000/stream -#13 +#12 #DiscoFox http://stream.laut.fm/discofox -#14 +#13 #Laser Star Radio http://stream.laut.fm/laserstarradio -#15 +#14 #LAUTSTARK! http://stream.laut.fm/lautstark -#16 +#15 #Happy Days Radio http://stream.laut.fm/happydaysradio -#17 -#Fluffy FM -http://stream.laut.fm/fluffyfm -#18 +#16 #Radio Nin 64 http://stream.laut.fm/radio_nin64 -#19 +#17 #RST Radio http://stream.radiorst.de:8000/rst128k -#20 +#18 #Liederlicht http://stream.laut.fm/liederlicht -#21 +#19 #Netzjournalist http://stream.laut.fm/netzjournalist -#22 +#20 #PlemFM http://stream.laut.fm/plemfm -#23 +#21 #1000 Melodien http://stream.laut.fm/1000melodien -#24 -#Strawberry FM -http://stream.laut.fm/strawberryfm -#25 +#22 #Radio Wedel http://stream.laut.fm/radio_wedel -#26 +#23 #AnZo Radio http://stream.laut.fm/anzoradio diff --git a/stations/de/retro/0.png b/stations/de/retro/0.png index 8849b9d0..b681f4b4 100644 Binary files a/stations/de/retro/0.png and b/stations/de/retro/0.png differ diff --git a/stations/de/retro/1.png b/stations/de/retro/1.png index b681f4b4..583d1e61 100644 Binary files a/stations/de/retro/1.png and b/stations/de/retro/1.png differ diff --git a/stations/de/retro/10.png b/stations/de/retro/10.png index 4dd02ee1..0735f881 100644 Binary files a/stations/de/retro/10.png and b/stations/de/retro/10.png differ diff --git a/stations/de/retro/11.png b/stations/de/retro/11.png index 0735f881..06c8b0f3 100644 Binary files a/stations/de/retro/11.png and b/stations/de/retro/11.png differ diff --git a/stations/de/retro/12.png b/stations/de/retro/12.png index 10a808ef..9310863e 100644 Binary files a/stations/de/retro/12.png and b/stations/de/retro/12.png differ diff --git a/stations/de/retro/13.png b/stations/de/retro/13.png index 06c8b0f3..7a6d74b8 100644 Binary files a/stations/de/retro/13.png and b/stations/de/retro/13.png differ diff --git a/stations/de/retro/14.png b/stations/de/retro/14.png index 9310863e..d611c8f9 100644 Binary files a/stations/de/retro/14.png and b/stations/de/retro/14.png differ diff --git a/stations/de/retro/15.png b/stations/de/retro/15.png index 7a6d74b8..401185a0 100644 Binary files a/stations/de/retro/15.png and b/stations/de/retro/15.png differ diff --git a/stations/de/retro/16.png b/stations/de/retro/16.png index d611c8f9..74109589 100644 Binary files a/stations/de/retro/16.png and b/stations/de/retro/16.png differ diff --git a/stations/de/retro/17.png b/stations/de/retro/17.png index cdd3bd81..3e528ae5 100644 Binary files a/stations/de/retro/17.png and b/stations/de/retro/17.png differ diff --git a/stations/de/retro/18.png b/stations/de/retro/18.png index 401185a0..8c47379d 100644 Binary files a/stations/de/retro/18.png and b/stations/de/retro/18.png differ diff --git a/stations/de/retro/19.png b/stations/de/retro/19.png index 74109589..2f66f8b2 100644 Binary files a/stations/de/retro/19.png and b/stations/de/retro/19.png differ diff --git a/stations/de/retro/2.png b/stations/de/retro/2.png index 583d1e61..466e7a41 100644 Binary files a/stations/de/retro/2.png and b/stations/de/retro/2.png differ diff --git a/stations/de/retro/20.png b/stations/de/retro/20.png index 3e528ae5..78a07931 100644 Binary files a/stations/de/retro/20.png and b/stations/de/retro/20.png differ diff --git a/stations/de/retro/21.png b/stations/de/retro/21.png deleted file mode 100644 index 8c47379d..00000000 Binary files a/stations/de/retro/21.png and /dev/null differ diff --git a/stations/de/retro/22.png b/stations/de/retro/22.png deleted file mode 100644 index 2f66f8b2..00000000 Binary files a/stations/de/retro/22.png and /dev/null differ diff --git a/stations/de/retro/23.png b/stations/de/retro/23.png deleted file mode 100644 index 78a07931..00000000 Binary files a/stations/de/retro/23.png and /dev/null differ diff --git a/stations/de/retro/3.png b/stations/de/retro/3.png index 466e7a41..f0f556b5 100644 Binary files a/stations/de/retro/3.png and b/stations/de/retro/3.png differ diff --git a/stations/de/retro/4.png b/stations/de/retro/4.png index f0f556b5..6f758b92 100644 Binary files a/stations/de/retro/4.png and b/stations/de/retro/4.png differ diff --git a/stations/de/retro/5.png b/stations/de/retro/5.png index 6f758b92..c9c62ee9 100644 Binary files a/stations/de/retro/5.png and b/stations/de/retro/5.png differ diff --git a/stations/de/retro/6.png b/stations/de/retro/6.png index c9c62ee9..c977570c 100644 Binary files a/stations/de/retro/6.png and b/stations/de/retro/6.png differ diff --git a/stations/de/retro/7.png b/stations/de/retro/7.png index c977570c..fa122b4d 100644 Binary files a/stations/de/retro/7.png and b/stations/de/retro/7.png differ diff --git a/stations/de/retro/8.png b/stations/de/retro/8.png index fa122b4d..37809798 100644 Binary files a/stations/de/retro/8.png and b/stations/de/retro/8.png differ diff --git a/stations/de/retro/9.png b/stations/de/retro/9.png index 37809798..4dd02ee1 100644 Binary files a/stations/de/retro/9.png and b/stations/de/retro/9.png differ diff --git a/stations/de/retro/retro.m3u b/stations/de/retro/retro.m3u index 7beee58d..380b6bab 100644 --- a/stations/de/retro/retro.m3u +++ b/stations/de/retro/retro.m3u @@ -1,72 +1,63 @@ #0 -#One Wave Radio -http://stream.laut.fm/one-wave-radio -#1 #Just 70s http://stream.laut.fm/just70s -#2 +#1 #Just 80s http://stream.laut.fm/just80s -#3 +#2 #Just 90s http://stream.laut.fm/just90s -#4 +#3 #Ostwelle http://stream.laut.fm/ostwelle -#5 +#4 #Oldies http://stream.laut.fm/oldoldies -#6 +#5 #Best of 80s http://stream.laut.fm/best_of_80s -#7 +#6 #Radio Nordsee http://stream2.radio-northsea.de:7000/;?1424858157332.mp3 -#8 +#7 #Planet 80s http://stream.laut.fm/planet80s -#9 +#8 #ANTENNE THÜRINGEN - 80er http://xapp2023227392c40000.f.l.i.lb.core-cdn.net/40000mb/live/app2023227392/w2075033606/live_de_128.mp3 -#10 +#9 #FFH. Die 80er http://streams.ffh.de/ffhchannels/mp3/hq80er.mp3 -#11 +#10 #FFH. Die 90er http://mp3.ffh.de/ffhchannels/hq90er.mp3 -#12 -#Surf -http://stream.laut.fm/surf -#13 +#11 #Sunday Music http://stream.laut.fm:80/sundaymusic -#14 +#12 #Zeiten http://stream.laut.fm:80/zeiten -#15 +#13 #Michas Schlagerbox http://stream.laut.fm:80/michas-schlagerbox -#16 +#14 #Veedelsradio http://stream.laut.fm/veedelsradio -#17 -#Oldies -http://stream3.laut.fm:8080/oldies -#18 +#15 #RTL. 80s http://stream.104.6rtl.com/rtl-80er/mp3-128/radioDE/ -#19 +#16 #Jukebox-58 http://stream.laut.fm/jukebox-58 -#20 +#17 #Schwany 6. Oldie Radio http://167.114.246.177:8114/stream -#21 +#18 #Wunschradio. 90er http://mp3.wunschradio.de/wunschradio-90er.mp3 -#22 +#19 #Club85 http://stream.laut.fm/club85 -#23 +#20 #Notenkiste http://stream.laut.fm/notenkiste diff --git a/stations/de/rock/10.png b/stations/de/rock/10.png index 2dcbfaaf..e978c056 100644 Binary files a/stations/de/rock/10.png and b/stations/de/rock/10.png differ diff --git a/stations/de/rock/11.png b/stations/de/rock/11.png index 60686c03..139fdd68 100644 Binary files a/stations/de/rock/11.png and b/stations/de/rock/11.png differ diff --git a/stations/de/rock/12.png b/stations/de/rock/12.png index e978c056..ea7a85fc 100644 Binary files a/stations/de/rock/12.png and b/stations/de/rock/12.png differ diff --git a/stations/de/rock/13.png b/stations/de/rock/13.png index 139fdd68..5fef8887 100644 Binary files a/stations/de/rock/13.png and b/stations/de/rock/13.png differ diff --git a/stations/de/rock/14.png b/stations/de/rock/14.png index ea7a85fc..de2195c5 100644 Binary files a/stations/de/rock/14.png and b/stations/de/rock/14.png differ diff --git a/stations/de/rock/15.png b/stations/de/rock/15.png index 5fef8887..b47dc580 100644 Binary files a/stations/de/rock/15.png and b/stations/de/rock/15.png differ diff --git a/stations/de/rock/16.png b/stations/de/rock/16.png index de2195c5..116b4a3a 100644 Binary files a/stations/de/rock/16.png and b/stations/de/rock/16.png differ diff --git a/stations/de/rock/17.png b/stations/de/rock/17.png index b47dc580..7d19fba9 100644 Binary files a/stations/de/rock/17.png and b/stations/de/rock/17.png differ diff --git a/stations/de/rock/18.png b/stations/de/rock/18.png index 116b4a3a..3897d9c2 100644 Binary files a/stations/de/rock/18.png and b/stations/de/rock/18.png differ diff --git a/stations/de/rock/19.png b/stations/de/rock/19.png deleted file mode 100644 index 1773d6ac..00000000 Binary files a/stations/de/rock/19.png and /dev/null differ diff --git a/stations/de/rock/20.png b/stations/de/rock/20.png deleted file mode 100644 index 7d19fba9..00000000 Binary files a/stations/de/rock/20.png and /dev/null differ diff --git a/stations/de/rock/21.png b/stations/de/rock/21.png deleted file mode 100644 index 3897d9c2..00000000 Binary files a/stations/de/rock/21.png and /dev/null differ diff --git a/stations/de/rock/7.png b/stations/de/rock/7.png index 127a0953..f15de9ab 100644 Binary files a/stations/de/rock/7.png and b/stations/de/rock/7.png differ diff --git a/stations/de/rock/8.png b/stations/de/rock/8.png index f15de9ab..f8497970 100644 Binary files a/stations/de/rock/8.png and b/stations/de/rock/8.png differ diff --git a/stations/de/rock/9.png b/stations/de/rock/9.png index f8497970..2dcbfaaf 100644 Binary files a/stations/de/rock/9.png and b/stations/de/rock/9.png differ diff --git a/stations/de/rock/rock.m3u b/stations/de/rock/rock.m3u index 6545a2a4..e39a039d 100644 --- a/stations/de/rock/rock.m3u +++ b/stations/de/rock/rock.m3u @@ -20,47 +20,38 @@ http://stream.laut.fm/covenant #Street Fighter http://stream.laut.fm/streetfighter #7 -#K-rock -http://stream.laut.fm/k-rock -#8 #NRJ. Rock http://adwzg3.tdf-cdn.com/8529/nrj_177960.mp3 -#9 +#8 #RPR1. Rock http://217.151.151.90:80/stream3 -#10 +#9 #Amorsaal http://stream.laut.fm:80/amorsaal -#11 -#Rockin -http://stream.laut.fm/rockin -#12 +#10 #MR Radio http://stream.laut.fm/mr-radio -#13 +#11 #C.C.RIDER http://stream.laut.fm/ccradio -#14 +#12 #Rock Am Ring Blog http://stream.laut.fm/rockamringblogradio -#15 +#13 #MDR. JUMP Rock Channel http://c22033-l.i.core.cdn.streamfarm.net/22033mdr/live/3087mdr_jump/ch_rock_128.mp3 -#16 +#14 #Dark Rock Radio http://stream.laut.fm/darkrockradio -#17 +#15 #Ost Metal http://stream.laut.fm/ostmetal -#18 +#16 #Rock'n'Radio http://stream.laut.fm/rock_n_radio -#19 -#NRJ. Metal -http://adwzg3.tdf-cdn.com/8604/nrj_170143.mp3 -#20 +#17 #Rockin'C http://stream.laut.fm/rockin_c -#21 +#18 #La Rocks Radio http://listen.larocksradio.de/larocksradio.mp3 diff --git a/stations/en_us/classical/classical.m3u b/stations/en_us/classical/classical.m3u index 013c2a2b..e1f45802 100644 --- a/stations/en_us/classical/classical.m3u +++ b/stations/en_us/classical/classical.m3u @@ -1,6 +1,6 @@ #0 #WABE -http://50.31.192.102:80/wabe +http://pba-ice.streamguys.org/wabe.mp3 #1 #KMFA. Classically Austin http://pubint.ic.llnwd.net/stream/pubint_kmfa diff --git a/stations/en_us/contemporary/7.png b/stations/en_us/contemporary/7.png index 42b529f4..c77a614b 100644 Binary files a/stations/en_us/contemporary/7.png and b/stations/en_us/contemporary/7.png differ diff --git a/stations/en_us/contemporary/8.png b/stations/en_us/contemporary/8.png deleted file mode 100644 index c77a614b..00000000 Binary files a/stations/en_us/contemporary/8.png and /dev/null differ diff --git a/stations/en_us/contemporary/contemporary.m3u b/stations/en_us/contemporary/contemporary.m3u index 38453e22..98eb8568 100644 --- a/stations/en_us/contemporary/contemporary.m3u +++ b/stations/en_us/contemporary/contemporary.m3u @@ -1,6 +1,6 @@ #0 #dublab -http://204.93.252.107/de.mp3 +http://16093.live.streamtheworld.com/SAM04AAC221_SC #1 #WCBN. University of Michigan http://floyd.wcbn.org:8000/wcbn-hi.mp3 @@ -20,8 +20,5 @@ http://ghost.wavestreamer.com:2202/stream/1/ #BDAY http://live.str3am.com:2340/live #7 -#WCCG. The Hip-Hop and R&B -http://67.159.5.57:9998/;/; -#8 #KAFM. Community Radio http://peridot.streamguys.com:5280/kafm diff --git a/stations/en_us/culture/10.png b/stations/en_us/culture/10.png index 9a14633b..9f8e19e6 100644 Binary files a/stations/en_us/culture/10.png and b/stations/en_us/culture/10.png differ diff --git a/stations/en_us/culture/11.png b/stations/en_us/culture/11.png deleted file mode 100644 index 9f8e19e6..00000000 Binary files a/stations/en_us/culture/11.png and /dev/null differ diff --git a/stations/en_us/culture/6.png b/stations/en_us/culture/6.png index a633d684..7b66ce40 100644 Binary files a/stations/en_us/culture/6.png and b/stations/en_us/culture/6.png differ diff --git a/stations/en_us/culture/7.png b/stations/en_us/culture/7.png index 7b66ce40..57d51d7b 100644 Binary files a/stations/en_us/culture/7.png and b/stations/en_us/culture/7.png differ diff --git a/stations/en_us/culture/8.png b/stations/en_us/culture/8.png index 57d51d7b..ef35a12d 100644 Binary files a/stations/en_us/culture/8.png and b/stations/en_us/culture/8.png differ diff --git a/stations/en_us/culture/9.png b/stations/en_us/culture/9.png index ef35a12d..9a14633b 100644 Binary files a/stations/en_us/culture/9.png and b/stations/en_us/culture/9.png differ diff --git a/stations/en_us/culture/culture.m3u b/stations/en_us/culture/culture.m3u index de87bb9f..0e588bc5 100644 --- a/stations/en_us/culture/culture.m3u +++ b/stations/en_us/culture/culture.m3u @@ -17,20 +17,17 @@ http://mediaserver3.afa.net:8000/talk.mp3 #KQNA. Arizona News Talk Sports http://stream.affordablestreaming.com:8000/KQNA.mp3 #6 -#Inception Radio Network -http://streaming.streamonomy.com/inceptionradionetwork -#7 #KABZ. The Buzz http://rs1.radiostreamer.com:8130/; -#8 +#7 #WTIG. ESPN http://50.22.253.45:8000/wtig-am -#9 +#8 #WNST. Baltimore Sports http://ice6.securenetsystems.net/WNST -#10 +#9 #Old Time Radio Fan http://www.otrfan.com:8000/stream -#11 +#10 #Science360 http://radiostream.science360.org:4200/live diff --git a/stations/en_us/news/10.png b/stations/en_us/news/10.png index b67caf4a..0bb176e8 100644 Binary files a/stations/en_us/news/10.png and b/stations/en_us/news/10.png differ diff --git a/stations/en_us/news/11.png b/stations/en_us/news/11.png index 0bb176e8..35d81209 100644 Binary files a/stations/en_us/news/11.png and b/stations/en_us/news/11.png differ diff --git a/stations/en_us/news/12.png b/stations/en_us/news/12.png index 35d81209..633d730f 100644 Binary files a/stations/en_us/news/12.png and b/stations/en_us/news/12.png differ diff --git a/stations/en_us/news/13.png b/stations/en_us/news/13.png index 633d730f..bef95d2a 100644 Binary files a/stations/en_us/news/13.png and b/stations/en_us/news/13.png differ diff --git a/stations/en_us/news/14.png b/stations/en_us/news/14.png index bef95d2a..1e0bc822 100644 Binary files a/stations/en_us/news/14.png and b/stations/en_us/news/14.png differ diff --git a/stations/en_us/news/15.png b/stations/en_us/news/15.png index 1e0bc822..1e9eb999 100644 Binary files a/stations/en_us/news/15.png and b/stations/en_us/news/15.png differ diff --git a/stations/en_us/news/16.png b/stations/en_us/news/16.png index 1e9eb999..f5207246 100644 Binary files a/stations/en_us/news/16.png and b/stations/en_us/news/16.png differ diff --git a/stations/en_us/news/17.png b/stations/en_us/news/17.png deleted file mode 100644 index f5207246..00000000 Binary files a/stations/en_us/news/17.png and /dev/null differ diff --git a/stations/en_us/news/5.png b/stations/en_us/news/5.png index d179018c..bd08ae3f 100644 Binary files a/stations/en_us/news/5.png and b/stations/en_us/news/5.png differ diff --git a/stations/en_us/news/6.png b/stations/en_us/news/6.png index bd08ae3f..0df2c9c1 100644 Binary files a/stations/en_us/news/6.png and b/stations/en_us/news/6.png differ diff --git a/stations/en_us/news/7.png b/stations/en_us/news/7.png index 0df2c9c1..2c993502 100644 Binary files a/stations/en_us/news/7.png and b/stations/en_us/news/7.png differ diff --git a/stations/en_us/news/8.png b/stations/en_us/news/8.png index 2c993502..d2d79e7f 100644 Binary files a/stations/en_us/news/8.png and b/stations/en_us/news/8.png differ diff --git a/stations/en_us/news/9.png b/stations/en_us/news/9.png index d2d79e7f..b67caf4a 100644 Binary files a/stations/en_us/news/9.png and b/stations/en_us/news/9.png differ diff --git a/stations/en_us/news/copy.png b/stations/en_us/news/copy.png deleted file mode 100644 index d7783ae4..00000000 Binary files a/stations/en_us/news/copy.png and /dev/null differ diff --git a/stations/en_us/news/news.m3u b/stations/en_us/news/news.m3u index afc8fddb..77eff8e3 100644 --- a/stations/en_us/news/news.m3u +++ b/stations/en_us/news/news.m3u @@ -14,41 +14,38 @@ http://pubint.ic.llnwd.net/stream/pubint_wnpr #Colorado Public Radio. News http://66.162.107.142/cpr1_lo #5 -#KNKI - iTalk -http://rfe2-r1.alldigital.net:8000/lmjc2060 -#6 #WFDD. NPR News http://live.str3am.com:2640/wfdd1.mp3 -#7 +#6 #WEEB. Talk Radio http://96.31.90.117:8000/weeb990 -#8 +#7 #WBHF. Community Radio -http://fire.wavestreamer.com:6352/;stream.nsv -#9 +http://fire.wavestreamer.com:6352/; +#8 #WFLN. News Radio -http://nyc04.egihosting.com:19910/;stream.nsv -#10 +http://ice64.securenetsystems.net/WFLN +#9 #Kera News http://kera-ice.streamguys.us:80/keralive -#11 +#10 #KSCO. Santa Cruz http://184.105.148.154:8000/live -#12 +#11 #WILL. Illinois Public Media http://icecast.will.illinois.edu:8000/WILL-mp3-48 -#13 +#12 #NPR News http://nprdmp-live01-mp3.akacast.akamaistream.net/7/998/364916/v1/npr.akacast.akamaistream.net/nprdmp_live01_mp3 -#14 +#13 #KUHF. Houston Public Radio http://pubint.ic.llnwd.net/stream/pubint_kuhfnews_128 -#15 +#14 #KSTX. Texas Public Radio -http://50.56.237.164:80/kstxmp3 -#16 +http://stream1.tpr.org/kstxmp3 +#15 #KAMU. Texas A&M University http://hd2-streams.kamu.tamu.edu:8000/HiBW.mp3 -#17 +#16 #WAMC. Northeast Public Radio http://pubint.ic.llnwd.net/stream/pubint_wamc diff --git a/stations/en_us/pop/10.png b/stations/en_us/pop/10.png index 18e0047a..73599d66 100644 Binary files a/stations/en_us/pop/10.png and b/stations/en_us/pop/10.png differ diff --git a/stations/en_us/pop/11.png b/stations/en_us/pop/11.png index 73599d66..6bce50b3 100644 Binary files a/stations/en_us/pop/11.png and b/stations/en_us/pop/11.png differ diff --git a/stations/en_us/pop/12.png b/stations/en_us/pop/12.png index 6bce50b3..d3f83f32 100644 Binary files a/stations/en_us/pop/12.png and b/stations/en_us/pop/12.png differ diff --git a/stations/en_us/pop/13.png b/stations/en_us/pop/13.png index d3f83f32..a8f57d3d 100644 Binary files a/stations/en_us/pop/13.png and b/stations/en_us/pop/13.png differ diff --git a/stations/en_us/pop/14.png b/stations/en_us/pop/14.png deleted file mode 100644 index a8f57d3d..00000000 Binary files a/stations/en_us/pop/14.png and /dev/null differ diff --git a/stations/en_us/pop/5.png b/stations/en_us/pop/5.png index 87287c5b..1ed0521b 100644 Binary files a/stations/en_us/pop/5.png and b/stations/en_us/pop/5.png differ diff --git a/stations/en_us/pop/6.png b/stations/en_us/pop/6.png index 1ed0521b..4419af21 100644 Binary files a/stations/en_us/pop/6.png and b/stations/en_us/pop/6.png differ diff --git a/stations/en_us/pop/7.png b/stations/en_us/pop/7.png index 4419af21..21e6b4af 100644 Binary files a/stations/en_us/pop/7.png and b/stations/en_us/pop/7.png differ diff --git a/stations/en_us/pop/8.png b/stations/en_us/pop/8.png index 21e6b4af..6b66c39e 100644 Binary files a/stations/en_us/pop/8.png and b/stations/en_us/pop/8.png differ diff --git a/stations/en_us/pop/9.png b/stations/en_us/pop/9.png index 6b66c39e..18e0047a 100644 Binary files a/stations/en_us/pop/9.png and b/stations/en_us/pop/9.png differ diff --git a/stations/en_us/pop/pop.m3u b/stations/en_us/pop/pop.m3u index dc82f77d..a177c964 100644 --- a/stations/en_us/pop/pop.m3u +++ b/stations/en_us/pop/pop.m3u @@ -1,6 +1,6 @@ #0 #KBVA. Variety -http://rfe2-r1.alldigital.net/lmjc213?type=.mp3 +http://r2.radioloyalty.net/lmjc213 #1 #WFIT. Florida Public Radio http://pubint.ic.llnwd.net/stream/pubint_wfit @@ -14,32 +14,29 @@ http://pubint.ic.llnwd.net/stream/pubint_ksut #Roothog Radio http://streaming.streamonomy.com/roothog #5 -#KAVV. Cave -http://208.70.75.109:9000/stream -#6 #Star104. At Work http://sc1.cdn.radiostorm.com/1067_128 -#7 +#6 #WHEI. Heidelberg University -http://141.139.35.23:8000/;stream.nsv/;84670407445923stream.nsv -#8 +http://141.139.35.23:8000/; +#7 #KUAZ. Arizona Public Media http://streaming.azpm.org:80/kuaz128.mp3 -#9 +#8 #KXLE. Ellensburg, WA http://kxleamfm.com:8000/kxlefm.mp3hi -#10 +#9 #KALQ. San Luis Valley -http://rfe2-r1.alldigital.net/lmjc1821 -#11 +http://r2.radioloyalty.net/lmjc1821 +#10 #KATL. Music News Sports http://icy2.abacast.com/katl-katlammp3-64 -#12 +#11 #KUTX. Music http://pubint.ic.llnwd.net/stream/pubint_kut2 -#13 +#12 #KMHS. Pirate Radio -http://kmhsam.coos-bay.k12.or.us:8000/kmhsam -#14 +http://kmhs.coos-bay.k12.or.us:8000/kmhsfm +#13 #WWZY. Fun107.1 http://icy3.abacast.com/press-wwzyfmmp3-64 diff --git a/stations/en_us/retro/10.png b/stations/en_us/retro/10.png deleted file mode 100644 index d988ba7b..00000000 Binary files a/stations/en_us/retro/10.png and /dev/null differ diff --git a/stations/en_us/retro/4.png b/stations/en_us/retro/4.png index 9bc50bbb..f3e57dd4 100644 Binary files a/stations/en_us/retro/4.png and b/stations/en_us/retro/4.png differ diff --git a/stations/en_us/retro/5.png b/stations/en_us/retro/5.png index f3e57dd4..05696007 100644 Binary files a/stations/en_us/retro/5.png and b/stations/en_us/retro/5.png differ diff --git a/stations/en_us/retro/6.png b/stations/en_us/retro/6.png index 05696007..f25669f6 100644 Binary files a/stations/en_us/retro/6.png and b/stations/en_us/retro/6.png differ diff --git a/stations/en_us/retro/7.png b/stations/en_us/retro/7.png index f25669f6..89dd2980 100644 Binary files a/stations/en_us/retro/7.png and b/stations/en_us/retro/7.png differ diff --git a/stations/en_us/retro/8.png b/stations/en_us/retro/8.png index 89dd2980..6cea9335 100644 Binary files a/stations/en_us/retro/8.png and b/stations/en_us/retro/8.png differ diff --git a/stations/en_us/retro/9.png b/stations/en_us/retro/9.png index 6cea9335..d988ba7b 100644 Binary files a/stations/en_us/retro/9.png and b/stations/en_us/retro/9.png differ diff --git a/stations/en_us/retro/retro.m3u b/stations/en_us/retro/retro.m3u index 6ffac146..3fa4e299 100644 --- a/stations/en_us/retro/retro.m3u +++ b/stations/en_us/retro/retro.m3u @@ -11,23 +11,20 @@ http://50.7.70.66:8269/wdjo128k #Star104. Oldies http://sc1.cdn.radiostorm.com/1076_128 #4 -#WJQB. True Oldies -http://www.ophanim.net:7320/;stream.nsv -#5 #WRTZ. True Oldies Channel http://www.ophanim.net:9520/; -#6 +#5 #KJUL-FM. The Sound of Las Vegas http://50.22.253.45:8000/kjul-fm3 -#7 +#6 #KMGR. Classy http://streams.midutahradio.com:8000/kmgr.mp3 -#8 +#7 #RatPackRadio http://streaming.radionomy.com/RatPackRadio -#9 +#8 #Dive Bar Jukebox http://streaming.radionomy.com/Dive-Bar-Jukebox -#10 +#9 #Yimago Radio 3 http://streaming.radionomy.com/Yimago-Radio-3 diff --git a/stations/en_us/rock/0.png b/stations/en_us/rock/0.png index 790c07c4..27ab4b96 100644 Binary files a/stations/en_us/rock/0.png and b/stations/en_us/rock/0.png differ diff --git a/stations/en_us/rock/1.png b/stations/en_us/rock/1.png index 27ab4b96..133e0b43 100644 Binary files a/stations/en_us/rock/1.png and b/stations/en_us/rock/1.png differ diff --git a/stations/en_us/rock/10.png b/stations/en_us/rock/10.png index db79d6dc..9dd8c97a 100644 Binary files a/stations/en_us/rock/10.png and b/stations/en_us/rock/10.png differ diff --git a/stations/en_us/rock/11.png b/stations/en_us/rock/11.png index 9dd8c97a..4c6b4b49 100644 Binary files a/stations/en_us/rock/11.png and b/stations/en_us/rock/11.png differ diff --git a/stations/en_us/rock/12.png b/stations/en_us/rock/12.png index 4c6b4b49..238bd19e 100644 Binary files a/stations/en_us/rock/12.png and b/stations/en_us/rock/12.png differ diff --git a/stations/en_us/rock/13.png b/stations/en_us/rock/13.png deleted file mode 100644 index 238bd19e..00000000 Binary files a/stations/en_us/rock/13.png and /dev/null differ diff --git a/stations/en_us/rock/2.png b/stations/en_us/rock/2.png index 133e0b43..925ecff3 100644 Binary files a/stations/en_us/rock/2.png and b/stations/en_us/rock/2.png differ diff --git a/stations/en_us/rock/3.png b/stations/en_us/rock/3.png index 925ecff3..aba9de47 100644 Binary files a/stations/en_us/rock/3.png and b/stations/en_us/rock/3.png differ diff --git a/stations/en_us/rock/4.png b/stations/en_us/rock/4.png index aba9de47..13f46502 100644 Binary files a/stations/en_us/rock/4.png and b/stations/en_us/rock/4.png differ diff --git a/stations/en_us/rock/5.png b/stations/en_us/rock/5.png index 13f46502..ffa14c18 100644 Binary files a/stations/en_us/rock/5.png and b/stations/en_us/rock/5.png differ diff --git a/stations/en_us/rock/6.png b/stations/en_us/rock/6.png index ffa14c18..1ed0521b 100644 Binary files a/stations/en_us/rock/6.png and b/stations/en_us/rock/6.png differ diff --git a/stations/en_us/rock/7.png b/stations/en_us/rock/7.png index 1ed0521b..b7f03853 100644 Binary files a/stations/en_us/rock/7.png and b/stations/en_us/rock/7.png differ diff --git a/stations/en_us/rock/8.png b/stations/en_us/rock/8.png index b7f03853..495595a9 100644 Binary files a/stations/en_us/rock/8.png and b/stations/en_us/rock/8.png differ diff --git a/stations/en_us/rock/9.png b/stations/en_us/rock/9.png index 495595a9..db79d6dc 100644 Binary files a/stations/en_us/rock/9.png and b/stations/en_us/rock/9.png differ diff --git a/stations/en_us/rock/rock.m3u b/stations/en_us/rock/rock.m3u index e0522fbe..5a140a90 100644 --- a/stations/en_us/rock/rock.m3u +++ b/stations/en_us/rock/rock.m3u @@ -1,42 +1,39 @@ #0 -#KFEG. The Eagle -http://rfe2-r1.alldigital.net/lmjc1606 -#1 #KRNU. STUDIO 201 http://129.93.42.17:8000/listen -#2 +#1 #Classic Rock http://www.wxexradio.com:8088/broadwavehigh.mp3 -#3 +#2 #KALX. Berkeley http://icecast.media.berkeley.edu:8000/kalx-128.mp3 -#4 +#3 #The Bear. Classic Rock http://7269.live.streamtheworld.com:80/KHXSFM_SC -#5 +#4 #KXT. North Texas http://kera-ice.streamguys.us:80/kxtlive128 -#6 +#5 #All Dixie Rock http://199.195.194.140:8192/stream -#7 +#6 #Star104. Classic Rock http://sc1.cdn.radiostorm.com/1074_128 -#8 +#7 #WCAL. California, PA http://184.154.90.186:8002/stream -#9 +#8 #KISM. Classic Rock http://stream.us.gslb.liquidcompass.net/KISMFMMP3 -#10 +#9 #ClassXRadio. The New Breed Of Classic Rock -http://174.36.206.197:8124/;stream.nsv -#11 +http://174.36.206.197:8124/; +#10 #WFPK. Radio Louisville http://lpm.streamguys.tv/lpm128-3 -#12 +#11 #Sunny 107. Soft Rock Station -http://rfe2-r1.alldigital.net/lmjc1600 -#13 +http://ice8.securenetsystems.net/WMRS +#12 #KHOWL. Lone Wolf http://listen.khowl.fm/; diff --git a/stations/fr/children/children.m3u b/stations/fr/children/children.m3u index 7750f471..1077b91e 100644 --- a/stations/fr/children/children.m3u +++ b/stations/fr/children/children.m3u @@ -17,7 +17,7 @@ http://radioouistiti2.ice.infomaniak.ch/radioouistiti-high.mp3 #Générikids http://streaming.radionomy.com/GeneriKids #6 -#Ецоле Notre Dame. Comines +#Еcole Notre Dame. Comines http://streaming.radionomy.com/notredamecomines #7 #BABYMIX diff --git a/stations/fr/jazz/jazz.m3u b/stations/fr/jazz/jazz.m3u index 438e70b3..6ca4cdff 100644 --- a/stations/fr/jazz/jazz.m3u +++ b/stations/fr/jazz/jazz.m3u @@ -18,7 +18,7 @@ http://streaming.radionomy.com/RJM-Jazzy http://listen.radionomy.com/all-smooth-jazz #6 #Nostalgie. Blues -http://adwzg3.tdf-cdn.com/8636/nrj_171485.mp3 +http://185.52.127.131/fr/30639/mp3_128.mp3 #7 #AZUR Jazz http://streaming.radionomy.com/Azur-JAZZ diff --git a/stations/fr/pop/11.png b/stations/fr/pop/11.png index 649a20b0..90a3b53d 100644 Binary files a/stations/fr/pop/11.png and b/stations/fr/pop/11.png differ diff --git a/stations/fr/pop/12.png b/stations/fr/pop/12.png index 90a3b53d..181ed2bf 100644 Binary files a/stations/fr/pop/12.png and b/stations/fr/pop/12.png differ diff --git a/stations/fr/pop/13.png b/stations/fr/pop/13.png index 181ed2bf..775be296 100644 Binary files a/stations/fr/pop/13.png and b/stations/fr/pop/13.png differ diff --git a/stations/fr/pop/14.png b/stations/fr/pop/14.png index 775be296..e0a251c5 100644 Binary files a/stations/fr/pop/14.png and b/stations/fr/pop/14.png differ diff --git a/stations/fr/pop/15.png b/stations/fr/pop/15.png index e0a251c5..d4e4d733 100644 Binary files a/stations/fr/pop/15.png and b/stations/fr/pop/15.png differ diff --git a/stations/fr/pop/16.png b/stations/fr/pop/16.png index d4e4d733..86dbfd73 100644 Binary files a/stations/fr/pop/16.png and b/stations/fr/pop/16.png differ diff --git a/stations/fr/pop/17.png b/stations/fr/pop/17.png deleted file mode 100644 index 86dbfd73..00000000 Binary files a/stations/fr/pop/17.png and /dev/null differ diff --git a/stations/fr/pop/pop.m3u b/stations/fr/pop/pop.m3u index a9fc2ebc..ec72ea5b 100644 --- a/stations/fr/pop/pop.m3u +++ b/stations/fr/pop/pop.m3u @@ -24,7 +24,7 @@ http://lorfm.ice.infomaniak.ch/lorfm-128.mp3 http://hotmixradio-frenchy.ice.infomaniak.ch/hotmixradio-frenchy-128.mp3 #8 #Chérie FM. @Work -http://adwzg3.tdf-cdn.com/8571/nrj_176201.mp3 +http://185.52.127.159/fr/30215/aac_64.mp3 #9 #Lyon 1ére http://ice5.infomaniak.ch:80/lyon1ere-high.mp3 @@ -32,23 +32,20 @@ http://ice5.infomaniak.ch:80/lyon1ere-high.mp3 #Perrine FM http://ice5.infomaniak.ch:80/perrinefm-high.mp3 #11 -#NRJ. French Hits -http://adwzg3.tdf-cdn.com/8520/nrj_170116.mp3 -#12 #Air Pop Music http://streaming.radionomy.com/AIR-POP-MUSIC -#13 +#12 #Radio Fever http://zenodore.streaming.radiopytagor.net:8000/pytagor-128.mp3 -#14 +#13 #Ellipse Radio http://streaming.radionomy.com/ellipse-radio -#15 +#14 #Planete Elea Radio http://streaming.radionomy.com/Planete-elea-radio -#16 +#15 #Flon-Flon Musette http://streaming.radionomy.com/FlonFlonMusette -#17 +#16 #MFM. Francophonie http://mfm-wr10.ice.infomaniak.ch/mfm-wr10.mp3 diff --git a/stations/fr/retro/retro.m3u b/stations/fr/retro/retro.m3u index 61d58216..47c1a9b8 100644 --- a/stations/fr/retro/retro.m3u +++ b/stations/fr/retro/retro.m3u @@ -36,10 +36,10 @@ http://streaming.radionomy.com/Surf-Radio-80 http://streaming.radionomy.com/La-Radio-de-Vos-Plus-Belles-Annees #12 #Nostalgie. Best of 60's -http://adwzg3.tdf-cdn.com/8565/nrj_167896.mp3 +http://185.52.127.131/fr/30615/mp3_128.mp3 #13 #Nostalgie. Best of 70's -http://adwzg3.tdf-cdn.com/8566/nrj_179861.mp3 +http://185.52.127.131/fr/30613/mp3_128.mp3 #14 #Nostalgie. Best of 80's -http://adwzg3.tdf-cdn.com/8560/nrj_169022.mp3 +http://185.52.127.159/fr/30605/mp3_128.mp3 diff --git a/stations/ru/children/10.png b/stations/ru/children/10.png new file mode 100644 index 00000000..a76be26f Binary files /dev/null and b/stations/ru/children/10.png differ diff --git a/stations/ru/children/11.png b/stations/ru/children/11.png new file mode 100644 index 00000000..a9457093 Binary files /dev/null and b/stations/ru/children/11.png differ diff --git a/stations/ru/children/12.png b/stations/ru/children/12.png new file mode 100644 index 00000000..d80bc1c9 Binary files /dev/null and b/stations/ru/children/12.png differ diff --git a/stations/ru/children/13.png b/stations/ru/children/13.png new file mode 100644 index 00000000..44d18f78 Binary files /dev/null and b/stations/ru/children/13.png differ diff --git a/stations/ru/children/14.png b/stations/ru/children/14.png new file mode 100644 index 00000000..306724ba Binary files /dev/null and b/stations/ru/children/14.png differ diff --git a/stations/ru/children/15.png b/stations/ru/children/15.png new file mode 100644 index 00000000..502803fc Binary files /dev/null and b/stations/ru/children/15.png differ diff --git a/stations/ru/children/2.png b/stations/ru/children/2.png index 50298b0c..a64b8cdf 100644 Binary files a/stations/ru/children/2.png and b/stations/ru/children/2.png differ diff --git a/stations/ru/children/3.png b/stations/ru/children/3.png index a64b8cdf..0ea76ed8 100644 Binary files a/stations/ru/children/3.png and b/stations/ru/children/3.png differ diff --git a/stations/ru/children/4.png b/stations/ru/children/4.png index 0ea76ed8..600d4eaf 100644 Binary files a/stations/ru/children/4.png and b/stations/ru/children/4.png differ diff --git a/stations/ru/children/5.png b/stations/ru/children/5.png index 600d4eaf..9fb1f69c 100644 Binary files a/stations/ru/children/5.png and b/stations/ru/children/5.png differ diff --git a/stations/ru/children/6.png b/stations/ru/children/6.png new file mode 100644 index 00000000..bdc27ce3 Binary files /dev/null and b/stations/ru/children/6.png differ diff --git a/stations/ru/children/7.png b/stations/ru/children/7.png new file mode 100644 index 00000000..20224cbd Binary files /dev/null and b/stations/ru/children/7.png differ diff --git a/stations/ru/children/8.png b/stations/ru/children/8.png new file mode 100644 index 00000000..a69ed2f3 Binary files /dev/null and b/stations/ru/children/8.png differ diff --git a/stations/ru/children/9.png b/stations/ru/children/9.png new file mode 100644 index 00000000..e35e11f9 Binary files /dev/null and b/stations/ru/children/9.png differ diff --git a/stations/ru/children/children.m3u b/stations/ru/children/children.m3u index 7e8fa0a3..038dcd84 100644 --- a/stations/ru/children/children.m3u +++ b/stations/ru/children/children.m3u @@ -1,18 +1,49 @@ #0 -#Radio Kids -http://78.129.159.25:8100/;stream.nsv +#Radio Kids FM +http://listen1.myradio24.com:9000/3817 #1 #Детский мир (eTVnet) http://icecast.etvnet.com:8000/kids128.mp3 #2 -#Детские песенки -http://radio.sunradio.ru:80/children64 -#3 #Детское радио http://ic2.101.ru:8000/v14_1 -#4 +#3 #Детское радио (Старое радио) http://195.91.237.50:8000/detskoe128 -#5 +#4 #Kids Hits -http://92.255.237.31:8000/kidshits_mp3_320 +http://92.255.229.74:8888/; +#5 +#Детский Хит +http://imgradio.pro/DetiHit +#6 +#Радио Disney +http://disney2.streamr.ru:8060/disney +#7 +#Детские песни +http://ic2.101.ru:8000/c3_3 +#8 +#Детские сказки +http://ic2.101.ru:8000/c17_27 +#9 +#Detskie skazki +http://relay.myradio.ua:8000/fairytales128.mp3 +#10 +#ANNA.FM +http://78.47.73.52:8000/live +#11 +#Детское радио Сириус +http://stream0.radiostyle.ru:8000/sirius +#12 +#ТМ-Радио +http://109.167.206.173:24000/live +#13 +#Радио Гамаюн +http://gamaun.online:8020/radio +#14 +#Поляна сказок +http://93.100.67.115:43000/poleskazok +#15 +#Детский радиоканал +http://icecast.russkoeradio.cdnvideo.ru/st28.mp3 + diff --git a/stations/ru/classical/2.png b/stations/ru/classical/2.png new file mode 100644 index 00000000..ea457760 Binary files /dev/null and b/stations/ru/classical/2.png differ diff --git a/stations/ru/classical/3.png b/stations/ru/classical/3.png new file mode 100644 index 00000000..ed9d489d Binary files /dev/null and b/stations/ru/classical/3.png differ diff --git a/stations/ru/classical/4.png b/stations/ru/classical/4.png new file mode 100644 index 00000000..5027eabb Binary files /dev/null and b/stations/ru/classical/4.png differ diff --git a/stations/ru/classical/5.png b/stations/ru/classical/5.png new file mode 100644 index 00000000..8490d2e6 Binary files /dev/null and b/stations/ru/classical/5.png differ diff --git a/stations/ru/classical/classical.m3u b/stations/ru/classical/classical.m3u index e3999e27..d07f681b 100644 --- a/stations/ru/classical/classical.m3u +++ b/stations/ru/classical/classical.m3u @@ -3,4 +3,16 @@ http://icecast.orpheus.cdnvideo.ru/orpheus_128 #1 #Опера (101.ru) -http://msk1.101.ru:8000/m15_4 +http://ic3.101.ru:8000/m15_4 +#2 +#Радио Классик +http://jfm1.hostingradio.ru:14536/rcstream.mp3 +#3 +#Classic Gold +http://jfm1.hostingradio.ru:14536/gcstream.mp3 +#4 +#Classic Vocals +http://jfm1.hostingradio.ru:14536/cvstream.mp3 +#5 +#Essential Classic +http://jfm1.hostingradio.ru:14536/ecstream.mp3 \ No newline at end of file diff --git a/stations/ru/contemporary/contemporary.m3u b/stations/ru/contemporary/contemporary.m3u index 9715a5a2..b2943067 100644 --- a/stations/ru/contemporary/contemporary.m3u +++ b/stations/ru/contemporary/contemporary.m3u @@ -18,10 +18,10 @@ http://variant.fm:8000/PPR-192 http://s04.radio-tochka.com:4420/radio #6 #DFM Club -http://st01.fmtuner.ru/ +http://cdn.rr.ru/st01.mp3 #7 #Keks FM -http://emgspb.hostingradio.ru/keksfmspb128.mp3 +http://mp3128.keks.emgsound.ru/ #8 #Нестандарт http://listen.radionestandart.ru:8013/nestandart diff --git a/stations/ru/culture/10.png b/stations/ru/culture/10.png new file mode 100644 index 00000000..a88ef672 Binary files /dev/null and b/stations/ru/culture/10.png differ diff --git a/stations/ru/culture/11.png b/stations/ru/culture/11.png new file mode 100644 index 00000000..ca9d159b Binary files /dev/null and b/stations/ru/culture/11.png differ diff --git a/stations/ru/culture/12.png b/stations/ru/culture/12.png new file mode 100644 index 00000000..d3b6de6f Binary files /dev/null and b/stations/ru/culture/12.png differ diff --git a/stations/ru/culture/13.png b/stations/ru/culture/13.png new file mode 100644 index 00000000..2326ef25 Binary files /dev/null and b/stations/ru/culture/13.png differ diff --git a/stations/ru/culture/14.png b/stations/ru/culture/14.png new file mode 100644 index 00000000..9fe8cb39 Binary files /dev/null and b/stations/ru/culture/14.png differ diff --git a/stations/ru/culture/9.png b/stations/ru/culture/9.png new file mode 100644 index 00000000..a26a2f2d Binary files /dev/null and b/stations/ru/culture/9.png differ diff --git a/stations/ru/culture/culture.m3u b/stations/ru/culture/culture.m3u index f76428ec..69f4c279 100644 --- a/stations/ru/culture/culture.m3u +++ b/stations/ru/culture/culture.m3u @@ -3,7 +3,7 @@ http://server.audiopedia.su:8000/ices128 #1 #Радио Культура -http://livestream.rfn.ru:8080/kulturafm/mp3_192kbps +http://icecast.vgtrk.cdnvideo.ru/kulturafm_mp3_192kbps #2 #Домашнее радио http://radio.ukraudio.com:8000/domashnee-radio @@ -15,13 +15,31 @@ http://mds-station.com:8000/mds http://89.208.99.16:8088/zvezda_128 #5 #Юмор FM -http://62.213.41.244:8003/;stream.nsv +http://ic2.101.ru:8000/v5_1 #6 #Анекдот (101.ru) http://m.101.ru/m101.php?uid=20 #7 #Классика жанра (101.ru) -http://eu7.101.ru:8000/c4_1 +http://ic2.101.ru:8000/c4_1 #8 #Юмор Non Stop (101.ru) -http://eu7.101.ru:8000/c2_4 +http://ic2.101.ru:8000/c2_4 +#9 +#Comedy Radio +http://ic2.101.ru:8000/v11_1 +#10 +#Радиокнига +http://bookradio.hostingradio.ru:8069/fm +#11 +#Радио Фантастики +http://fantasyradioru.no-ip.biz:8000/;stream.nsv +#12 +#Литературное радио +http://79.137.234.183:8000/;stream.mp3 +#13 +#Звукокнига +http://94.181.45.104:8005/;stream.nsv/ +#14 +#Поэзия на Classic +http://jfm1.hostingradio.ru:14536/pcstream.mp3 diff --git a/stations/ru/jazz/12.png b/stations/ru/jazz/12.png new file mode 100644 index 00000000..80007a92 Binary files /dev/null and b/stations/ru/jazz/12.png differ diff --git a/stations/ru/jazz/jazz.m3u b/stations/ru/jazz/jazz.m3u index 16f6e278..16fc0d0a 100644 --- a/stations/ru/jazz/jazz.m3u +++ b/stations/ru/jazz/jazz.m3u @@ -34,4 +34,6 @@ http://radio.1jazz.ru:8290/radio #11 #Vibraphone Jazz http://radio.1jazz.ru:8300/radio - +#12 +#Эрмитаж +http://91.190.127.185:8000/live_test \ No newline at end of file diff --git a/stations/ru/news/14.png b/stations/ru/news/14.png new file mode 100644 index 00000000..09862a0e Binary files /dev/null and b/stations/ru/news/14.png differ diff --git a/stations/ru/news/15.png b/stations/ru/news/15.png new file mode 100644 index 00000000..9dc465a7 Binary files /dev/null and b/stations/ru/news/15.png differ diff --git a/stations/ru/news/news.m3u b/stations/ru/news/news.m3u index 4b8c6c08..438b9787 100644 --- a/stations/ru/news/news.m3u +++ b/stations/ru/news/news.m3u @@ -6,25 +6,25 @@ http://81.19.85.197/echo.mp3 http://radiosilver.corbina.net:8000/silver128a.mp3 #2 #Маяк -http://livestream.rfn.ru:8080/mayakfm/mp3_192kbps +http://icecast.vgtrk.cdnvideo.ru/mayakfm_mp3_192kbps #3 #Бизнес FM http://bfmstream.bfm.ru:8004/fm64 #4 #Радио России -http://livestream.rfn.ru:8080/rrzonam/mp3_192kbps +http://icecast.vgtrk.cdnvideo.ru/rrzonam_mp3_192kbps #5 #Русская служба новостей http://91.229.113.62:8000/rsn.mp3 #6 #Спутник -http://audio2.video.ria.ru/voicerus +http://icecast.rian.cdnvideo.ru/rian.voicerus #7 #Комсомольская правда -http://kpradio.hostingradio.ru:8000/128 +http://russia.radiokp128.mp3.streamr.ru/ #8 #Коммерсантъ -http://81.19.85.196/komm128.mp3 +http://kommersant77.hostingradio.ru:8016/kommersant128.mp3 #9 #Вести http://icecast.vgtrk.cdnvideo.ru/vestifm_mp3_192kbps @@ -40,3 +40,9 @@ http://sportfm.hostingradio.ru:8050/sportfm128.mp3 #13 #Команда http://komandaserver.streamr.ru:8051/komanda128 +#14 +#i am radio (Петербург) +http://s2.radioboss.fm:8516/stream +#15 +#Радио Свобода +http://rfe-channel-04.akacast.akamaistream.net/7/885/229654/v1/ibb.akacast.akamaistream.net/rfe_channel_04.mp3 \ No newline at end of file diff --git a/stations/ru/pop/22.png b/stations/ru/pop/22.png new file mode 100644 index 00000000..020df314 Binary files /dev/null and b/stations/ru/pop/22.png differ diff --git a/stations/ru/pop/23.png b/stations/ru/pop/23.png new file mode 100644 index 00000000..8345583e Binary files /dev/null and b/stations/ru/pop/23.png differ diff --git a/stations/ru/pop/24.png b/stations/ru/pop/24.png new file mode 100644 index 00000000..93ef0a57 Binary files /dev/null and b/stations/ru/pop/24.png differ diff --git a/stations/ru/pop/25.png b/stations/ru/pop/25.png new file mode 100644 index 00000000..769a51e4 Binary files /dev/null and b/stations/ru/pop/25.png differ diff --git a/stations/ru/pop/26.png b/stations/ru/pop/26.png new file mode 100644 index 00000000..2d4c0cbf Binary files /dev/null and b/stations/ru/pop/26.png differ diff --git a/stations/ru/pop/27.png b/stations/ru/pop/27.png new file mode 100644 index 00000000..fbbad908 Binary files /dev/null and b/stations/ru/pop/27.png differ diff --git a/stations/ru/pop/28.png b/stations/ru/pop/28.png new file mode 100644 index 00000000..1f4584fd Binary files /dev/null and b/stations/ru/pop/28.png differ diff --git a/stations/ru/pop/29.png b/stations/ru/pop/29.png new file mode 100644 index 00000000..a6181def Binary files /dev/null and b/stations/ru/pop/29.png differ diff --git a/stations/ru/pop/30.png b/stations/ru/pop/30.png new file mode 100644 index 00000000..41c3d125 Binary files /dev/null and b/stations/ru/pop/30.png differ diff --git a/stations/ru/pop/31.png b/stations/ru/pop/31.png new file mode 100644 index 00000000..69f56a70 Binary files /dev/null and b/stations/ru/pop/31.png differ diff --git a/stations/ru/pop/32.png b/stations/ru/pop/32.png new file mode 100644 index 00000000..4625db2c Binary files /dev/null and b/stations/ru/pop/32.png differ diff --git a/stations/ru/pop/33.png b/stations/ru/pop/33.png new file mode 100644 index 00000000..a2fb04ca Binary files /dev/null and b/stations/ru/pop/33.png differ diff --git a/stations/ru/pop/34.png b/stations/ru/pop/34.png new file mode 100644 index 00000000..d059e2f4 Binary files /dev/null and b/stations/ru/pop/34.png differ diff --git a/stations/ru/pop/35.png b/stations/ru/pop/35.png new file mode 100644 index 00000000..2c1532ba Binary files /dev/null and b/stations/ru/pop/35.png differ diff --git a/stations/ru/pop/36.png b/stations/ru/pop/36.png new file mode 100644 index 00000000..b2271654 Binary files /dev/null and b/stations/ru/pop/36.png differ diff --git a/stations/ru/pop/37.png b/stations/ru/pop/37.png new file mode 100644 index 00000000..72644bdb Binary files /dev/null and b/stations/ru/pop/37.png differ diff --git a/stations/ru/pop/8.png b/stations/ru/pop/8.png index 21dcc819..c96f2292 100644 Binary files a/stations/ru/pop/8.png and b/stations/ru/pop/8.png differ diff --git a/stations/ru/pop/pop.m3u b/stations/ru/pop/pop.m3u index 36e7bd1c..c8241b26 100644 --- a/stations/ru/pop/pop.m3u +++ b/stations/ru/pop/pop.m3u @@ -1,12 +1,12 @@ #0 #Авторадио -http://nbn.101.ru:8000/v3_1 +http://193.232.148.42:8000/v3_1 #1 #Дорожное радио http://variant.fm:8000/DR-192 #2 #Европа Плюс -http://ep2561.streamr.ru:8052/europaplus256.mp3 +http://ep128server.streamr.ru:8030/ep128 #3 #Русский хит (etvnet) http://icecast.etvnet.com:8000/russianhit128.mp3 @@ -24,7 +24,7 @@ http://listen1.myradio24.com:9000/6262 http://80.232.162.149:8000/plus96mp3 #8 #Радио Шансон -http://icecast.chanson.cdnvideo.ru:8000/chanson_128_bu.mp3 +http://chanson.hostingradio.ru:8041/chanson256.mp3 #9 #Русские песни http://listen.rusongs.ru/ru-mp3-128 @@ -36,19 +36,19 @@ http://setmedia.ru:8000/high3 http://rr.babahhcdn.com/RR?/russkoeradio_high.mp3 #12 #Русский хит -http://imgradio.pro/RusHit +http://s9.imgradio.pro/RusHit48 #13 #Золотой век http://setmedia.ru:8000/high #14 #Милицейская волна -http://95.173.156.75:8000/mv128.mp3 +http://radio.mvd.ru:8000/mv128.mp3 #15 #Март -http://listen4.myradio24.com:9000/8212 +http://radio.xn--38-6kcaty2apogw.xn--p1ai:8010/radio #16 #Удачное радио (Хабаровск) -http://mixfmonline.ru:8000/radio1 +http://radio.gubernia.com:8000/radio2 #17 #Дача http://listen.vdfm.ru:8000/dacha @@ -57,10 +57,58 @@ http://listen.vdfm.ru:8000/dacha https://audio.baltika.fm/baltika.mp3 #19 #Radio 7 -http://radio7server.streamr.ru/radio732 +http://server03.hostingradio.ru:8040/radio7256.mp3 #20 #Романтика -http://eu4.101.ru:8000/v4_1 +http://ic3.101.ru:8000/v4_1 #21 #Радио Сибирь http://stream.radiosibir.ru:8090/HQ +#22 +#Радио для двоих +http://icecast.piktv.cdnvideo.ru/rdd128 +#23 +#SKY Радио (Таллинн) +http://stream05.akaver.com/skyradio_hi.mp3 +#24 +#Радио Борнео (Воронеж) +http://live.borneo.ru:8888/128 +#25 +#Mедляк FM +http://online.radiorecord.ru:8102/mdl_128 +#26 +#Радио Ваня +http://radio.gorodkovrov.ru:8000/Vanya.mp3 +#27 +#Super Instrumental +http://aska.ru-hoster.com:8054/supinst256-may2016 +#28 +#Хорошее FM +http://radio.horoshee.fm:8000/mp3 +#29 +#МЕГА РАДИО +http://mega.imgradio.pro/MegaRadio48 +#30 +#Радио ПАССАЖ +http://listen.radiopassazh.ru/mp3-128 +#31 +#Волшебный Шансон +http://listen2.myradio24.com:9000/8144 +#32 +#Радио Мир +http://icecast.mirtv.cdnvideo.ru:8000/radio_mir128 +#33 +#Шансон 24 +http://5.19.168.205:8010/;stream.nsv +#34 +#Радио День +http://air.radioday.fm/ogg +#35 +#Best FM +http://nashe1.hostingradio.ru/best-128.mp3 +#36 +#Отличное радио +http://radio.oneex.ru:9000/8234 +#37 +#Золотой Граммофон +http://icecast.russkoeradio.cdnvideo.ru/st31.mp3 \ No newline at end of file diff --git a/stations/ru/retro/11.png b/stations/ru/retro/11.png new file mode 100644 index 00000000..99350ea1 Binary files /dev/null and b/stations/ru/retro/11.png differ diff --git a/stations/ru/retro/12.png b/stations/ru/retro/12.png new file mode 100644 index 00000000..0e1fbb26 Binary files /dev/null and b/stations/ru/retro/12.png differ diff --git a/stations/ru/retro/13.png b/stations/ru/retro/13.png new file mode 100644 index 00000000..a980e364 Binary files /dev/null and b/stations/ru/retro/13.png differ diff --git a/stations/ru/retro/14.png b/stations/ru/retro/14.png new file mode 100644 index 00000000..d7b729ae Binary files /dev/null and b/stations/ru/retro/14.png differ diff --git a/stations/ru/retro/15.png b/stations/ru/retro/15.png new file mode 100644 index 00000000..d711f5be Binary files /dev/null and b/stations/ru/retro/15.png differ diff --git a/stations/ru/retro/16.png b/stations/ru/retro/16.png new file mode 100644 index 00000000..ecb954e2 Binary files /dev/null and b/stations/ru/retro/16.png differ diff --git a/stations/ru/retro/17.png b/stations/ru/retro/17.png new file mode 100644 index 00000000..eb2b2b9d Binary files /dev/null and b/stations/ru/retro/17.png differ diff --git a/stations/ru/retro/retro.m3u b/stations/ru/retro/retro.m3u index 5817190a..437dd460 100644 --- a/stations/ru/retro/retro.m3u +++ b/stations/ru/retro/retro.m3u @@ -6,7 +6,7 @@ http://retroserver.streamr.ru:8043/retro128 http://stream5.radiostyle.ru:8005/theatre #2 #Радиола (Екатеринбург) -http://radiolaeka.gkvr.ru:8000/radiola_eka_256.mp3 +http://101.ru/m101.php?uid=209 #3 #Подмосковные вечера http://setmedia.ru:8000/high5 @@ -15,7 +15,7 @@ http://setmedia.ru:8000/high5 http://server.audiopedia.su:8000/music128 #5 #Весна -http://vesna.fmtuner.ru/;stream.nsv +http://stream2.n340.com/17_vesna_24 #6 #Ретро Радио(etvnet) http://icecast.etvnet.com:8000/retro128.mp3 @@ -30,4 +30,25 @@ http://icecast.etvnet.com:8000/discoussr128.mp3 http://air.radiorecord.ru:8102/sd90_320 #10 #Maximum. 90е -http://st15.fmtuner.ru/ +http://icecast.radiomaximum.cdnvideo.ru/st15.mp3 +#11 +#Ретро хит +http://relay2.imgradio.pro/RetroHit +#12 +#Душевное радио (Минск) +http://imgradio.pro/DushevnoeRadio48 +#13 +#Мелодия +http://stream128.melodiafm.spb.ru:8000/melodia128 +#14 +#Ретро-Ретро +http://radio.retro-retro.ru/;stream.mp3 +#15 +#Мировое кино +http://icecast.radiomontecarlo.cdnvideo.ru/st09.mp3 +#16 +#Русское кино +http://icecast.russkoeradio.cdnvideo.ru/st19.mp3 +#17 +#Высоцкий +http://icecast.russkoeradio.cdnvideo.ru/st27.mp3 \ No newline at end of file diff --git a/stations/ru/rock/14.png b/stations/ru/rock/14.png new file mode 100644 index 00000000..eec6d4ab Binary files /dev/null and b/stations/ru/rock/14.png differ diff --git a/stations/ru/rock/15.png b/stations/ru/rock/15.png new file mode 100644 index 00000000..9d66f35a Binary files /dev/null and b/stations/ru/rock/15.png differ diff --git a/stations/ru/rock/16.png b/stations/ru/rock/16.png new file mode 100644 index 00000000..3083e311 Binary files /dev/null and b/stations/ru/rock/16.png differ diff --git a/stations/ru/rock/17.png b/stations/ru/rock/17.png new file mode 100644 index 00000000..5320010e Binary files /dev/null and b/stations/ru/rock/17.png differ diff --git a/stations/ru/rock/18.png b/stations/ru/rock/18.png new file mode 100644 index 00000000..cdaca8fa Binary files /dev/null and b/stations/ru/rock/18.png differ diff --git a/stations/ru/rock/19.png b/stations/ru/rock/19.png new file mode 100644 index 00000000..ac6c9b7c Binary files /dev/null and b/stations/ru/rock/19.png differ diff --git a/stations/ru/rock/20.png b/stations/ru/rock/20.png new file mode 100644 index 00000000..6a1df556 Binary files /dev/null and b/stations/ru/rock/20.png differ diff --git a/stations/ru/rock/rock.m3u b/stations/ru/rock/rock.m3u index 992092bd..e54f1d56 100644 --- a/stations/ru/rock/rock.m3u +++ b/stations/ru/rock/rock.m3u @@ -1,9 +1,9 @@ #0 #Наше радио -http://mp3.nashe.ru/nashe-128.mp3 +http://nashe.streamr.ru/nashe-128.mp3 #1 #Rock FM -http://mp3.nashe.ru/rock-128.mp3 +http://nashe1.hostingradio.ru/rock-128.mp3 #2 #Матч http://168.144.82.139:8000/;stream/1 @@ -30,13 +30,34 @@ http://orrp.ru:8000/live_192 http://radio.oldxit.ru:8000/radio #10 #Dipol -http://icecast.sibinformburo.cdnvideo.ru:8000/radio1 +http://icecast.sibinformburo.cdnvideo.ru:8000/dipolfm #11 #Radio Maximum -http://maximum.fmtuner.ru/ +http://icecast.radiomaximum.cdnvideo.ru/maximum.mp3 #12 #Rock Radio http://air.radiorecord.ru:8102/rock_320 #13 #Полигон http://94.23.36.117:5159/stream +#14 +#Новое Чистое Радио +http://live.ncradio.ru/256.mp3 +#15 +#Челябинское Рок Радио +http://46.105.180.202:8082/chelrockradio +#16 +#Русский рок +http://imgradio.pro/RusRock +#17 +#Ultra +http://nashe2.hostingradio.ru/ultra-128.mp3 +#18 +#Radio13 +http://play.radio13.ru/ +#19 +#Rock Hits +http://icecast.radiomaximum.cdnvideo.ru/st25.mp3 +#20 +#Русский рок +http://icecast.russkoeradio.cdnvideo.ru/st37.mp3 \ No newline at end of file diff --git a/ui/button/button.py b/ui/button/button.py index bebb584d..4bda622a 100644 --- a/ui/button/button.py +++ b/ui/button/button.py @@ -101,7 +101,7 @@ def add_image(self, state, bb): self.add_component(c) def add_label(self, state, bb): - """ Add label + """ Add button label :param state: button state :param bb: bounding box @@ -132,6 +132,7 @@ def add_label(self, state, bb): def set_img_coord(self): """ Center image in bounding box """ + c = self.components[1] bb = self.bounding_box img = c.content @@ -185,7 +186,8 @@ def set_selected(self, flag=False): self.set_icon() def set_icon(self): - """ Set icon as button component """ + """ Set icon as button component """ + scaled = getattr(self.state, "scaled", False) enabled = getattr(self.state, "enabled", True) if self.components[1]: @@ -224,6 +226,7 @@ def get_icon(self, selected, scaled, enabled): def set_label(self): """ Set label color depending on 'enabled' flag """ + enabled = getattr(self.state, "enabled", True) if enabled: @@ -279,6 +282,7 @@ def user_event_action(self, event): def press_action(self): """ Press button event handler """ + enabled = getattr(self.state, "enabled", True) if not enabled: return @@ -292,7 +296,8 @@ def press_action(self): self.notify_press_listeners(self.state) def release_action(self): - """ Release button event handler """ + """ Release button event handler """ + enabled = getattr(self.state, "enabled", True) if not enabled: return diff --git a/ui/button/multistatebutton.py b/ui/button/multistatebutton.py index ffdf20ca..54b1dd68 100644 --- a/ui/button/multistatebutton.py +++ b/ui/button/multistatebutton.py @@ -72,6 +72,7 @@ def user_event_action(self, event): def press_action(self): """ Button press event handler """ + self.set_selected(True) self.clicked = True super(MultiStateButton, self).clean_draw_update() @@ -79,6 +80,7 @@ def press_action(self): def release_action(self): """ Button release event handler """ + self.set_selected(False) self.clicked = False self.notify_listeners(self.state) diff --git a/ui/button/togglebutton.py b/ui/button/togglebutton.py index 9ad8bf79..8c6ddeaa 100644 --- a/ui/button/togglebutton.py +++ b/ui/button/togglebutton.py @@ -84,12 +84,14 @@ def keyboard_action(self, event): def press_action(self): """ Button press event handler """ + self.set_selected(True) self.clicked = True self.clean_draw_update() def release_action(self, state): """ Button release event handler """ + self.clicked = False if self.pressed: self.notify_release_listeners(self.state) @@ -100,6 +102,7 @@ def release_action(self, state): def cancel_action(self): """ Cancel previous action """ + if self.pressed: self.set_selected(False) self.pressed = False @@ -120,4 +123,4 @@ def notify_cancel_listeners(self, state): :param state: button state """ for listener in self.cancel_listeners: - listener(state) \ No newline at end of file + listener(state) diff --git a/ui/component.py b/ui/component.py index bc7f6265..0c41dccd 100644 --- a/ui/component.py +++ b/ui/component.py @@ -19,8 +19,7 @@ from util.config import PYGAME_SCREEN class Component(object): - """ Represent the lowest UI component level. - + """ Represent the lowest UI component level. This is the only class which knows how to draw on Pygame Screen. """ @@ -50,12 +49,12 @@ def __init__(self, util, c=None, x=0, y=0, bb=None, fgr=(0, 0, 0), bgr=(0, 0, 0) def clean(self): """ Clean component by filling its bounding box by background color """ + if not self.visible: return self.draw_rect(self.bgr, self.bounding_box) def draw(self): - """ Dispatcher drawing method. - + """ Dispatcher drawing method. Distinguishes between Rectangle and Image components. Doesn't draw invisible component. """ @@ -77,7 +76,7 @@ def draw_rect(self, f, r, t=0): pygame.draw.rect(self.screen, f, r, t) def draw_image(self, c, x, y): - """ Draw Image on on Pygame Screen + """ Draw Image on Pygame Screen :param c: image :param x: coordinate X of the image top-left corner on Pygame Screen @@ -87,12 +86,25 @@ def draw_image(self, c, x, y): if isinstance(c, tuple): comp = c[1] if comp: - self.screen.blit(comp, (x, y)) - + if self.bounding_box: + if isinstance(self.content, tuple): + self.screen.blit(self.content[1], (self.content_x, self.content_y), self.bounding_box) + else: + self.screen.blit(self.content, self.bounding_box) + else: + self.screen.blit(comp, (x, y)) + def update(self): """ Update Pygame Screen """ + if not self.visible: return - pygame.display.update(self.bounding_box) + pygame.display.update(self.bounding_box) + + def update_rectangle(self, r): + """ Update Pygame Screen """ + + if not self.visible: return + pygame.display.update(r) def set_visible(self, flag): """ Set component visibility @@ -103,5 +115,7 @@ def set_visible(self, flag): def refresh(self): """ Refresh component. Used for periodical updates animation. """ + pass + \ No newline at end of file diff --git a/ui/container.py b/ui/container.py index 4dcb2624..b6789b2d 100644 --- a/ui/container.py +++ b/ui/container.py @@ -40,12 +40,14 @@ def add_component(self, component): def draw(self): """ Draw all components in container. Doesn't draw invisible container. """ + if not self.visible: return for comp in self.components: if comp: comp.draw() def clean_draw_update(self): """ Clean, draw and update container """ + self.clean() self.draw() self.update() @@ -65,6 +67,7 @@ def handle_event(self, event): def set_current(self): """ Set container as current. Used by screens """ + pass def set_visible(self, flag): diff --git a/ui/factory.py b/ui/factory.py index 13db8309..942c1096 100644 --- a/ui/factory.py +++ b/ui/factory.py @@ -139,7 +139,7 @@ def create_volume_control(self, bb): :param bb: bounding box - :return: colume control slider + :return: volume control slider """ img_knob = self.util.load_icon(IMAGE_VOLUME) img_knob_on = self.util.load_icon(IMAGE_VOLUME + IMAGE_SELECTED_SUFFIX) @@ -230,8 +230,7 @@ def create_menu_button(self, s, constr, action, scale, label_area_percent, label :param scale: True - scale images, False - don't scale images :return: menu button - """ - + """ if scale: self.set_state_scaled_icons(s, constr) s.bounding_box = constr diff --git a/ui/layout/borderlayout.py b/ui/layout/borderlayout.py index 65a12374..a3c3dd55 100644 --- a/ui/layout/borderlayout.py +++ b/ui/layout/borderlayout.py @@ -44,9 +44,8 @@ def get_next_constraints(self): return const def set_percent_constraints(self, top_percent, bottom_percent, left_percent, right_percent): - """ Creates bounding boxes for each screen part (TOP, BOTTOM, LEFT, RIGHT, CENTER). - - The parameters define constraints in percents. The center component always using remaining space + """ Create bounding boxes for each screen part (TOP, BOTTOM, LEFT, RIGHT, CENTER). + The parameters define constraints in percents. The center component is always using remaining space :param top_percent: percentage for top component :param bottom_percent: percentage for bottom component @@ -76,9 +75,8 @@ def set_percent_constraints(self, top_percent, bottom_percent, left_percent, rig self.CENTER = pygame.Rect(left_width + self.x, top_height + self.y, self.w - left_width - right_width, self.h - top_height - bottom_height) def set_pixel_constraints(self, top_pixels, bottom_pixels, left_pixels, right_pixels): - """ Creates bounding boxes for each screen part (TOP, BOTTOM, LEFT, RIGHT, CENTER). - - The parameters define constraints in pixels. The center component always using remaining space + """ Create bounding boxes for each screen part (TOP, BOTTOM, LEFT, RIGHT, CENTER). + The parameters define constraints in pixels. The center component is always using remaining space :param top_pixels: pixels for top component :param bottom_pixels: pixels for bottom component @@ -98,4 +96,5 @@ def set_pixel_constraints(self, top_pixels, bottom_pixels, left_pixels, right_pi if right_pixels != 0: self.RIGHT = pygame.Rect(self.w - right_pixels, top_pixels, right_pixels, left_height) - self.CENTER = pygame.Rect(left_pixels + self.x, top_pixels + self.y, self.w - left_pixels - right_pixels, self.h - top_pixels - bottom_pixels) \ No newline at end of file + self.CENTER = pygame.Rect(left_pixels + self.x, top_pixels + self.y, self.w - left_pixels - right_pixels, self.h - top_pixels - bottom_pixels) + \ No newline at end of file diff --git a/ui/layout/buttonlayout.py b/ui/layout/buttonlayout.py index 09ff5b80..e9d2e067 100644 --- a/ui/layout/buttonlayout.py +++ b/ui/layout/buttonlayout.py @@ -27,7 +27,7 @@ class ButtonLayout(object): """ Layout which arranges button components (icon and label) """ def __init__(self, state): - """ Initializer. Defines bounding boxes for button label (if any) and icon (if any) + """ Initializer. Define bounding boxes for button label (if any) and icon (if any) :param state: button state """ diff --git a/ui/layout/gridlayout.py b/ui/layout/gridlayout.py index 1a7d2bc5..57099077 100644 --- a/ui/layout/gridlayout.py +++ b/ui/layout/gridlayout.py @@ -18,7 +18,7 @@ import pygame class GridLayout(object): - """ Creates bounding boxes for placing components in a grid """ + """ Create bounding boxes for placing components in a grid """ def __init__(self, bb): """ Initializer @@ -42,22 +42,35 @@ def get_next_constraints(self): self.current_constraints = 0 return const - def set_pixel_constraints(self, rows, cols, gap_x=0, gap_y=0, shift_x=0, shift_y=0): - """ Prepares the list of bounding boxes according to the provided parameters + def set_pixel_constraints(self, rows, cols, gap_x=0, gap_y=0): + """ Prepare the list of bounding boxes according to the provided parameters :param rows: number of rows in the grid :param cols: number of columns in the grid :param gap_x: horizontal gap between components :param gap_y: vertical gap between components - :param shift_x: horizontal shift of the components - :param shift_y: vertical shift of the components """ - x_gaps = (cols - 1) * gap_x - y_gaps = (rows - 1) * gap_y + + x_gaps = (cols + 1) * gap_x + y_gaps = (rows + 1) * gap_y + grid_width = int((self.width - x_gaps)/cols) - grid_height = int((self.height - y_gaps)/rows) + grid_height = int((self.height - y_gaps)/rows) + + leftover_x = self.width - (grid_width * cols) - x_gaps + leftover_y = self.height - (grid_height * rows) - y_gaps + for num_y in range(rows): for num_x in range(cols): - x = shift_x + self.x + (grid_width + gap_x) * num_x - y = shift_y + self.y + (grid_height + gap_y) * num_y - self.constraints.append(pygame.Rect(x, y, grid_width, grid_height)) + x = gap_x + self.x + (grid_width + gap_x) * num_x + y = gap_y + self.y + (grid_height + gap_y) * num_y + final_grid_width = grid_width + final_grid_height = grid_height + + if num_x == cols - 1 and leftover_x: + final_grid_width += leftover_x + + if num_y == rows - 1 and leftover_y: + final_grid_height += leftover_y + 1 + + self.constraints.append(pygame.Rect(x, y, final_grid_width, final_grid_height)) diff --git a/ui/menu/genremenu.py b/ui/menu/genremenu.py index 979839e4..061cd4b3 100644 --- a/ui/menu/genremenu.py +++ b/ui/menu/genremenu.py @@ -28,7 +28,7 @@ def __init__(self, util, bgr=None, bounding_box=None): :param util: utility object :param bgr: menu background - :param bb: bounding box + :param bounding_box: bounding box """ self.factory = Factory(util) m = self.factory.create_genre_menu_button diff --git a/ui/menu/homemenu.py b/ui/menu/homemenu.py index d9361821..936ed318 100644 --- a/ui/menu/homemenu.py +++ b/ui/menu/homemenu.py @@ -28,7 +28,7 @@ def __init__(self, util, bgr=None, bounding_box=None): :param util: utility object :param bgr: menu background - :param bb: bounding box + :param bounding_box: bounding box """ self.factory = Factory(util) self.config = util.config diff --git a/ui/menu/languagemenu.py b/ui/menu/languagemenu.py index dff36c84..c4a64854 100644 --- a/ui/menu/languagemenu.py +++ b/ui/menu/languagemenu.py @@ -28,7 +28,7 @@ def __init__(self, util, bgr=None, bounding_box=None): :param util: utility object :param bgr: menu background - :param bb: bounding box + :param bounding_box: bounding box """ self.factory = Factory(util) m = self.factory.create_language_menu_button diff --git a/ui/menu/menu.py b/ui/menu/menu.py index 4dca8ee8..00a4aed6 100644 --- a/ui/menu/menu.py +++ b/ui/menu/menu.py @@ -24,12 +24,10 @@ KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_SELECT class Menu(Container): - """ Base class for all menu components. - + """ Base class for all menu components. Extends Component class. Consists of Button components - """ - + """ def __init__(self, util, bgr=None, bb=None, rows=3, cols=3, create_item_method=None): """ Initializer @@ -46,7 +44,7 @@ def __init__(self, util, bgr=None, bb=None, rows=3, cols=3, create_item_method=N self.start_listeners = [] self.move_listeners = [] self.layout = GridLayout(bb) - self.layout.set_pixel_constraints(self.rows, self.cols, 1, 1, 0, 1) + self.layout.set_pixel_constraints(self.rows, self.cols, 1, 1) self.items = {} self.buttons = {} self.factory = Factory(util) @@ -179,6 +177,7 @@ def select_by_index(self, index): def select_action(self): """ Notify listeners of the selected button """ + for button in self.buttons.values(): if button.state.index == self.selected_index: button.notify_release_listeners(button.state) @@ -244,6 +243,5 @@ def handle_event(self, event): self.select_by_index(i) else: Container.handle_event(self, event) - - + \ No newline at end of file diff --git a/ui/menu/saverdelaymenu.py b/ui/menu/saverdelaymenu.py index 25bae386..04f5f486 100644 --- a/ui/menu/saverdelaymenu.py +++ b/ui/menu/saverdelaymenu.py @@ -27,7 +27,7 @@ def __init__(self, util, bgr=None, bounding_box=None): :param util: utility object :param bgr: menu background - :param bb: bounding box + :param bounding_box: bounding box """ self.factory = Factory(util) m = self.factory.create_saver_delay_menu_button diff --git a/ui/menu/savermenu.py b/ui/menu/savermenu.py index 0796b270..b3e90ebd 100644 --- a/ui/menu/savermenu.py +++ b/ui/menu/savermenu.py @@ -28,11 +28,11 @@ def __init__(self, util, bgr=None, bounding_box=None): :param util: utility object :param bgr: menu background - :param bb: bounding box + :param bounding_box: bounding box """ self.factory = Factory(util) m = self.factory.create_saver_menu_button - Menu.__init__(self, util, bgr, bounding_box, 1, 3, create_item_method=m) + Menu.__init__(self, util, bgr, bounding_box, 1, 4, create_item_method=m) self.config = util.config current_saver_name = self.config[CURRENT][KEY_SCREENSAVER] self.savers = util.load_menu(SCREENSAVER_ITEMS, GENRE) diff --git a/ui/menu/stationmenu.py b/ui/menu/stationmenu.py index cb59d06d..7006996e 100644 --- a/ui/menu/stationmenu.py +++ b/ui/menu/stationmenu.py @@ -34,15 +34,17 @@ def __init__(self, playlist, util, bgr=None, bounding_box=None): :param playlist: playlist object :param util: utility object :param bgr: menu background - :param bb: bounding box + :param bounding_box: bounding box """ self.factory = Factory(util) self.util = util self.config = self.util.config m = self.factory.create_station_menu_button n = playlist.items_per_line - Menu.__init__(self, util, bgr, bounding_box, n, n, create_item_method=m) - self.bounding_box = bounding_box + bb = bounding_box + bb.height += 0 + Menu.__init__(self, util, bgr, bb, n, n, create_item_method=m) + self.bounding_box = bb self.playlist = playlist self.current_mode = self.STATION_MODE s = self.util.load_icon(IMAGE_SHADOW, False) @@ -65,7 +67,7 @@ def set_playlist(self, playlist): self.playlist = playlist def init_station(self, index): - """ Initialize the station specified by it index + """ Initialize the station specified by its index :param index: station index """ @@ -263,7 +265,8 @@ def switch_mode(self, state): self.set_station_mode(state) def set_page_mode(self): - """ Set Page mode """ + """ Set Page mode """ + self.current_mode = self.PAGE_MODE self.draw() self.notify_mode_listeners(self.current_mode) @@ -383,6 +386,7 @@ def handle_event(self, event): def draw(self): """ Draw Station Menu """ + self.clean() l = len(self.components) @@ -428,4 +432,4 @@ def notify_mode_listeners(self, mode): :param mode: the mode """ for listener in self.mode_listeners: - listener(mode) \ No newline at end of file + listener(mode) diff --git a/ui/screen/about.py b/ui/screen/about.py index 7a17574b..bd408f41 100644 --- a/ui/screen/about.py +++ b/ui/screen/about.py @@ -58,7 +58,7 @@ def __init__(self, util): release = factory.create_output_text("about-name", layout.BOTTOM, color_web_bgr, color_logo, int(release_font_size), full_width=True) - release.set_text_no_draw("Leonardo Edition") + release.set_text_no_draw("Michelangelo Edition") self.add_component(release) def add_listener(self, listener): @@ -92,7 +92,8 @@ def handle_event(self, event): self.notify_listeners(None) def get_clickable_rect(self): - """ Return the list of rectangles which define the clickable areas on screen. Used for web browser. + """ Return the list of rectangles which define the clickable areas on screen. + Used for web browser. :return: list of rectangles """ @@ -102,4 +103,4 @@ def get_clickable_rect(self): c.bgr = c.fgr = (0, 0, 0) c.content_x = c.content_y = 0 d = [c] - return d \ No newline at end of file + return d diff --git a/ui/screen/screen.py b/ui/screen/screen.py index 77507fe2..e0aaa204 100644 --- a/ui/screen/screen.py +++ b/ui/screen/screen.py @@ -71,3 +71,4 @@ def get_clickable_rect(self): c.content_x = c.content_y = 0 d = [c] return d + diff --git a/ui/screen/station.py b/ui/screen/station.py index e1197ebd..fe393619 100644 --- a/ui/screen/station.py +++ b/ui/screen/station.py @@ -97,6 +97,8 @@ def __init__(self, listeners, util): self.volume = self.factory.create_volume_control(layout.BOTTOM) self.volume.add_slide_listener(listeners["set volume"]) + self.volume.add_slide_listener(listeners["set config volume"]) + self.volume.add_slide_listener(listeners["set screensaver volume"]) self.volume.add_knob_listener(listeners["mute"]) Container.add_component(self, self.volume) @@ -153,7 +155,7 @@ def update_arrow_button_labels(self, state): self.page_up_button.clean_draw_update() def create_left_panel(self, layout, listeners): - """ Create Station Screen left panel. Includes Shutdown button, Left button and Home button. + """ Create Station Screen left panel. Include Shutdown button, Left button and Home button. :param layout: left panel layout :param listeners: event listeners @@ -174,7 +176,7 @@ def create_left_panel(self, layout, listeners): Container.add_component(self, panel) def create_right_panel(self, layout, listeners): - """ Create Station Screen right panel. Includes Genre button, right button and Play/Pause button + """ Create Station Screen right panel. Include Genre button, right button and Play/Pause button :param layout: right panel layout :param listeners: event listeners @@ -229,6 +231,7 @@ def set_genre_button_image(self, genre): def set_current(self): """ Set current station by index defined in current playlist """ + selected_genre = self.genres[self.config[CURRENT][PLAYLIST]] if selected_genre == self.current_genre: return diff --git a/ui/slider/slider.py b/ui/slider/slider.py index b3c0f5c4..69964bf9 100644 --- a/ui/slider/slider.py +++ b/ui/slider/slider.py @@ -20,7 +20,6 @@ from util.keys import USER_EVENT_TYPE, SUB_TYPE_KEYBOARD from ui.component import Component from ui.container import Container -from player.player import Player class Slider(Container): """ Slider UI component. Extends Container class """ @@ -66,6 +65,7 @@ def __init__(self, util, name, bgr, slider_color, img_knob, img_knob_on, img_sel self.key_decr = key_decr self.key_knob = key_knob slider_x = self.knob_width/2 + self.bounding_box.h += 1 slider_y = self.bounding_box.y + self.bounding_box.height - self.bounding_box.height/2 slider_width = self.bounding_box.width - self.knob_width slider_height = 2 @@ -106,7 +106,8 @@ def add_press_listener(self, listener): self.press_listeners.append(listener) def notify_press_listeners(self): - """ Notify all press event listeners """ + """ Notify all press event listeners """ + for listener in self.press_listeners: listener(self.get_position()) @@ -119,7 +120,8 @@ def add_slide_listener(self, listener): self.slide_listeners.append(listener) def notify_slide_listeners(self): - """ Notify event listeners for clicking on slider line event """ + """ Notify event listeners for clicking on slider line event """ + for listener in self.slide_listeners: listener(self.get_position()) @@ -133,6 +135,7 @@ def add_knob_listener(self, listener): def notify_knob_listeners(self): """ Notify all knob event listeners """ + for listener in self.knob_listeners: if "mute" in listener.__name__: listener() @@ -149,6 +152,7 @@ def add_motion_listener(self, listener): def notify_motion_listeners(self): """ Notify all motion event listeners """ + for listener in self.motion_listeners: if self.event_source_local: listener(self.get_position()) @@ -181,6 +185,7 @@ def get_position(self): def update_position(self): """ Update the knob position - redraw slider """ + if self.last_knob_position > self.slider_max_x - self.knob_width/2: self.last_knob_position = self.slider_max_x - self.knob_width/2 elif self.last_knob_position <= 0: @@ -283,6 +288,7 @@ def knob_event(self, event): def press_action(self): """ Knob press event handler """ + self.clicked = True self.current_img = self.img_knob_on self.current_filename = self.knob_on_filename @@ -291,6 +297,7 @@ def press_action(self): def update_knob_image(self): """ Update knob image """ + knob = self.components[2] knob.content = self.current_img knob.image_filename = self.current_filename @@ -334,6 +341,7 @@ def motion_action(self, pos): def handle_knob_selection(self): """ Knob selection event handler """ + if self.selected == False: self.selected = True self.current_img = self.img_selected diff --git a/ui/state.py b/ui/state.py index 6c609118..6538a3b7 100644 --- a/ui/state.py +++ b/ui/state.py @@ -17,4 +17,5 @@ class State(object): """ Button State class. It's populated dynamically. """ + pass diff --git a/ui/text/dynamictext.py b/ui/text/dynamictext.py index 85396305..e7ca4ecd 100644 --- a/ui/text/dynamictext.py +++ b/ui/text/dynamictext.py @@ -28,6 +28,7 @@ class DynamicText(OutputText): """ Dynamic text UI component. Extends static OutputText class """ + def __init__(self, name, bb, util, font_size=None, bgr=None, fgr=None, halign=1, valign=4, shift_x=0, shift_y=0, font=None): """ Initializer @@ -71,7 +72,7 @@ def update_text(self, text): 1. If text length less than component width creates one line label text 2. If text length larger than component width reduces the font, if new text length is smaller creates one line label with new font 3. If reduced text is still larger than component width reduce text and create two labels one above the other - 4. If two lines of text still don't fit to current componet size than use animation + 4. If two lines of text still don't fit to current component size than use animation :param text: new text """ @@ -145,6 +146,7 @@ def start_animation(self, text): def refresh(self): """ Animation method """ + if not self.animate: return step = 1 @@ -193,10 +195,13 @@ def add_listener(self, listener): def notify_listeners(self): """ Notify all event listeners """ + for listener in self.start_listeners: listener(self) def shutdown(self): """ Stop animation (if any) """ + self.animate = False + \ No newline at end of file diff --git a/ui/text/outputtext.py b/ui/text/outputtext.py index 3d37eb9f..da6c1aae 100644 --- a/ui/text/outputtext.py +++ b/ui/text/outputtext.py @@ -15,12 +15,14 @@ # You should have received a copy of the GNU General Public License # along with Peppy Player. If not, see . +import pygame + from ui.component import Component from ui.container import Container from util.keys import H_ALIGN_LEFT, H_ALIGN_CENTER, H_ALIGN_RIGHT, V_ALIGN_TOP, V_ALIGN_CENTER, V_ALIGN_BOTTOM class OutputText(Container): - """ Static output text UI component. Extands Container class """ + """ Static output text UI component. Extends Container class """ def __init__(self, util, name, bb, font_size=None, bgr=None, fgr=None, halign=1, valign=4, shift_x=0, shift_y=0, full_width=False, font=None): """ Initializer @@ -53,8 +55,12 @@ def __init__(self, util, name, bb, font_size=None, bgr=None, fgr=None, halign=1, def add_bgr(self): """ Add background rectangle """ + if not self.full_width: - self.bounding_box.w = self.bounding_box.w - 1 + self.bounding_box.x += 1 + self.bounding_box.y += 1 + self.bounding_box.w -= 2 + self.bounding_box.h -= 1 comp = Component(self.util, self.bounding_box) comp.name = self.name + ".bgr" comp.bgr = self.bgr @@ -79,6 +85,7 @@ def set_text_no_draw(self, text): def prepare_label(self): """ Prepare label component representing this output text. Used for web. """ + if self.text == None: return size = self.font.size(self.text) @@ -136,3 +143,4 @@ def set_visible(self, flag): :param flag: True - visible, False - invisible """ Container.set_visible(self, flag) + diff --git a/util/config.py b/util/config.py index e6988b9c..30ec9110 100644 --- a/util/config.py +++ b/util/config.py @@ -23,16 +23,17 @@ from util.keys import * FILE_CONFIG = "config.txt" -FOLDER = "folder" -MUSIC_SERVER = "music.server" -COMMAND = "command" +AUDIO = "audio" +SERVER_FOLDER = "server.folder" +SERVER_COMMAND = "server.command" +CLIENT_NAME = "client.name" HOST = "host" PORT = "port" USAGE = "usage" +USE_TOUCHSCREEN = "use.touchscreen" +USE_MOUSE = "use.mouse" USE_LIRC = "use.lirc" USE_ROTARY_ENCODERS = "use.rotary.encoders" -USE_MPC_PLAYER = "use.mpc.player" -USE_MPD_PLAYER = "use.mpd.player" USE_WEB = "use.web" USE_LOGGING = "use.logging" FONT_SECTION = "font" @@ -42,14 +43,16 @@ class Config(object): def __init__(self): """ Initializer """ + self.config = self.load_config() self.init_lcd() self.config[PYGAME_SCREEN] = self.get_pygame_screen() def load_config(self): """ Loads and parses configuration file config.txt. - Creates dictionary entry for each property in file. - :return: dictionary containing all properties from config.txt file + Creates dictionary entry for each property in the file. + + :return: dictionary containing all properties from the config.txt file """ config = {} @@ -75,16 +78,15 @@ def load_config(self): config[ICON_SIZE_FOLDER] = folder_name config[SCREEN_RECT] = pygame.Rect(0, 0, c[WIDTH], c[HEIGHT]) - c = {FOLDER : config_file.get(MUSIC_SERVER, FOLDER)} - c[COMMAND] = config_file.get(MUSIC_SERVER, COMMAND) - c[HOST] = config_file.get(MUSIC_SERVER, HOST) - c[PORT] = config_file.get(MUSIC_SERVER, PORT) - config[MUSIC_SERVER] = c + c = {SERVER_FOLDER : config_file.get(AUDIO, SERVER_FOLDER)} + c[SERVER_COMMAND] = config_file.get(AUDIO, SERVER_COMMAND) + c[CLIENT_NAME] = config_file.get(AUDIO, CLIENT_NAME) + config[AUDIO] = c c = {USE_LIRC : config_file.getboolean(USAGE, USE_LIRC)} + c[USE_TOUCHSCREEN] = config_file.getboolean(USAGE, USE_TOUCHSCREEN) + c[USE_MOUSE] = config_file.getboolean(USAGE, USE_MOUSE) c[USE_ROTARY_ENCODERS] = config_file.getboolean(USAGE, USE_ROTARY_ENCODERS) - c[USE_MPC_PLAYER] = config_file.getboolean(USAGE, USE_MPC_PLAYER) - c[USE_MPD_PLAYER] = config_file.getboolean(USAGE, USE_MPD_PLAYER) c[USE_WEB] = config_file.getboolean(USAGE, USE_WEB) c[USE_LOGGING] = config_file.getboolean(USAGE, USE_LOGGING) if c[USE_LOGGING]: @@ -94,11 +96,6 @@ def load_config(self): config[USAGE] = c c = {HTTP_PORT : config_file.get(WEB_SERVER, HTTP_PORT)} - try: - c[HTTP_HOST] = config_file.get(WEB_SERVER, HTTP_HOST) - except: - pass - config[WEB_SERVER] = c c = {COLOR_WEB_BGR : self.get_color_tuple(config_file.get(COLORS, COLOR_WEB_BGR))} @@ -117,6 +114,7 @@ def load_config(self): c[STATION] = config_file.getint(CURRENT, STATION) c[KEY_SCREENSAVER] = config_file.get(CURRENT, KEY_SCREENSAVER) c[KEY_SCREENSAVER_DELAY] = config_file.get(CURRENT, KEY_SCREENSAVER_DELAY) + c[VOLUME] = config_file.getint(CURRENT, VOLUME) config[CURRENT] = c config[ORDER_HOME_MENU] = self.get_section(config_file, ORDER_HOME_MENU) @@ -131,8 +129,8 @@ def load_config(self): def get_section(self, config_file, section_name): """ Return property file section specified by name - :param config_file: parsed config file - :section_name: sction name in config file (string enclosed between []) + :param config_file: parsed configuration file + :section_name: section name in the configuration file (string enclosed between []) :return: dictionary with properties from specified section """ @@ -154,7 +152,8 @@ def get_color_tuple(self, s): return tuple(int(e) for e in a) def save_config(self): - """ Save current configuration object (self.config) into config.txt file """ + """ Save current configuration object (self.config) into config.txt file """ + config_parser = ConfigParser() config_parser.read(FILE_CONFIG) @@ -186,7 +185,7 @@ def save_config(self): def get_pygame_screen(self): """ Initialize Pygame screen and place it in config object - :return: pygame screen object which is used as drawing context + :return: pygame display object which is used as drawing context """ if self.config[LINUX_PLATFORM]: pygame.display.init() @@ -201,9 +200,13 @@ def get_pygame_screen(self): return pygame.display.set_mode((w, h), pygame.DOUBLEBUF, d) def init_lcd(self): - """ Initialize touch-screen """ + """ Initialize touch-screen """ + + if not self.config[USAGE][USE_TOUCHSCREEN]: + return os.environ["SDL_FBDEV"] = "/dev/fb1" - os.environ["SDL_MOUSEDEV"] = "/dev/input/touchscreen" - os.environ["SDL_MOUSEDRV"] = "TSLIB" + if self.config[USAGE][USE_MOUSE]: + os.environ["SDL_MOUSEDEV"] = "/dev/input/touchscreen" + os.environ["SDL_MOUSEDRV"] = "TSLIB" \ No newline at end of file diff --git a/util/keys.py b/util/keys.py index cc151e6c..49936abf 100644 --- a/util/keys.py +++ b/util/keys.py @@ -51,6 +51,7 @@ STATION = "station" PREVIOUS = "previous" PLAYLIST = "playlist" +VOLUME = "volume" ORDER_GENRE_MENU = "order.genre.menu" ORDER_HOME_MENU = "order.home.menu" ORDER_LANGUAGE_MENU = "order.language.menu" @@ -60,7 +61,6 @@ GENRE = "genre" NAME= "name" WEB_SERVER = "web.server" -HTTP_HOST = "http.host" HTTP_PORT = "http.port" USER_EVENT_TYPE = pygame.USEREVENT + 1 diff --git a/util/util.py b/util/util.py index 0cf6eccb..ce29bda6 100644 --- a/util/util.py +++ b/util/util.py @@ -66,23 +66,24 @@ HOME_DISABLED_ITEMS = [IMAGE_HARD_DRIVE, IMAGE_STREAM] GENRE_ITEMS = ["children", "classical", "contemporary", "culture", "jazz", "news", "pop", "retro", "rock"] LANGUAGE_ITEMS = ["en_us", "de", "fr", "ru"] -SCREENSAVER_ITEMS = ["clock", "logo", "slideshow"] +SCREENSAVER_ITEMS = ["clock", "logo", "slideshow", "vumeter"] FOLDER_BUNDLES = "bundles" class Util(object): """ Utility class """ def __init__(self): - """ Initializer. Prepares Config object. """ - self.config_class = Config() - self.config = self.config_class.config - self.config[LABELS] = self.get_labels() + """ Initializer. Prepares Config object. """ + self.font_cache = {} self.image_cache = {} self.image_cache_base64 = {} + self.config_class = Config() + self.config = self.config_class.config + self.config[LABELS] = self.get_labels() def get_labels(self): - """ Reads labels for current language + """ Read labels for current language :return: labels dictionary """ @@ -103,14 +104,14 @@ def load_image(self, path, base64=False): return self.load_pygame_image(path) def load_pygame_image(self, path): - """ Check if image is in the cache. - + """ Load image. + First, check if image is in the cache. If yes, return the image from the cache. If not load image file and place it in the cache. :param path: image path - :return: pygame image + :return: tuple where the first element is the path to the image and the second element is the image itself """ image = None try: @@ -131,10 +132,10 @@ def load_pygame_image(self, path): return None def load_base64_image(self, path): - """ Check if image is in the cache for encoded images. - + """ Load image and encode it using base64 encoding. + First, check if image is in the cache for encoded images. If yes, return the image from the cache. - If not load image file and place it in the cache. + If not load image file, encode it using base64 and place it in the cache. :param path: image path @@ -167,7 +168,7 @@ def load_icon(self, filename, resizable=True): def load_screensaver_images(self, saver_name, folder_name): """ Load screensaver images (e.g. for Slideshow plug-in) - :param saver_name: defines the folder below screensaver folder (e.g. slideshow) + :param saver_name: defines the folder under screensaver folder (e.g. slideshow) :param folder_name: defines the images folder (e.g. slides) :return: list of images @@ -262,7 +263,7 @@ def load_properties(self, path, encoding=None): :param path: file path :param encoding: file encoding - :return: dictioanry with properties + :return: dictionary with properties """ properties = {} file = codecs.open(path, "r", encoding) diff --git a/web/client/controller.js b/web/client/controller.js index 660037ea..8bdc5641 100644 --- a/web/client/controller.js +++ b/web/client/controller.js @@ -99,6 +99,7 @@ function stopScreensaver() { /** * Remove components specified by their IDs +* * @param ids - the list of component ids to remove */ function removeComponents(ids) { diff --git a/web/client/uifactory.js b/web/client/uifactory.js index 6f281657..db04bfb6 100644 --- a/web/client/uifactory.js +++ b/web/client/uifactory.js @@ -129,7 +129,7 @@ function createScreen(id, bgr) { } /** -* Creates SVG container with contains all UI components +* Creates SVG container which contains all UI components * * @param id - the name of container * @param width - container width @@ -269,7 +269,7 @@ function getAnimation(from, to) { } /** -* Appends animated text to provided group component +* Appends animated text to the provided group component * * @param group - the group component to which animation will be added * @param comp - text component to animate diff --git a/web/client/volume.js b/web/client/volume.js index 777cce21..18496aa7 100644 --- a/web/client/volume.js +++ b/web/client/volume.js @@ -25,7 +25,7 @@ var volumeSliderId = "volume.slider"; var volumeKnobId = "volume.knob"; /** -* Adds knob mouse listeners to provided knob object +* Adds knob mouse listeners to the provided knob object * * @param knob - the knob object */ @@ -39,7 +39,7 @@ function addKnobFunctionality(knob) { } /** -* Adds mouse up/down listeners to provided element +* Adds mouse up/down listeners to the provided element * * @param element - the element */ @@ -49,7 +49,7 @@ function addSliderFunctionality(element) { } /** -* Event handler for knob mouse down event +* Event handler for the knob mouse down event * * @param event - the event to handle */ @@ -61,7 +61,7 @@ function knobDown(event) { } /** -* Event handler for knob mouse up event +* Event handler for the knob mouse up event * * @param event - the event to handle */ @@ -74,7 +74,7 @@ function knobUp(event) { } /** -* Event handler for knob move event +* Event handler for the knob move event * * @param event - the event to handle */ diff --git a/web/server/jsonfactory.py b/web/server/jsonfactory.py index 6931e53c..101b22bf 100644 --- a/web/server/jsonfactory.py +++ b/web/server/jsonfactory.py @@ -159,7 +159,7 @@ def saver_screen_to_json(self, screen): return self.screen_to_json("saver", screen) def menu_to_json(self, menu): - """ Convert menu object into Json + """ Convert menu object into Json object :param menu: menu object @@ -172,7 +172,7 @@ def menu_to_json(self, menu): return e def start_screensaver_to_json(self): - """ Convert start screensaver event into Json + """ Convert start screensaver event into Json object :return: Json object """ @@ -181,7 +181,7 @@ def start_screensaver_to_json(self): return e def stop_screensaver_to_json(self): - """ Convert stop screensaver event into Json + """ Convert stop screensaver event into Json object :return: Json object """ diff --git a/web/server/requesthandler.py b/web/server/requesthandler.py index cb26ce69..2817e2cc 100644 --- a/web/server/requesthandler.py +++ b/web/server/requesthandler.py @@ -28,6 +28,7 @@ class RequestHandler(SimpleHTTPRequestHandler): def do_GET(self): """ GET request handler. Initiates WebSocket protocol """ + u = None try: u = self.headers['upgrade'] @@ -51,6 +52,7 @@ def do_GET(self): def log_message(self, f, *args): """ Empty implementation to avoid issues when used without console """ + return def handle_command(self, d): diff --git a/web/server/webserver.py b/web/server/webserver.py index 15547097..083cf0fa 100644 --- a/web/server/webserver.py +++ b/web/server/webserver.py @@ -16,7 +16,7 @@ # along with Peppy Player. If not, see . import threading -from util.keys import WEB_SERVER, HTTP_HOST, HTTP_PORT +from util.keys import WEB_SERVER, HTTP_PORT from web.server.jsonfactory import JsonFactory from http.server import HTTPServer from web.server.requesthandler import RequestHandler @@ -40,6 +40,7 @@ def __init__(self, util, peppy): self.web_server_thread.start() def get_ip(self): + """ Returns current IP address """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect(('10.255.255.255', 0)) @@ -52,12 +53,8 @@ def get_ip(self): def start(self): """ Prepares request handler and starts web server """ - host = None - try: - host = self.config[WEB_SERVER][HTTP_HOST] - except: - host = self.get_ip() + host = self.get_ip() port = self.config[WEB_SERVER][HTTP_PORT] handler = RequestHandler @@ -173,7 +170,7 @@ def get_about_screen(self): def screen_to_json(self): """ Convert current screen to JSON objects - :return: list of json objects representing current screen + :return: list of JSON objects representing current screen """ if self.peppy.screensaver_dispatcher.saver_running: self.peppy.screensaver_dispatcher.cancel_screensaver() @@ -183,128 +180,128 @@ def screen_to_json(self): return self.jason_factory.screen_to_json(current_screen, screen) def title_to_json(self): - """ Convert title object into json object + """ Convert title object into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.title_to_json(self.get_station_screen().screen_title) def volume_to_json(self): - """ Convert volume object into json object + """ Convert volume object into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().volume) def genre_button_to_json(self): - """ Convert genre button into json object + """ Convert genre button into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().genres_button) def home_button_to_json(self): - """ Convert home button into json object + """ Convert home button into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().home_button) def left_button_to_json(self): - """ Convert left button into json object + """ Convert left button into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().left_button) def page_down_button_to_json(self): - """ Convert page down button into json object + """ Convert page down button into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().page_down_button) def right_button_to_json(self): - """ Convert right button into json object + """ Convert right button into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().right_button) def page_up_button_to_json(self): - """ Convert page up button into json object + """ Convert page up button into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().page_up_button) def station_menu_to_json(self): - """ Convert station menu into json object + """ Convert station menu into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.station_menu_to_json(self.get_station_screen().station_menu) def play_button_to_json(self): - """ Convert play button into json object + """ Convert play button into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().play_button) def shutdown_button_to_json(self): - """ Convert shutdown button into json object + """ Convert shutdown button into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.container_to_json(self.get_station_screen().shutdown_button) def genre_menu_to_json(self): - """ Convert genre menu into json object + """ Convert genre menu into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.genre_menu_to_json(self.get_genre_screen().genre_menu) def home_menu_to_json(self): - """ Convert home menu into json object + """ Convert home menu into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.home_menu_to_json(self.get_home_screen().home_menu) def language_menu_to_json(self): - """ Convert language menu into json object + """ Convert language menu into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.language_menu_to_json(self.get_language_screen().language_menu) def saver_screen_to_json(self): - """ Convert screensaver screen into json object + """ Convert screensaver screen into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.saver_screen_to_json(self.get_saver_screen()) def about_screen_to_json(self): - """ Convert about screen into json object + """ Convert about screen into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.about_screen_to_json(self.get_about_screen()) def start_screensaver_to_json(self): - """ Convert start screensaver event into json object + """ Convert start screensaver event into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.start_screensaver_to_json() def stop_screensaver_to_json(self): - """ Convert stop screensaver event into json object + """ Convert stop screensaver event into JSON object - :return: json object + :return: JSON object """ return self.jason_factory.stop_screensaver_to_json() @@ -411,7 +408,7 @@ def add_screensaver_web_listener(self, screensaver_dispatcher): def shutdown(self): """ Shutdown Web Server """ - self.web_server.socket.close() + self.web_server.socket.close() \ No newline at end of file diff --git a/web/server/websocket.py b/web/server/websocket.py index 1472a5d3..1c6f9743 100644 --- a/web/server/websocket.py +++ b/web/server/websocket.py @@ -43,6 +43,7 @@ def __init__(self, request): def handshake(self): """ Implements WebSocket handshake functionality """ + key = self.request.headers['Sec-WebSocket-Key'].strip() accept_data = (key + self.magic).encode('latin-1', 'strict') digest = b64encode(sha1(accept_data).digest()) @@ -55,6 +56,7 @@ def handshake(self): def read_next_message(self): """ Read next message from request """ + i = self.request.rfile header_byte_1 = struct.unpack('B', i.read(1))[0] @@ -98,6 +100,7 @@ def read_next_message(self): def read_message(self): """ Read the message from request """ + message = None while message is None and not self.closed: message = self.read_next_message()