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()