16 Commits

Author SHA1 Message Date
Martin Wagner
79ce749f61 preparations for 0.9.1 2020-09-14 13:59:45 +02:00
Martin Wagner
75a08b6254 fixed .desktop file 2020-09-13 22:06:08 +02:00
Martin Wagner
ce350bca83 changed description 2020-09-13 21:40:47 +02:00
Martin Wagner
6d3def7d62 small style fixes 2020-09-12 15:00:36 +02:00
Martin Wagner
1b96ea30f5 wrapped long lines (>150 chars) 2020-09-12 14:31:17 +02:00
Martin Wagner
20566566cd removed mpris related stuff from MainWindow 2020-09-11 18:15:27 +02:00
Martin Wagner
12da5321b8 fixed version number 2020-09-11 14:00:48 +02:00
Martin Wagner
03f2ccd747 improved audio_output_format parsing 2020-09-11 13:57:39 +02:00
Martin Wagner
e79b6f8d6f fixed main menu 2020-09-11 13:48:40 +02:00
Martin Wagner
c953bfa8b3 fixed ProfileSelect 2020-09-11 00:24:22 +02:00
Martin Wagner
423a123f79 fixed wrong title 2020-09-10 23:40:27 +02:00
Martin Wagner
7a2c8cbf12 fixed race condition in AlbumWindow 2020-09-10 23:35:10 +02:00
Martin Wagner
4cc8c46055 minor fixes 2020-09-10 23:07:00 +02:00
Martin Wagner
053dbbe08f fixed gui stall caused by auto reconnect 2020-09-10 21:45:57 +02:00
Martin Wagner
291507ca1c fixed type error in _main_loop fixing problems with .ogg files 2020-09-10 17:00:56 +02:00
Martin Wagner
a958438f32 fixed spacing of lyrics button 2020-09-06 22:15:02 +02:00
6 changed files with 540 additions and 325 deletions

View File

@@ -39,8 +39,9 @@ import re
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
VERSION='0.9.0' # sync with setup.py
VERSION='0.9.1' # sync with setup.py
COVER_REGEX="^\.?(album|cover|folder|front).*\.(gif|jpeg|jpg|png)$"
@@ -100,6 +101,8 @@ class MPRISInterface(dbus.service.Object): # TODO emit Seeked if needed
self._client.emitter.connect("repeat", self._on_loop_changed)
self._client.emitter.connect("single", self._on_loop_changed)
self._client.emitter.connect("random", self._on_random_changed)
self._client.emitter.connect("disconnected", self._on_disconnected)
self._client.emitter.connect("reconnected", self._on_reconnected)
def acquire_name(self):
self._bus_name=dbus.service.BusName(self._name, bus=self._bus, allow_replacement=True, replace_existing=True)
@@ -178,7 +181,8 @@ class MPRISInterface(dbus.service.Object): # TODO emit Seeked if needed
if 'file' in mpd_meta:
song_file=mpd_meta['file']
self._metadata['xesam:url']="file://"+os.path.join(self._settings.get_value("paths")[self._settings.get_int("active-profile")], song_file)
lib_path=self._settings.get_value("paths")[self._settings.get_int("active-profile")]
self._metadata['xesam:url']="file://"+os.path.join(lib_path, song_file)
cover=Cover(self._settings, mpd_meta)
if cover.path is None:
self._metadata['mpris:artUrl']=None
@@ -430,6 +434,13 @@ class MPRISInterface(dbus.service.Object): # TODO emit Seeked if needed
def _on_random_changed(self, *args):
self.update_property('org.mpris.MediaPlayer2.Player', 'Shuffle')
def _on_reconnected(self, *args):
self.acquire_name()
def _on_disconnected(self, *args):
self.release_name()
self._metadata={}
def _name_owner_changed_callback(self, name, old_owner, new_owner):
if name == self._name and old_owner == self._uname and new_owner != "":
try:
@@ -566,8 +577,8 @@ class Cover(object):
break
def get_pixbuf(self, size):
if self.path is None:
self.path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename() # fallback cover
if self.path is None: # fallback needed
self.path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename()
return GdkPixbuf.Pixbuf.new_from_file_at_size(self.path, size, size)
######################
@@ -590,7 +601,16 @@ class ClientHelper():
return return_song
def extend_song_for_display(song):
base_song={"title": _("Unknown Title"), "track": "0", "disc": "", "artist": _("Unknown Artist"), "album": _("Unknown Album"), "duration": "0.0", "date": "", "genre": ""}
base_song={
"title": _("Unknown Title"),
"track": "0",
"disc": "",
"artist": _("Unknown Artist"),
"album": _("Unknown Album"),
"duration": "0.0",
"date": "",
"genre": ""
}
base_song.update(song)
base_song["human_duration"]=str(datetime.timedelta(seconds=int(float(base_song["duration"])))).lstrip("0").lstrip(":")
return base_song
@@ -610,6 +630,7 @@ class MpdEventEmitter(GObject.Object):
'update': (GObject.SignalFlags.RUN_FIRST, None, ()),
'disconnected': (GObject.SignalFlags.RUN_FIRST, None, ()),
'reconnected': (GObject.SignalFlags.RUN_FIRST, None, ()),
'connection_error': (GObject.SignalFlags.RUN_FIRST, None, ()),
'current_song_changed': (GObject.SignalFlags.RUN_FIRST, None, ()),
'state': (GObject.SignalFlags.RUN_FIRST, None, (str,)),
'elapsed_changed': (GObject.SignalFlags.RUN_FIRST, None, (float,float,)),
@@ -619,7 +640,7 @@ class MpdEventEmitter(GObject.Object):
'random': (GObject.SignalFlags.RUN_FIRST, None, (bool,)),
'single': (GObject.SignalFlags.RUN_FIRST, None, (bool,)),
'consume': (GObject.SignalFlags.RUN_FIRST, None, (bool,)),
'audio': (GObject.SignalFlags.RUN_FIRST, None, (float,int,int,)),
'audio': (GObject.SignalFlags.RUN_FIRST, None, (str,str,str,)),
'bitrate': (GObject.SignalFlags.RUN_FIRST, None, (float,))
}
@@ -636,6 +657,9 @@ class MpdEventEmitter(GObject.Object):
def do_reconnected(self):
pass
def do_connection_error(self):
print("Connection error!")
def do_current_file_changed(self):
pass
@@ -666,6 +690,7 @@ class Client(MPDClient):
self.emitter=MpdEventEmitter()
self._last_status={}
self._refresh_interval=self._settings.get_int("refresh-interval")
self._main_timeout_id=None
#connect
self._settings.connect("changed::active-profile", self._on_active_profile_changed)
@@ -678,9 +703,27 @@ class Client(MPDClient):
return func(*args)
def start(self):
if self._disconnected_loop():
self.emitter.emit("disconnected")
self._disconnected_timeout_id=GLib.timeout_add(1000, self._disconnected_loop)
self.emitter.emit("disconnected") # bring player in defined state
active=self._settings.get_int("active-profile")
try:
self.connect(self._settings.get_value("hosts")[active], self._settings.get_value("ports")[active])
if self._settings.get_value("passwords")[active] != "":
self.password(self._settings.get_value("passwords")[active])
except:
self.emitter.emit("connection_error")
return False
# connect successful
self._main_timeout_id=GLib.timeout_add(self._refresh_interval, self._main_loop)
self.emitter.emit("reconnected")
return True
def reconnect(self):
if self._main_timeout_id is not None:
GLib.source_remove(self._main_timeout_id)
self._main_timeout_id=None
self._last_status={}
self.disconnect()
self.start()
def connected(self):
try:
@@ -761,8 +804,11 @@ class Client(MPDClient):
elif key == "state":
self.emitter.emit("state", val)
elif key == "audio":
# see: https://www.musicpd.org/doc/html/user.html#audio-output-format
samplerate, bits, channels=val.split(':')
self.emitter.emit("audio", float(samplerate), int(bits), int(channels))
if bits == "f":
bits="32fp"
self.emitter.emit("audio", samplerate, bits, channels)
elif key == "volume":
self.emitter.emit("volume_changed", float(val))
elif key == "playlist":
@@ -784,27 +830,13 @@ class Client(MPDClient):
self.disconnect()
self._last_status={}
self.emitter.emit("disconnected")
if self._disconnected_loop():
self._disconnected_timeout_id=GLib.timeout_add(1000, self._disconnected_loop)
self.emitter.emit("connection_error")
self._main_timeout_id=None
return False
return True
def _disconnected_loop(self, *args):
active=self._settings.get_int("active-profile")
try:
self.connect(self._settings.get_value("hosts")[active], self._settings.get_value("ports")[active])
if self._settings.get_value("passwords")[active] != "":
self.password(self._settings.get_value("passwords")[active])
except:
print("connect failed")
return True
# connect successful
self._main_timeout_id=GLib.timeout_add(self._refresh_interval, self._main_loop)
self.emitter.emit("reconnected")
return False
def _on_active_profile_changed(self, *args):
self.disconnect()
self.reconnect()
########################
# gio settings wrapper #
@@ -818,7 +850,14 @@ class Settings(Gio.Settings):
# fix profile settings
if len(self.get_value("profiles")) < (self.get_int("active-profile")+1):
self.set_int("active-profile", 0)
profile_keys=[('as', "profiles", "new profile"), ('as', "hosts", "localhost"), ('ai', "ports", 6600), ('as', "passwords", ""), ('as', "paths", ""), ('as', "regex", "")]
profile_keys=[
('as', "profiles", "new profile"),
('as', "hosts", "localhost"),
('ai', "ports", 6600),
('as', "passwords", ""),
('as', "paths", ""),
('as', "regex", "")
]
profile_arrays=[]
for vtype, key, default in profile_keys:
profile_arrays.append(self.get_value(key).unpack())
@@ -976,7 +1015,11 @@ class SearchWindow(Gtk.Box):
songs=self._client.wrapped_call("search", self._tags.get_active_text(), self._search_entry.get_text())
for s in songs:
song=ClientHelper.extend_song_for_display(ClientHelper.song_to_str_dict(s))
self._store.append([int(song["track"]), song["title"], song["artist"], song["album"], song["human_duration"], song["file"]])
self._store.append([
int(song["track"]), song["title"],
song["artist"], song["album"],
song["human_duration"], song["file"]
])
self._hits_label.set_text(_("%i hits") % (self._songs_view.count()))
if self._songs_view.count() == 0:
self._action_bar.set_sensitive(False)
@@ -1325,8 +1368,8 @@ class ArtistWindow(FocusFrame):
self._treeview.connect("row-activated", self._on_row_activated)
self._settings.connect("changed::use-album-artist", self._refresh)
self._settings.connect("changed::show-initials", self._on_show_initials_changed)
self._client.emitter.connect("disconnected", self._clear)
self._client.emitter.connect("reconnected", self._refresh)
self._client.emitter.connect("disconnected", self._on_disconnected)
self._client.emitter.connect("reconnected", self._on_reconnected)
self._client.emitter.connect("update", self._refresh)
self._genre_select.connect("genre_changed", self._refresh)
@@ -1402,6 +1445,14 @@ class ArtistWindow(FocusFrame):
self._store[path][1]=Pango.Weight.BOLD
self.emit("artists_changed")
def _on_disconnected(self, *args):
self.set_sensitive(False)
self._clear()
def _on_reconnected(self, *args):
self._refresh()
self.set_sensitive(True)
def _on_show_initials_changed(self, *args):
self._column_initials.set_visible(self._settings.get_boolean("show-initials"))
@@ -1438,12 +1489,12 @@ class AlbumWindow(FocusFrame):
self._iconview.connect("button-press-event", self._on_button_press_event)
self._key_press_event=self.connect("key-press-event", self._on_key_press_event)
self._client.emitter.connect("update", self._clear)
self._client.emitter.connect("disconnected", self._clear)
self._client.emitter.connect("disconnected", self._on_disconnected)
self._client.emitter.connect("reconnected", self._on_reconnected)
self._settings.connect("changed::show-album-view-tooltips", self._tooltip_settings)
self._settings.connect("changed::sort-albums-by-year", self._sort_settings)
self._settings.connect("changed::album-cover", self._on_cover_size_changed)
self._settings.connect("changed::use-album-artist", self._clear)
self.connect("done", self._on_done)
self._artist_window.connect("artists_changed", self._refresh)
self.set_widget(self._iconview)
@@ -1503,7 +1554,10 @@ class AlbumWindow(FocusFrame):
def _refresh(self, *args):
def callback():
GLib.idle_add(self._workaround_clear)
try: # self._artist_window could still be empty
genre, artists=self._artist_window.get_selected_artists()
except:
GLib.idle_add(self._done_callback)
# show artist names if all albums are shown
if len(artists) > 1:
self._iconview.set_markup_column(2)
@@ -1515,22 +1569,26 @@ class AlbumWindow(FocusFrame):
for artist in artists:
try: # client cloud meanwhile disconnect
if self.stop_flag:
GLib.idle_add(self.emit, "done")
GLib.idle_add(self._done_callback)
return
else:
if genre is None:
album_candidates=self._client.wrapped_call("comp_list", "album", artist_type, artist)
else:
album_candidates=self._client.wrapped_call("comp_list", "album", artist_type, artist, "genre", genre)
album_candidates=self._client.wrapped_call(
"comp_list", "album", artist_type, artist, "genre", genre
)
for album in album_candidates:
years=self._client.wrapped_call("comp_list", "date", "album", album, artist_type, artist)
for year in years:
songs=self._client.wrapped_call("find", "album", album, "date", year, artist_type, artist)
songs=self._client.wrapped_call(
"find", "album", album, "date", year, artist_type, artist
)
albums.append({"artist": artist, "album": album, "year": year, "songs": songs})
while Gtk.events_pending():
Gtk.main_iteration_do(True)
except MPDBase.ConnectionError:
GLib.idle_add(self.emit, "done")
GLib.idle_add(self._done_callback)
return
# display albums
if self._settings.get_boolean("sort-albums-by-year"):
@@ -1550,9 +1608,11 @@ class AlbumWindow(FocusFrame):
except:
discs=1
if discs > 1:
tooltip=(_("%(total_tracks)i titles on %(discs)i discs (%(total_length)s)") % {"total_tracks": len(album["songs"]), "discs": discs, "total_length": length_human_readable})
tooltip=(_("%(total_tracks)i titles on %(discs)i discs (%(total_length)s)")
%{"total_tracks": len(album["songs"]), "discs": discs, "total_length": length_human_readable})
else:
tooltip=(_("%(total_tracks)i titles (%(total_length)s)") % {"total_tracks": len(album["songs"]), "total_length": length_human_readable})
tooltip=(_("%(total_tracks)i titles (%(total_length)s)")
%{"total_tracks": len(album["songs"]), "total_length": length_human_readable})
# album label
display_label="<b>"+album["album"]+"</b>"
if album["year"] != "":
@@ -1561,12 +1621,14 @@ class AlbumWindow(FocusFrame):
display_label=display_label.replace("&", "&amp;")
display_label_artist=display_label_artist.replace("&", "&amp;")
# add album
GLib.idle_add(self._add_row, [cover, display_label, display_label_artist, tooltip, album["album"], album["year"], album["artist"]])
GLib.idle_add(self._add_row,
[cover, display_label, display_label_artist, tooltip, album["album"], album["year"], album["artist"]]
)
# execute pending events
if i%16 == 0:
while Gtk.events_pending():
Gtk.main_iteration_do(True)
GLib.idle_add(self.emit, "done")
GLib.idle_add(self._done_callback)
if self._done:
self._done=False
callback()
@@ -1589,11 +1651,8 @@ class AlbumWindow(FocusFrame):
album_dialog.open()
album_dialog.destroy()
@GObject.Signal
def done(self):
def _done_callback(self, *args):
self.stop_flag=False
def _on_done(self, *args):
self._done=True
pending=self._pending
self._pending=[]
@@ -1602,6 +1661,7 @@ class AlbumWindow(FocusFrame):
p()
except:
pass
return False
def _on_button_press_event(self, widget, event):
path=widget.get_path_at_pos(int(event.x), int(event.y))
@@ -1642,6 +1702,13 @@ class AlbumWindow(FocusFrame):
selected_artist=self._store.get_value(treeiter, 6)
self._client.wrapped_call("album_to_playlist", selected_album, selected_artist, selected_album_year, "play")
def _on_disconnected(self, *args):
self._iconview.set_sensitive(False)
self._clear()
def _on_reconnected(self, *args):
self._iconview.set_sensitive(True)
def _on_cover_size_changed(self, *args):
def callback():
self._refresh()
@@ -1782,7 +1849,13 @@ class LyricsWindow(Gtk.Overlay):
self._client=client
# text view
text_view=Gtk.TextView(editable=False, cursor_visible=False, wrap_mode=Gtk.WrapMode.WORD, justification=Gtk.Justification.CENTER, opacity=0.9)
text_view=Gtk.TextView(
editable=False,
cursor_visible=False,
wrap_mode=Gtk.WrapMode.WORD,
justification=Gtk.Justification.CENTER,
opacity=0.9
)
text_view.set_left_margin(5)
text_view.set_bottom_margin(5)
text_view.set_top_margin(3)
@@ -1800,11 +1873,11 @@ class LyricsWindow(Gtk.Overlay):
frame.set_widget(text_view)
# close button
close_button=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("window-close-symbolic", Gtk.IconSize.BUTTON))
close_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("window-close-symbolic", Gtk.IconSize.BUTTON))
close_button.set_margin_top(6)
close_button.set_margin_end(6)
close_button.set_halign(2)
close_button.set_valign(1)
close_button.set_halign(Gtk.Align.END)
close_button.set_valign(Gtk.Align.START)
style_context=close_button.get_style_context()
style_context.add_class("circular")
@@ -1831,7 +1904,11 @@ class LyricsWindow(Gtk.Overlay):
GLib.idle_add(self._text_buffer.set_text, text, -1)
def _refresh(self, *args):
update_thread=threading.Thread(target=self._display_lyrics, kwargs={"current_song": ClientHelper.song_to_first_str_dict(self._client.wrapped_call("currentsong"))}, daemon=True)
update_thread=threading.Thread(
target=self._display_lyrics,
kwargs={"current_song": ClientHelper.song_to_first_str_dict(self._client.wrapped_call("currentsong"))},
daemon=True
)
update_thread.start()
def _get_lyrics(self, singer, song): # partially copied from PyLyrics 1.1.0
@@ -1897,11 +1974,15 @@ class AudioType(Gtk.Label):
self.file_type=""
def _refresh(self, *args):
string=_("%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)i bit, %(channels)i channels, %(file_type)s") % {"bitrate": str(self.brate), "frequency": str(self.freq), "resolution": self.res, "channels": self.chan, "file_type": self.file_type}
string=(_("%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)s bit, %(channels)s channels, %(file_type)s")
%{"bitrate": self.brate, "frequency": self.freq, "resolution": self.res, "channels": self.chan, "file_type": self.file_type})
self.set_text(string)
def _on_audio(self, emitter, freq, res, chan):
self.freq=freq/1000
try:
self.freq=str(int(freq)/1000)
except:
self.freq=freq
self.res=res
self.chan=chan
self._refresh()
@@ -2024,10 +2105,18 @@ class PlaylistWindow(Gtk.Box):
css=b"""* {min-height: 8px;}""" # allow further shrinking
provider.load_from_data(css)
self._back_to_current_song_button=Gtk.Button(image=self._icons["go-previous-symbolic"], tooltip_text=_("Scroll to current song"), relief=Gtk.ReliefStyle.NONE)
self._back_to_current_song_button=Gtk.Button(
image=self._icons["go-previous-symbolic"],
tooltip_text=_("Scroll to current song"),
relief=Gtk.ReliefStyle.NONE
)
style_context=self._back_to_current_song_button.get_style_context()
style_context.add_provider(provider, 800)
self._clear_button=Gtk.Button(image=self._icons["edit-clear-symbolic"], tooltip_text=_("Clear playlist"), relief=Gtk.ReliefStyle.NONE)
self._clear_button=Gtk.Button(
image=self._icons["edit-clear-symbolic"],
tooltip_text=_("Clear playlist"),
relief=Gtk.ReliefStyle.NONE
)
style_context=self._clear_button.get_style_context()
style_context.add_class("destructive-action")
style_context.add_provider(provider, 800)
@@ -2143,7 +2232,8 @@ class PlaylistWindow(Gtk.Box):
self._playlist_info.set_text("")
else:
whole_length_human_readable=ClientHelper.calc_display_length(songs)
self._playlist_info.set_text(_("%(total_tracks)i titles (%(total_length)s)") % {"total_tracks": len(songs), "total_length": whole_length_human_readable})
self._playlist_info.set_text(_("%(total_tracks)i titles (%(total_length)s)")
%{"total_tracks": len(songs), "total_length": whole_length_human_readable})
def _scroll_to_selected_title(self, *args):
treeview, treeiter=self._selection.get_selected()
@@ -2219,9 +2309,26 @@ class PlaylistWindow(Gtk.Box):
song=ClientHelper.extend_song_for_display(ClientHelper.song_to_str_dict(s))
try:
treeiter=self._store.get_iter(song["pos"])
self._store.set(treeiter, 0, song["track"], 1, song["disc"], 2, song["title"], 3, song["artist"], 4, song["album"], 5, song["human_duration"], 6, song["date"], 7, song["genre"], 8, song["file"], 9, Pango.Weight.BOOK)
self._store.set(treeiter,
0, song["track"],
1, song["disc"],
2, song["title"],
3, song["artist"],
4, song["album"],
5, song["human_duration"],
6, song["date"],
7, song["genre"],
8, song["file"],
9, Pango.Weight.BOOK
)
except:
self._store.append([song["track"], song["disc"], song["title"], song["artist"], song["album"], song["human_duration"], song["date"], song["genre"], song["file"], Pango.Weight.BOOK])
self._store.append([
song["track"], song["disc"],
song["title"], song["artist"],
song["album"], song["human_duration"],
song["date"], song["genre"],
song["file"], Pango.Weight.BOOK
])
for i in reversed(range(int(self._client.wrapped_call("status")["playlistlength"]), len(self._store))):
treeiter=self._store.get_iter(i)
self._store.remove(treeiter)
@@ -2247,6 +2354,7 @@ class PlaylistWindow(Gtk.Box):
self._client.clear()
def _on_disconnected(self, *args):
self._treeview.set_sensitive(False)
self._clear()
self._back_to_current_song_button.set_sensitive(False)
self._clear_button.set_sensitive(False)
@@ -2254,6 +2362,7 @@ class PlaylistWindow(Gtk.Box):
def _on_reconnected(self, *args):
self._back_to_current_song_button.set_sensitive(True)
self._clear_button.set_sensitive(True)
self._treeview.set_sensitive(True)
def _on_icon_size_changed(self, *args):
pixel_size=self._settings.get_int("icon-size-sec")
@@ -2273,17 +2382,20 @@ class CoverLyricsOSD(Gtk.Overlay):
self._main_cover=MainCover(self._client, self._settings, self._window)
# lyrics button
self._lyrics_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("media-view-subtitles-symbolic", Gtk.IconSize.BUTTON), tooltip_text=_("Show lyrics"))
self._lyrics_button=Gtk.Button(
image=Gtk.Image.new_from_icon_name("media-view-subtitles-symbolic", Gtk.IconSize.BUTTON),
tooltip_text=_("Show lyrics")
)
self._lyrics_button.set_margin_top(6)
self._lyrics_button.set_margin_end(6)
style_context=self._lyrics_button.get_style_context()
style_context.add_class("circular")
# revealer
# workaround to get tooltips in overlay
self._revealer=Gtk.Revealer()
self._revealer.set_halign(2)
self._revealer.set_valign(1)
self._revealer.set_margin_top(6)
self._revealer.set_margin_end(6)
self._revealer.set_halign(Gtk.Align.END)
self._revealer.set_valign(Gtk.Align.START)
self._revealer.add(self._lyrics_button)
# packing
@@ -2306,11 +2418,11 @@ class CoverLyricsOSD(Gtk.Overlay):
self._lyrics_button.set_sensitive(True)
def _on_disconnected(self, *args):
self._lyrics_button.set_sensitive(False)
try:
self._lyrics_win.destroy()
except:
pass
self._lyrics_button.set_sensitive(False)
def _on_lyrics_clicked(self, widget):
self._lyrics_button.set_sensitive(False)
@@ -2365,49 +2477,61 @@ class GeneralSettings(Gtk.Box):
# int_settings
int_settings={}
int_settings_data=[(_("Main cover size:"), (100, 1200, 10), "track-cover"),\
(_("Album view cover size:"), (50, 600, 10), "album-cover"),\
(_("Action bar icon size:"), (16, 64, 2), "icon-size"),\
(_("Secondary icon size:"), (16, 64, 2), "icon-size-sec")]
for data in int_settings_data:
int_settings[data[2]]=(Gtk.Label(label=data[0], xalign=0), Gtk.SpinButton.new_with_range(data[1][0], data[1][1], data[1][2]))
int_settings[data[2]][1].set_value(self._settings.get_int(data[2]))
int_settings[data[2]][1].connect("value-changed", self._on_int_changed, data[2])
self._settings_handlers.append(self._settings.connect("changed::"+data[2], self._on_int_settings_changed, int_settings[data[2]][1]))
int_settings_data=[
(_("Main cover size:"), (100, 1200, 10), "track-cover"),
(_("Album view cover size:"), (50, 600, 10), "album-cover"),
(_("Action bar icon size:"), (16, 64, 2), "icon-size"),
(_("Secondary icon size:"), (16, 64, 2), "icon-size-sec")
]
for label, (vmin, vmax, step), key in int_settings_data:
int_settings[key]=(Gtk.Label(label=label, xalign=0), Gtk.SpinButton.new_with_range(vmin, vmax, step))
int_settings[key][1].set_value(self._settings.get_int(key))
int_settings[key][1].connect("value-changed", self._on_int_changed, key)
self._settings_handlers.append(
self._settings.connect("changed::"+key, self._on_int_settings_changed, int_settings[key][1])
)
# combo_settings
combo_settings={}
combo_settings_data=[(_("Sort albums by:"), _("name"), _("year"), "sort-albums-by-year"), \
(_("Position of playlist:"), _("bottom"), _("right"), "playlist-right")]
for data in combo_settings_data:
combo_settings[data[3]]=(Gtk.Label(label=data[0], xalign=0), Gtk.ComboBoxText(entry_text_column=0))
combo_settings[data[3]][1].append_text(data[1])
combo_settings[data[3]][1].append_text(data[2])
if self._settings.get_boolean(data[3]):
combo_settings[data[3]][1].set_active(1)
combo_settings_data=[
(_("Sort albums by:"), _("name"), _("year"), "sort-albums-by-year"),
(_("Position of playlist:"), _("bottom"), _("right"), "playlist-right")
]
for label, vfalse, vtrue, key in combo_settings_data:
combo_settings[key]=(Gtk.Label(label=label, xalign=0), Gtk.ComboBoxText(entry_text_column=0))
combo_settings[key][1].append_text(vfalse)
combo_settings[key][1].append_text(vtrue)
if self._settings.get_boolean(key):
combo_settings[key][1].set_active(1)
else:
combo_settings[data[3]][1].set_active(0)
combo_settings[data[3]][1].connect("changed", self._on_combo_changed, data[3])
self._settings_handlers.append(self._settings.connect("changed::"+data[3], self._on_combo_settings_changed, combo_settings[data[3]][1]))
combo_settings[key][1].set_active(0)
combo_settings[key][1].connect("changed", self._on_combo_changed, key)
self._settings_handlers.append(
self._settings.connect("changed::"+key, self._on_combo_settings_changed, combo_settings[key][1])
)
# check buttons
check_buttons={}
check_buttons_data=[(_("Use Client-side decoration"), "use-csd"), \
(_("Show stop button"), "show-stop"), \
(_("Show lyrics button"), "show-lyrics-button"), \
(_("Show initials in artist view"), "show-initials"), \
(_("Show tooltips in album view"), "show-album-view-tooltips"), \
(_("Use 'Album Artist' tag"), "use-album-artist"), \
(_("Send notification on title change"), "send-notify"), \
(_("Stop playback on quit"), "stop-on-quit"), \
(_("Play selected albums and titles immediately"), "force-mode")]
check_buttons_data=[
(_("Use Client-side decoration"), "use-csd"),
(_("Show stop button"), "show-stop"),
(_("Show lyrics button"), "show-lyrics-button"),
(_("Show initials in artist view"), "show-initials"),
(_("Show tooltips in album view"), "show-album-view-tooltips"),
(_("Use 'Album Artist' tag"), "use-album-artist"),
(_("Send notification on title change"), "send-notify"),
(_("Stop playback on quit"), "stop-on-quit"),
(_("Play selected albums and titles immediately"), "force-mode")
]
for data in check_buttons_data:
check_buttons[data[1]]=Gtk.CheckButton(label=data[0])
check_buttons[data[1]].set_active(self._settings.get_boolean(data[1]))
check_buttons[data[1]].set_margin_start(12)
check_buttons[data[1]].connect("toggled", self._on_toggled, data[1])
self._settings_handlers.append(self._settings.connect("changed::"+data[1], self._on_check_settings_changed, check_buttons[data[1]]))
for label, key in check_buttons_data:
check_buttons[key]=Gtk.CheckButton(label=label)
check_buttons[key].set_active(self._settings.get_boolean(key))
check_buttons[key].set_margin_start(12)
check_buttons[key].connect("toggled", self._on_toggled, key)
self._settings_handlers.append(
self._settings.connect("changed::"+key, self._on_check_settings_changed, check_buttons[key])
)
# headings
view_heading=Gtk.Label(label=_("<b>View</b>"), use_markup=True, xalign=0)
@@ -2431,7 +2555,11 @@ class GeneralSettings(Gtk.Box):
behavior_grid=Gtk.Grid(row_spacing=6, column_spacing=12)
behavior_grid.set_margin_start(12)
behavior_grid.add(combo_settings["sort-albums-by-year"][0])
behavior_grid.attach_next_to(combo_settings["sort-albums-by-year"][1], combo_settings["sort-albums-by-year"][0], Gtk.PositionType.RIGHT, 1, 1)
behavior_grid.attach_next_to(
combo_settings["sort-albums-by-year"][1],
combo_settings["sort-albums-by-year"][0],
Gtk.PositionType.RIGHT, 1, 1
)
# connect
self.connect("destroy", self._remove_handlers)
@@ -2484,22 +2612,25 @@ class GeneralSettings(Gtk.Box):
self._settings.set_boolean(key, widget.get_active())
class ProfileSettings(Gtk.Grid):
def __init__(self, parent, settings):
def __init__(self, parent, client, settings):
super().__init__(row_spacing=6, column_spacing=12, border_width=18)
# adding vars
self._client=client
self._settings=settings
self._gui_modification=False # indicates whether the settings were changed from the settings dialog
# widgets
self._profiles_combo=Gtk.ComboBoxText(entry_text_column=0, hexpand=True)
add_button=Gtk.Button(label=None, image=Gtk.Image(stock=Gtk.STOCK_ADD))
delete_button=Gtk.Button(label=None, image=Gtk.Image(stock=Gtk.STOCK_DELETE))
add_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("list-add", Gtk.IconSize.BUTTON))
delete_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("list-remove", Gtk.IconSize.BUTTON))
add_delete_buttons=Gtk.ButtonBox(layout_style=Gtk.ButtonBoxStyle.EXPAND)
add_delete_buttons.pack_start(add_button, True, True, 0)
add_delete_buttons.pack_start(delete_button, True, True, 0)
connect_button=Gtk.Button(label=_("Connect"), image=Gtk.Image.new_from_icon_name("system-run", Gtk.IconSize.BUTTON))
self._profile_entry=Gtk.Entry(hexpand=True)
self._host_entry=Gtk.Entry(hexpand=True)
self._port_entry=Gtk.SpinButton.new_with_range(0, 65535, 1)
@@ -2508,12 +2639,16 @@ class ProfileSettings(Gtk.Grid):
address_entry.pack_start(self._port_entry, False, False, 0)
self._password_entry=Gtk.Entry(hexpand=True, visibility=False)
self._path_entry=Gtk.Entry(hexpand=True)
self._path_select_button=Gtk.Button(image=Gtk.Image(stock=Gtk.STOCK_OPEN))
self._path_select_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("folder-open", Gtk.IconSize.BUTTON))
path_box=Gtk.Box(spacing=6)
path_box.pack_start(self._path_entry, True, True, 0)
path_box.pack_start(self._path_select_button, False, False, 0)
self._regex_entry=Gtk.Entry(hexpand=True, placeholder_text=COVER_REGEX)
self._regex_entry.set_tooltip_text(_("The first image in the same directory as the song file matching this regex will be displayed. %AlbumArtist% and %Album% will be replaced by the corresponding tags of the song."))
self._regex_entry.set_tooltip_text(
_("The first image in the same directory as the song file "\
"matching this regex will be displayed. %AlbumArtist% and "\
"%Album% will be replaced by the corresponding tags of the song.")
)
profiles_label=Gtk.Label(label=_("Profile:"), xalign=1)
profile_label=Gtk.Label(label=_("Name:"), xalign=1)
@@ -2525,6 +2660,7 @@ class ProfileSettings(Gtk.Grid):
# connect
add_button.connect("clicked", self._on_add_button_clicked)
delete_button.connect("clicked", self._on_delete_button_clicked)
connect_button.connect("clicked", self._on_connect_button_clicked)
self._path_select_button.connect("clicked", self._on_path_select_button_clicked, parent)
self._profiles_combo.connect("changed", self._on_profiles_changed)
self.entry_changed_handlers=[]
@@ -2560,6 +2696,8 @@ class ProfileSettings(Gtk.Grid):
self.attach_next_to(self._password_entry, password_label, Gtk.PositionType.RIGHT, 2, 1)
self.attach_next_to(path_box, path_label, Gtk.PositionType.RIGHT, 2, 1)
self.attach_next_to(self._regex_entry, regex_label, Gtk.PositionType.RIGHT, 2, 1)
connect_button.set_margin_top(12)
self.attach_next_to(connect_button, self._regex_entry, Gtk.PositionType.BOTTOM, 2, 1)
def _block_entry_changed_handlers(self, *args):
for obj, handler in self.entry_changed_handlers:
@@ -2616,6 +2754,10 @@ class ProfileSettings(Gtk.Grid):
new_pos=max(pos-1,0)
self._profiles_combo.set_active(new_pos)
def _on_connect_button_clicked(self, *args):
self._settings.set_int("active-profile", self._profiles_combo.get_active())
self._client.reconnect()
def _on_profile_entry_changed(self, *args):
self._gui_modification=True
pos=self._profiles_combo.get_active()
@@ -2814,7 +2956,7 @@ class PlaylistSettings(Gtk.Box):
self._store.handler_unblock(self._row_deleted)
class SettingsDialog(Gtk.Dialog):
def __init__(self, parent, settings):
def __init__(self, parent, client, settings):
use_csd=settings.get_boolean("use-csd")
if use_csd:
super().__init__(title=_("Settings"), transient_for=parent, use_header_bar=True)
@@ -2831,7 +2973,7 @@ class SettingsDialog(Gtk.Dialog):
# widgets
general=GeneralSettings(settings)
profiles=ProfileSettings(parent, settings)
profiles=ProfileSettings(parent, client, settings)
playlist=PlaylistSettings(settings)
# packing
@@ -2860,8 +3002,11 @@ class PlaybackControl(Gtk.ButtonBox):
# widgets
self._icons={}
icons_data=["media-playback-start-symbolic", "media-playback-stop-symbolic", "media-playback-pause-symbolic", \
"media-skip-backward-symbolic", "media-skip-forward-symbolic"]
icons_data=[
"media-playback-start-symbolic", "media-playback-stop-symbolic",
"media-playback-pause-symbolic", "media-skip-backward-symbolic",
"media-skip-forward-symbolic"
]
for data in icons_data:
self._icons[data]=PixelSizedIcon(data, self._icon_size)
@@ -3013,6 +3158,7 @@ class SeekBar(Gtk.Box):
def _disable(self, *args):
self.set_sensitive(False)
self.scale.set_fill_level(0)
self.scale.set_range(0, 0)
self._elapsed.set_text("00:00")
self._rest.set_text("-00:00")
@@ -3254,7 +3400,7 @@ class AboutDialog(Gtk.AboutDialog):
super().__init__(transient_for=window, modal=True)
self.set_program_name("mpdevil")
self.set_version(VERSION)
self.set_comments(_("A small MPD client written in python"))
self.set_comments(_("A simple music browser for MPD"))
self.set_authors(["Martin Wagner"])
self.set_website("https://github.com/SoongNoonien/mpdevil")
self.set_copyright("\xa9 2020 Martin Wagner")
@@ -3279,6 +3425,7 @@ class ProfileSelect(Gtk.ComboBoxText):
self._settings.connect("changed::ports", self._refresh)
self._settings.connect("changed::passwords", self._refresh)
self._settings.connect("changed::paths", self._refresh)
self._settings.connect("changed::active-profile", self._on_active_profile_changed)
self._refresh()
@@ -3294,6 +3441,57 @@ class ProfileSelect(Gtk.ComboBoxText):
active=self.get_active()
self._settings.set_int("active-profile", active)
def _on_active_profile_changed(self, *args):
self.handler_block(self._changed)
self.set_active(self._settings.get_int("active-profile"))
self.handler_unblock(self._changed)
class ConnectionNotify(Gtk.Revealer):
def __init__(self, client, settings):
super().__init__(valign=Gtk.Align.START, halign=Gtk.Align.CENTER)
# adding vars
self._client=client
self._settings=settings
# widgets
self._label=Gtk.Label(wrap=True)
close_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("window-close-symbolic", Gtk.IconSize.BUTTON))
close_button.set_relief(Gtk.ReliefStyle.NONE)
connect_button=Gtk.Button(label=_("Connect"))
# connect
close_button.connect("clicked", self._on_close_button_clicked)
connect_button.connect("clicked", self._on_connect_button_clicked)
self._client.emitter.connect("connection_error", self._on_connection_error)
self._client.emitter.connect("reconnected", self._on_reconnected)
# packing
box=Gtk.Box(spacing=12)
box.get_style_context().add_class("app-notification")
box.pack_start(self._label, False, True, 6)
box.pack_end(close_button, False, True, 0)
box.pack_end(connect_button, False, True, 0)
self.add(box)
def _on_connection_error(self, *args):
active=self._settings.get_int("active-profile")
profile=self._settings.get_value("profiles")[active]
host=self._settings.get_value("hosts")[active]
port=self._settings.get_value("ports")[active]
string=_('Connection to "%(profile)s" (%(host)s:%(port)s) failed') % {"profile": profile, "host": host, "port": port}
self._label.set_text(string)
self.set_reveal_child(True)
def _on_reconnected(self, *args):
self.set_reveal_child(False)
def _on_close_button_clicked(self, *args):
self.set_reveal_child(False)
def _on_connect_button_clicked(self, *args):
self._client.reconnect()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, app, client, settings):
super().__init__(title=("mpdevil"), icon_name="mpdevil", application=app)
@@ -3310,8 +3508,7 @@ class MainWindow(Gtk.ApplicationWindow):
self._icon_size=self._settings.get_int("icon-size")
# MPRIS
DBusGMainLoop(set_as_default=True)
self._dbus_service=MPRISInterface(self, self._client, self._settings)
dbus_service=MPRISInterface(self, self._client, self._settings)
# actions
save_action=Gio.SimpleAction.new("save", None)
@@ -3322,9 +3519,9 @@ class MainWindow(Gtk.ApplicationWindow):
settings_action.connect("activate", self._on_settings)
self.add_action(settings_action)
stats_action=Gio.SimpleAction.new("stats", None)
stats_action.connect("activate", self._on_stats)
self.add_action(stats_action)
self._stats_action=Gio.SimpleAction.new("stats", None)
self._stats_action.connect("activate", self._on_stats)
self.add_action(self._stats_action)
self._update_action=Gio.SimpleAction.new("update", None)
self._update_action.connect("activate", self._on_update)
@@ -3346,6 +3543,7 @@ class MainWindow(Gtk.ApplicationWindow):
self._playback_control=PlaybackControl(self._client, self._settings)
self._seek_bar=SeekBar(self._client)
playback_options=PlaybackOptions(self._client, self._settings)
connection_notify=ConnectionNotify(self._client, self._settings)
# menu
subsection=Gio.Menu()
@@ -3354,10 +3552,13 @@ class MainWindow(Gtk.ApplicationWindow):
subsection.append(_("About"), "app.about")
subsection.append(_("Quit"), "app.quit")
mpd_subsection=Gio.Menu()
mpd_subsection.append(_("Update database"), "win.update")
mpd_subsection.append(_("Server stats"), "win.stats")
menu=Gio.Menu()
menu.append(_("Save window layout"), "win.save")
menu.append(_("Update database"), "win.update")
menu.append(_("Server stats"), "win.stats")
menu.append_section(None, mpd_subsection)
menu.append_section(None, subsection)
menu_button=Gtk.MenuButton(image=self._icons["open-menu-symbolic"], tooltip_text=_("Menu"))
@@ -3393,6 +3594,9 @@ class MainWindow(Gtk.ApplicationWindow):
vbox=Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
vbox.pack_start(self._paned2, True, True, 0)
vbox.pack_start(action_bar, False, False, 0)
overlay=Gtk.Overlay()
overlay.add(vbox)
overlay.add_overlay(connection_notify)
if self._use_csd:
self._header_bar=Gtk.HeaderBar()
@@ -3408,7 +3612,7 @@ class MainWindow(Gtk.ApplicationWindow):
action_bar.pack_start(self._profile_select)
action_bar.pack_start(menu_button)
self.add(vbox)
self.add(overlay)
self.show_all()
if self._settings.get_boolean("maximize"):
@@ -3443,18 +3647,17 @@ class MainWindow(Gtk.ApplicationWindow):
notify.show()
def _on_reconnected(self, *args):
self._dbus_service.acquire_name()
self._playback_control.set_sensitive(True)
self._update_action.set_enabled(True)
self._stats_action.set_enabled(True)
def _on_disconnected(self, *args):
self._dbus_service.release_name()
if self._use_csd:
self.set_title("mpdevil")
self._header_bar.set_subtitle(_("not connected"))
else:
self.set_title("mpdevil ("+_("not connected")+")")
self.songid_playing=None
if self._use_csd:
self._header_bar.set_subtitle("")
self._playback_control.set_sensitive(False)
self._update_action.set_enabled(False)
self._stats_action.set_enabled(False)
def _on_key_press_event(self, widget, event):
ctrl = (event.state & Gdk.ModifierType.CONTROL_MASK)
@@ -3507,17 +3710,15 @@ class MainWindow(Gtk.ApplicationWindow):
self._settings.set_int("paned2", self._paned2.get_position())
def _on_settings(self, action, param):
settings=SettingsDialog(self, self._settings)
settings=SettingsDialog(self, self._client, self._settings)
settings.run()
settings.destroy()
def _on_stats(self, action, param):
if self._client.connected():
stats=ServerStats(self, self._client, self._settings)
stats.destroy()
def _on_update(self, action, param):
if self._client.connected():
self._client.wrapped_call("update")
def _on_help(self, action, param):

View File

@@ -1,11 +1,11 @@
[Desktop Entry]
Version=1.0
_Name=mpdevil
_GenericName=MPD Client
_Comment=A small MPD client written in python
Name=mpdevil
GenericName=MPD Client
_Comment=A simple music browser for MPD
Exec=mpdevil
Icon=mpdevil
Terminal=false
Type=Application
StartupNotify=true
Categories=GTK;AudioVideo;Player;
Categories=Audio;AudioVideo;Player;GTK

View File

@@ -1,3 +1,4 @@
[encoding: UTF-8]
bin/mpdevil
data/mpdevil.desktop.in

198
po/de.po
View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-29 10:57+0200\n"
"PO-Revision-Date: 2020-08-29 10:57+0200\n"
"POT-Creation-Date: 2020-09-13 21:30+0200\n"
"PO-Revision-Date: 2020-09-13 21:32+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de\n"
@@ -18,72 +18,72 @@ msgstr ""
"X-Generator: Poedit 2.3.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: mpdevil:515
#: mpdevil:518
msgid "MPD-Tag"
msgstr "MPD-Tag"
#: mpdevil:519
#: mpdevil:522
msgid "Value"
msgstr "Wert"
#: mpdevil:601
#: mpdevil:605
msgid "Unknown Title"
msgstr "Unbekannter Titel"
#: mpdevil:601
#: mpdevil:608
msgid "Unknown Artist"
msgstr "Unbekannter Interpret"
#: mpdevil:601
#: mpdevil:609
msgid "Unknown Album"
msgstr "Unbekanntes Album"
#: mpdevil:906 mpdevil:1209 mpdevil:2084 mpdevil:2771
#: mpdevil:936 mpdevil:1240 mpdevil:2141 mpdevil:2846
msgid "No"
msgstr "Nr."
#: mpdevil:911 mpdevil:1214 mpdevil:2086 mpdevil:2771
#: mpdevil:941 mpdevil:1245 mpdevil:2143 mpdevil:2846
msgid "Title"
msgstr "Titel"
#: mpdevil:917 mpdevil:1393 mpdevil:2087 mpdevil:2771
#: mpdevil:947 mpdevil:1422 mpdevil:2144 mpdevil:2846
msgid "Artist"
msgstr "Interpret"
#: mpdevil:923 mpdevil:2088 mpdevil:2771
#: mpdevil:953 mpdevil:2145 mpdevil:2846
msgid "Album"
msgstr "Album"
#: mpdevil:929 mpdevil:1220 mpdevil:2089 mpdevil:2771
#: mpdevil:959 mpdevil:1251 mpdevil:2146 mpdevil:2846
msgid "Length"
msgstr "Länge"
#: mpdevil:990
#: mpdevil:1023
#, python-format
msgid "%i hits"
msgstr "%i Treffer"
#: mpdevil:1092
#: mpdevil:1124
msgid "Append"
msgstr "Anhängen"
#: mpdevil:1093
#: mpdevil:1125
msgid "Add all titles to playlist"
msgstr "Alle Titel der Wiedergabeliste anhängen"
#: mpdevil:1094
#: mpdevil:1126
msgid "Play"
msgstr "Abspielen"
#: mpdevil:1095
#: mpdevil:1127
msgid "Directly play all titles"
msgstr "Alle Titel sofort abspielen"
#: mpdevil:1096
#: mpdevil:1128
msgid "Enqueue"
msgstr "Einreihen"
#: mpdevil:1097
#: mpdevil:1129
msgid ""
"Append all titles after the currently playing track and clear the playlist "
"from all other songs"
@@ -91,170 +91,174 @@ msgstr ""
"Alle Titel hinter dem aktuellen Stück einreihen und die weitere "
"Wiedergabeliste leeren"
#: mpdevil:1226
#: mpdevil:1257
msgid "Close"
msgstr "Schließen"
#: mpdevil:1279
#: mpdevil:1310
msgid "all genres"
msgstr "Alle Genres"
#: mpdevil:1391
#: mpdevil:1420
msgid "Album Artist"
msgstr "Albuminterpret"
#: mpdevil:1394
#: mpdevil:1423
msgid "all artists"
msgstr "Alle Interpreten"
#: mpdevil:1571
#: mpdevil:1611
#, python-format
msgid "%(total_tracks)i titles on %(discs)i discs (%(total_length)s)"
msgstr "%(total_tracks)i Titel auf %(discs)i CDs (%(total_length)s)"
#: mpdevil:1573 mpdevil:2180
#: mpdevil:1614 mpdevil:2235
#, python-format
msgid "%(total_tracks)i titles (%(total_length)s)"
msgstr "%(total_tracks)i Titel (%(total_length)s)"
#: mpdevil:1691
#: mpdevil:1738
msgid "Back to current album"
msgstr "Zurück zu aktuellem Album"
#: mpdevil:1693
#: mpdevil:1739
msgid "Search"
msgstr "Suche"
#: mpdevil:1853
#: mpdevil:1899
msgid "searching..."
msgstr "suche..."
#: mpdevil:1857
#: mpdevil:1903
msgid "lyrics not found"
msgstr "Liedtext nicht gefunden"
#: mpdevil:1927
#: mpdevil:1977
#, python-format
msgid ""
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)i bit, %(channels)i "
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)s bit, %(channels)s "
"channels, %(file_type)s"
msgstr ""
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)i bit, %(channels)i "
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)s bit, %(channels)s "
"Kanäle, %(file_type)s"
#: mpdevil:2055
#: mpdevil:2110
msgid "Scroll to current song"
msgstr "Gehe zu aktuellem Lied"
#: mpdevil:2060
#: mpdevil:2117
msgid "Clear playlist"
msgstr "Wiedergabeliste leeren"
#: mpdevil:2085 mpdevil:2771
#: mpdevil:2142 mpdevil:2846
msgid "Disc"
msgstr "CD"
#: mpdevil:2090 mpdevil:2771
#: mpdevil:2147 mpdevil:2846
msgid "Year"
msgstr "Jahr"
#: mpdevil:2091 mpdevil:2771
#: mpdevil:2148 mpdevil:2846
msgid "Genre"
msgstr "Genre"
#: mpdevil:2311
#: mpdevil:2387
msgid "Show lyrics"
msgstr "Zeige Liedtext"
#: mpdevil:2404
#: mpdevil:2481
msgid "Main cover size:"
msgstr "Größe des Haupt-Covers:"
#: mpdevil:2405
#: mpdevil:2482
msgid "Album view cover size:"
msgstr "Covergröße in Albumliste:"
#: mpdevil:2406
#: mpdevil:2483
msgid "Action bar icon size:"
msgstr "Symbolgröße Aktionsleiste:"
#: mpdevil:2407
#: mpdevil:2484
msgid "Secondary icon size:"
msgstr "Sekundäre Symbolgröße:"
#: mpdevil:2418
#: mpdevil:2497
msgid "Sort albums by:"
msgstr "Sortiere Alben nach:"
#: mpdevil:2418
#: mpdevil:2497
msgid "name"
msgstr "Name"
#: mpdevil:2418
#: mpdevil:2497
msgid "year"
msgstr "Jahr"
#: mpdevil:2419
#: mpdevil:2498
msgid "Position of playlist:"
msgstr "Wiedergabelistenposition:"
#: mpdevil:2419
#: mpdevil:2498
msgid "bottom"
msgstr "unten"
#: mpdevil:2419
#: mpdevil:2498
msgid "right"
msgstr "rechts"
#: mpdevil:2436
#: mpdevil:2516
msgid "Use Client-side decoration"
msgstr "Benutze \"Client-side decoration\""
#: mpdevil:2437
#: mpdevil:2517
msgid "Show stop button"
msgstr "Zeige Stopp-Knopf"
#: mpdevil:2438
#: mpdevil:2518
msgid "Show lyrics button"
msgstr "Zeige Liedtext-Knopf"
#: mpdevil:2439
#: mpdevil:2519
msgid "Show initials in artist view"
msgstr "Zeige Anfangsbuchstaben in Interpretenliste"
#: mpdevil:2440
#: mpdevil:2520
msgid "Show tooltips in album view"
msgstr "Zeige Tooltips in Albumliste"
#: mpdevil:2441
#: mpdevil:2521
msgid "Use 'Album Artist' tag"
msgstr "Benutze \"Album Artist\" Tag"
#: mpdevil:2442
#: mpdevil:2522
msgid "Send notification on title change"
msgstr "Sende Benachrichtigung bei Titelwechsel"
#: mpdevil:2443
#: mpdevil:2523
msgid "Stop playback on quit"
msgstr "Wiedergabe beim Beenden stoppen"
#: mpdevil:2444
#: mpdevil:2524
msgid "Play selected albums and titles immediately"
msgstr "Ausgewählte Alben und Titel sofort abspielen"
#: mpdevil:2455
#: mpdevil:2537
msgid "<b>View</b>"
msgstr "<b>Ansicht</b>"
#: mpdevil:2458
#: mpdevil:2538
msgid "<b>Behavior</b>"
msgstr "<b>Verhalten</b>"
#: mpdevil:2491
#: mpdevil:2570
msgid "(restart required)"
msgstr "(Neustart erforderlich)"
#: mpdevil:2572
#: mpdevil:2632 mpdevil:3461
msgid "Connect"
msgstr "Verbinden"
#: mpdevil:2648
msgid ""
"The first image in the same directory as the song file matching this regex "
"will be displayed. %AlbumArtist% and %Album% will be replaced by the "
@@ -264,115 +268,119 @@ msgstr ""
"regulären Ausdruck entspricht, wird angezeigt. %AlbumArtist% und %Album% "
"werden durch die entsprechenden Tags des Liedes ersetzt."
#: mpdevil:2574
#: mpdevil:2653
msgid "Profile:"
msgstr "Profil:"
#: mpdevil:2576
#: mpdevil:2654
msgid "Name:"
msgstr "Name:"
#: mpdevil:2578
#: mpdevil:2655
msgid "Host:"
msgstr "Host:"
#: mpdevil:2580
#: mpdevil:2656
msgid "Password:"
msgstr "Passwort:"
#: mpdevil:2582
#: mpdevil:2657
msgid "Music lib:"
msgstr "Musikverzeichnis:"
#: mpdevil:2584
#: mpdevil:2658
msgid "Cover regex:"
msgstr "Cover-Regex:"
#: mpdevil:2709
#: mpdevil:2789
msgid "Choose directory"
msgstr "Verzeichnis Wählen"
#: mpdevil:2743
#: mpdevil:2822
msgid "Choose the order of information to appear in the playlist:"
msgstr ""
"Lege die Reihenfolge fest, in der Informationen in der Wiedergabeliste "
"angezeigt werden sollen:"
#: mpdevil:2887 mpdevil:2895 mpdevil:3434
#: mpdevil:2962 mpdevil:2970 mpdevil:3550
msgid "Settings"
msgstr "Einstellungen"
#: mpdevil:2906
#: mpdevil:2981
msgid "General"
msgstr "Allgemein"
#: mpdevil:2907
#: mpdevil:2982
msgid "Profiles"
msgstr "Profile"
#: mpdevil:2908
#: mpdevil:2983
msgid "Playlist"
msgstr "Wiedergabeliste"
#: mpdevil:3168
#: mpdevil:3242
msgid "Random mode"
msgstr "Zufallsmodus"
#: mpdevil:3170
#: mpdevil:3243
msgid "Repeat mode"
msgstr "Dauerschleife"
#: mpdevil:3172
#: mpdevil:3244
msgid "Single mode"
msgstr "Einzelstückmodus"
#: mpdevil:3174
#: mpdevil:3245
msgid "Consume mode"
msgstr "Wiedergabeliste verbrauchen"
#: mpdevil:3278 mpdevil:3286
#: mpdevil:3346 mpdevil:3354
msgid "Stats"
msgstr "Statistik"
#: mpdevil:3337
msgid "A small MPD client written in python"
msgstr ""
#: mpdevil:3403
msgid "A simple music browser for MPD"
msgstr "Ein einfacher Musikbrowser für MPD"
#: mpdevil:3427
#: mpdevil:3415
msgid "Select profile"
msgstr "Profil auswählen"
#: mpdevil:3435
#: mpdevil:3482
#, python-format
msgid "Connection to \"%(profile)s\" (%(host)s:%(port)s) failed"
msgstr "Verbindung zu \"%(profile)s\" (%(host)s:%(port)s) fehlgeschlagen"
#: mpdevil:3551
msgid "Help"
msgstr "Hilfe"
#: mpdevil:3436
#: mpdevil:3552
msgid "About"
msgstr "Über"
#: mpdevil:3437
#: mpdevil:3553
msgid "Quit"
msgstr "Beenden"
#: mpdevil:3440
msgid "Save window layout"
msgstr "Fensterlayout speichern"
#: mpdevil:3441
#: mpdevil:3556
msgid "Update database"
msgstr "Datenbank aktualisieren"
#: mpdevil:3442
#: mpdevil:3557
msgid "Server stats"
msgstr "Serverstatistik"
#: mpdevil:3448
#: mpdevil:3560
msgid "Save window layout"
msgstr "Fensterlayout speichern"
#: mpdevil:3564
msgid "Menu"
msgstr "Menü"
#: mpdevil:3537 mpdevil:3539
msgid "not connected"
msgstr "nicht verbunden"
#~ msgid "not connected"
#~ msgstr "nicht verbunden"
#, python-format
#~ msgid "hits: %i"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-08-29 10:57+0200\n"
"POT-Creation-Date: 2020-09-13 21:30+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,349 +17,354 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: mpdevil:515
#: mpdevil:518
msgid "MPD-Tag"
msgstr ""
#: mpdevil:519
#: mpdevil:522
msgid "Value"
msgstr ""
#: mpdevil:601
#: mpdevil:605
msgid "Unknown Title"
msgstr ""
#: mpdevil:601
#: mpdevil:608
msgid "Unknown Artist"
msgstr ""
#: mpdevil:601
#: mpdevil:609
msgid "Unknown Album"
msgstr ""
#: mpdevil:906 mpdevil:1209 mpdevil:2084 mpdevil:2771
#: mpdevil:936 mpdevil:1240 mpdevil:2141 mpdevil:2846
msgid "No"
msgstr ""
#: mpdevil:911 mpdevil:1214 mpdevil:2086 mpdevil:2771
#: mpdevil:941 mpdevil:1245 mpdevil:2143 mpdevil:2846
msgid "Title"
msgstr ""
#: mpdevil:917 mpdevil:1393 mpdevil:2087 mpdevil:2771
#: mpdevil:947 mpdevil:1422 mpdevil:2144 mpdevil:2846
msgid "Artist"
msgstr ""
#: mpdevil:923 mpdevil:2088 mpdevil:2771
#: mpdevil:953 mpdevil:2145 mpdevil:2846
msgid "Album"
msgstr ""
#: mpdevil:929 mpdevil:1220 mpdevil:2089 mpdevil:2771
#: mpdevil:959 mpdevil:1251 mpdevil:2146 mpdevil:2846
msgid "Length"
msgstr ""
#: mpdevil:990
#: mpdevil:1023
#, python-format
msgid "%i hits"
msgstr ""
#: mpdevil:1092
#: mpdevil:1124
msgid "Append"
msgstr ""
#: mpdevil:1093
#: mpdevil:1125
msgid "Add all titles to playlist"
msgstr ""
#: mpdevil:1094
#: mpdevil:1126
msgid "Play"
msgstr ""
#: mpdevil:1095
#: mpdevil:1127
msgid "Directly play all titles"
msgstr ""
#: mpdevil:1096
#: mpdevil:1128
msgid "Enqueue"
msgstr ""
#: mpdevil:1097
#: mpdevil:1129
msgid ""
"Append all titles after the currently playing track and clear the playlist "
"from all other songs"
msgstr ""
#: mpdevil:1226
#: mpdevil:1257
msgid "Close"
msgstr ""
#: mpdevil:1279
#: mpdevil:1310
msgid "all genres"
msgstr ""
#: mpdevil:1391
#: mpdevil:1420
msgid "Album Artist"
msgstr ""
#: mpdevil:1394
#: mpdevil:1423
msgid "all artists"
msgstr ""
#: mpdevil:1571
#: mpdevil:1611
#, python-format
msgid "%(total_tracks)i titles on %(discs)i discs (%(total_length)s)"
msgstr ""
#: mpdevil:1573 mpdevil:2180
#: mpdevil:1614 mpdevil:2235
#, python-format
msgid "%(total_tracks)i titles (%(total_length)s)"
msgstr ""
#: mpdevil:1691
#: mpdevil:1738
msgid "Back to current album"
msgstr ""
#: mpdevil:1693
#: mpdevil:1739
msgid "Search"
msgstr ""
#: mpdevil:1853
#: mpdevil:1899
msgid "searching..."
msgstr ""
#: mpdevil:1857
#: mpdevil:1903
msgid "lyrics not found"
msgstr ""
#: mpdevil:1927
#: mpdevil:1977
#, python-format
msgid ""
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)i bit, %(channels)i "
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)s bit, %(channels)s "
"channels, %(file_type)s"
msgstr ""
#: mpdevil:2055
#: mpdevil:2110
msgid "Scroll to current song"
msgstr ""
#: mpdevil:2060
#: mpdevil:2117
msgid "Clear playlist"
msgstr ""
#: mpdevil:2085 mpdevil:2771
#: mpdevil:2142 mpdevil:2846
msgid "Disc"
msgstr ""
#: mpdevil:2090 mpdevil:2771
#: mpdevil:2147 mpdevil:2846
msgid "Year"
msgstr ""
#: mpdevil:2091 mpdevil:2771
#: mpdevil:2148 mpdevil:2846
msgid "Genre"
msgstr ""
#: mpdevil:2311
#: mpdevil:2387
msgid "Show lyrics"
msgstr ""
#: mpdevil:2404
#: mpdevil:2481
msgid "Main cover size:"
msgstr ""
#: mpdevil:2405
#: mpdevil:2482
msgid "Album view cover size:"
msgstr ""
#: mpdevil:2406
#: mpdevil:2483
msgid "Action bar icon size:"
msgstr ""
#: mpdevil:2407
#: mpdevil:2484
msgid "Secondary icon size:"
msgstr ""
#: mpdevil:2418
#: mpdevil:2497
msgid "Sort albums by:"
msgstr ""
#: mpdevil:2418
#: mpdevil:2497
msgid "name"
msgstr ""
#: mpdevil:2418
#: mpdevil:2497
msgid "year"
msgstr ""
#: mpdevil:2419
#: mpdevil:2498
msgid "Position of playlist:"
msgstr ""
#: mpdevil:2419
#: mpdevil:2498
msgid "bottom"
msgstr ""
#: mpdevil:2419
#: mpdevil:2498
msgid "right"
msgstr ""
#: mpdevil:2436
#: mpdevil:2516
msgid "Use Client-side decoration"
msgstr ""
#: mpdevil:2437
#: mpdevil:2517
msgid "Show stop button"
msgstr ""
#: mpdevil:2438
#: mpdevil:2518
msgid "Show lyrics button"
msgstr ""
#: mpdevil:2439
#: mpdevil:2519
msgid "Show initials in artist view"
msgstr ""
#: mpdevil:2440
#: mpdevil:2520
msgid "Show tooltips in album view"
msgstr ""
#: mpdevil:2441
#: mpdevil:2521
msgid "Use 'Album Artist' tag"
msgstr ""
#: mpdevil:2442
#: mpdevil:2522
msgid "Send notification on title change"
msgstr ""
#: mpdevil:2443
#: mpdevil:2523
msgid "Stop playback on quit"
msgstr ""
#: mpdevil:2444
#: mpdevil:2524
msgid "Play selected albums and titles immediately"
msgstr ""
#: mpdevil:2455
#: mpdevil:2537
msgid "<b>View</b>"
msgstr ""
#: mpdevil:2458
#: mpdevil:2538
msgid "<b>Behavior</b>"
msgstr ""
#: mpdevil:2491
#: mpdevil:2570
msgid "(restart required)"
msgstr ""
#: mpdevil:2572
#: mpdevil:2632 mpdevil:3461
msgid "Connect"
msgstr ""
#: mpdevil:2648
msgid ""
"The first image in the same directory as the song file matching this regex "
"will be displayed. %AlbumArtist% and %Album% will be replaced by the "
"corresponding tags of the song."
msgstr ""
#: mpdevil:2574
#: mpdevil:2653
msgid "Profile:"
msgstr ""
#: mpdevil:2576
#: mpdevil:2654
msgid "Name:"
msgstr ""
#: mpdevil:2578
#: mpdevil:2655
msgid "Host:"
msgstr ""
#: mpdevil:2580
#: mpdevil:2656
msgid "Password:"
msgstr ""
#: mpdevil:2582
#: mpdevil:2657
msgid "Music lib:"
msgstr ""
#: mpdevil:2584
#: mpdevil:2658
msgid "Cover regex:"
msgstr ""
#: mpdevil:2709
#: mpdevil:2789
msgid "Choose directory"
msgstr ""
#: mpdevil:2743
#: mpdevil:2822
msgid "Choose the order of information to appear in the playlist:"
msgstr ""
#: mpdevil:2887 mpdevil:2895 mpdevil:3434
#: mpdevil:2962 mpdevil:2970 mpdevil:3550
msgid "Settings"
msgstr ""
#: mpdevil:2906
#: mpdevil:2981
msgid "General"
msgstr ""
#: mpdevil:2907
#: mpdevil:2982
msgid "Profiles"
msgstr ""
#: mpdevil:2908
#: mpdevil:2983
msgid "Playlist"
msgstr ""
#: mpdevil:3168
#: mpdevil:3242
msgid "Random mode"
msgstr ""
#: mpdevil:3170
#: mpdevil:3243
msgid "Repeat mode"
msgstr ""
#: mpdevil:3172
#: mpdevil:3244
msgid "Single mode"
msgstr ""
#: mpdevil:3174
#: mpdevil:3245
msgid "Consume mode"
msgstr ""
#: mpdevil:3278 mpdevil:3286
#: mpdevil:3346 mpdevil:3354
msgid "Stats"
msgstr ""
#: mpdevil:3337
msgid "A small MPD client written in python"
#: mpdevil:3403
msgid "A simple music browser for MPD"
msgstr ""
#: mpdevil:3427
#: mpdevil:3415
msgid "Select profile"
msgstr ""
#: mpdevil:3435
#: mpdevil:3482
#, python-format
msgid "Connection to \"%(profile)s\" (%(host)s:%(port)s) failed"
msgstr ""
#: mpdevil:3551
msgid "Help"
msgstr ""
#: mpdevil:3436
#: mpdevil:3552
msgid "About"
msgstr ""
#: mpdevil:3437
#: mpdevil:3553
msgid "Quit"
msgstr ""
#: mpdevil:3440
msgid "Save window layout"
msgstr ""
#: mpdevil:3441
#: mpdevil:3556
msgid "Update database"
msgstr ""
#: mpdevil:3442
#: mpdevil:3557
msgid "Server stats"
msgstr ""
#: mpdevil:3448
msgid "Menu"
#: mpdevil:3560
msgid "Save window layout"
msgstr ""
#: mpdevil:3537 mpdevil:3539
msgid "not connected"
#: mpdevil:3564
msgid "Menu"
msgstr ""

View File

@@ -4,10 +4,10 @@ import DistUtilsExtra.auto
DistUtilsExtra.auto.setup(
name='mpdevil',
version='0.9.0', # sync with bin/mpdevil
version='0.9.1', # sync with bin/mpdevil
author="Martin Wagner",
author_email="martin.wagner.dev@gmail.com",
description=('A small MPD client written in python'),
description=('A simple music browser for MPD'),
url="https://github.com/SoongNoonien/mpdevil",
license='GPL-3.0',
data_files=[