27 Commits
v0.3 ... v0.4.2

Author SHA1 Message Date
Martin Wagner
7037628af9 added icon size setting 2020-01-28 19:59:14 +01:00
Martin Wagner
26ee51fb18 reworked Gio.Settings handling 2020-01-28 18:39:18 +01:00
Martin Wagner
9625bd9cf3 reworked search dialog 2020-01-27 21:03:13 +01:00
Martin Wagner
75d8ff21c6 added playlist length information 2020-01-27 20:27:35 +01:00
Martin Wagner
36b023f04c connect to server after showing window 2020-01-26 23:51:50 +01:00
Martin Wagner
177bd27aa5 reworked connection 2020-01-26 22:23:21 +01:00
Martin Wagner
e07cce7ea6 fixed some play inconsistencies 2020-01-26 21:32:12 +01:00
Martin Wagner
0332fe75b7 fixed shebang 2020-01-19 20:48:49 +01:00
Martin Wagner
e62f4824c2 gui improvements 2020-01-19 00:00:40 +01:00
Martin Wagner
b9b1ba989a reworked album query dialog 2020-01-18 16:23:52 +01:00
Martin Wagner
f1831a5569 new screenshot 2020-01-18 10:28:53 +01:00
Martin Wagner
b904907f6f Delete mainwindow.png 2020-01-18 10:26:35 +01:00
Martin Wagner
54673a9840 new screenshot 2020-01-18 10:23:57 +01:00
Martin Wagner
89ffc03cb2 Update configure.ac 2020-01-17 22:43:33 +01:00
Martin Wagner
d04c84e5d0 readme update 2020-01-17 22:39:56 +01:00
Martin Wagner
6990d59f72 translation update 2020-01-17 22:35:30 +01:00
Martin Wagner
421f685b58 search dialog fix 2020-01-17 22:18:50 +01:00
Martin Wagner
6238df4d21 added new album dialog / tooltip fixes 2020-01-17 22:13:58 +01:00
Martin Wagner
b8d1f9aafc fixed unintended behavior on stopped state 2020-01-14 20:24:28 +01:00
Martin Wagner
3414212173 converted to symbolic icons 2020-01-14 17:18:49 +01:00
Martin Wagner
ab7c9c6bd6 fixed duration display error and small translation update 2020-01-12 16:27:23 +01:00
Martin Wagner
4c7f953c98 Update README.md 2020-01-12 15:32:02 +01:00
Martin Wagner
8ba986bb4d Update configure.ac
added dependency tests
2020-01-12 13:57:18 +01:00
Martin Wagner
0230544df4 Update README.md 2020-01-11 22:29:19 +01:00
Martin Wagner
0b08bd80bf updated READMEs 2020-01-11 15:09:10 +01:00
Martin Wagner
b786b55644 added screenshot 2020-01-11 14:29:37 +01:00
Martin Wagner
6f608d923b Update README.rst 2020-01-11 14:02:45 +01:00
9 changed files with 499 additions and 297 deletions

2
README
View File

@@ -1 +1 @@
README.rst
README.md

33
README.md Normal file
View File

@@ -0,0 +1,33 @@
README for mpdevil
==================
mpdevil is focused on playing your local music directly instead of managing playlists or playing network streams. So it neither supports saving playlists nor restoring them. Therefore mpdevil is mainly a music browser which aims to be easy to use. mpdevil dosen't store any client side database of your music library. Instead all tags and covers get presented to you in real time. So you'll never see any outdated information in your browser. mpdevil strongly relies on tags especially on the AlbumArtist tag.
![ScreenShot](screenshots/mainwindow.png)
Features
--------
1. playing songs without doubleclicking
2. displaying covers
3. fetching lyrics form the web (based on PyLyrics 1.1.0)
4. searching songs in your music library
5. removing single tracks form playlist by hovering and pressing del
6. appending albums by middleclick
7. query albums by rightclick
8. sending notifications on title change
9. managing multiple mpd servers
TODO
----
1. MPRIS interface
2. connecting to mpd servers with password
Building and installation
-------------------------
To build from source, use::
./autogen.sh
make
make install

View File

@@ -1,30 +0,0 @@
README for mpdevil
==================
mpdevil is focused on playing your local music directly instead of managing playlists or playing network streams. So it neither supports saving playlists nor restoring them. Therefore mpdevil is mainly a music browser which aims to be easy to use. mpdevil dosen't store any client side database of your music library. Instead all tags and covers get presented to you in real time. So you'll never see any outdated information in your browser.
Features
--------
-playing songs without doubleclicking
-displaying covers
-fetching lyrics form the web (based on PyLyrics 1.1.0)
-searching songs in your music library
-removing single tracks form playlist by hovering and pressing del
-sending notifications on title change
-managing multiple mpd servers
Building and installation
-------------------------
To build from source, use::
./autogen.sh
make
make install

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# mpdevil - MPD Client.
@@ -20,7 +20,7 @@
import gi #python-gobject dev-python/pygobject:3[${PYTHON_USEDEP}]
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, Gdk, GdkPixbuf, GObject, GLib
from gi.repository import Gtk, Gio, Gdk, GdkPixbuf, Pango, GObject, GLib
from mpd import MPDClient
import requests #dev-python/requests
from bs4 import BeautifulSoup, Comment #, NavigableString #dev-python/beautifulsoup
@@ -79,8 +79,16 @@ class Cover(object):
return GdkPixbuf.Pixbuf.new_from_file_at_size(self.path, size, size)
class Client(MPDClient):
def __init__(self):
def __init__(self, settings):
MPDClient.__init__(self)
self.settings = settings
def try_connect_default(self):
active=self.settings.get_int("active-profile")
try:
self.connect(self.settings.get_value("hosts")[active], self.settings.get_value("ports")[active])
except:
pass
def connected(self):
try:
@@ -89,6 +97,136 @@ class Client(MPDClient):
except:
return False
class Settings(Gio.Settings):
BASE_KEY = "org.mpdevil"
def __init__(self):
super().__init__(schema=self.BASE_KEY)
def array_append(self, vtype, key, value): #append to Gio.Settings (self.settings) array
array=self.get_value(key).unpack()
array.append(value)
self.set_value(key, GLib.Variant(vtype, array))
def array_delete(self, vtype, key, pos): #delete entry of Gio.Settings (self.settings) array
array=self.get_value(key).unpack()
array.pop(pos)
self.set_value(key, GLib.Variant(vtype, array))
def array_modify(self, vtype, key, pos, value): #modify entry of Gio.Settings (self.settings) array
array=self.get_value(key).unpack()
array[pos]=value
self.set_value(key, GLib.Variant(vtype, array))
def get_gtk_icon_size(self, key):
icon_size=self.get_int(key)
if icon_size == 16:
return Gtk.IconSize.BUTTON
elif icon_size == 32:
return Gtk.IconSize.DND
elif icon_size == 48:
return Gtk.IconSize.DIALOG
else:
raise ValueError
class AlbumDialog(Gtk.Dialog):
def __init__(self, parent, client, album, artist, year):
Gtk.Dialog.__init__(self, title=(artist+" - "+album+" ("+year+")"), transient_for=parent)
self.add_buttons(Gtk.STOCK_ADD, Gtk.ResponseType.ACCEPT, Gtk.STOCK_MEDIA_PLAY, Gtk.ResponseType.YES, Gtk.STOCK_OPEN, Gtk.ResponseType.OK, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
self.set_default_size(800, 600)
#adding vars
self.client=client
#scroll
scroll=Gtk.ScrolledWindow()
scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
#Store
#(track, title, artist, duration, file)
self.store = Gtk.ListStore(str, str, str, str, str)
#TreeView
self.treeview = Gtk.TreeView(model=self.store)
self.treeview.set_search_column(-1)
self.treeview.columns_autosize()
self.selection = self.treeview.get_selection()
self.selection.set_mode(Gtk.SelectionMode.SINGLE)
#Column
renderer_text = Gtk.CellRendererText()
self.column_track = Gtk.TreeViewColumn(_("No"), renderer_text, text=0)
self.column_track.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_track.set_property("resizable", False)
self.treeview.append_column(self.column_track)
self.column_title = Gtk.TreeViewColumn(_("Title"), renderer_text, text=1)
self.column_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_title.set_property("resizable", False)
self.treeview.append_column(self.column_title)
self.column_artist = Gtk.TreeViewColumn(_("Artist"), renderer_text, text=2)
self.column_artist.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_artist.set_property("resizable", False)
self.treeview.append_column(self.column_artist)
self.column_time = Gtk.TreeViewColumn(_("Length"), renderer_text, text=3)
self.column_time.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_time.set_property("resizable", False)
self.treeview.append_column(self.column_time)
self.populate_treeview(album, artist, year)
#connect
self.title_activated=self.treeview.connect("row-activated", self.on_row_activated)
#packing
scroll.add(self.treeview)
self.vbox.pack_start(scroll, True, True, 0) #vbox default widget of dialogs
self.vbox.set_spacing(6)
self.show_all()
#selection workaround
self.selection.unselect_all()
self.title_change=self.selection.connect("changed", self.on_selection_change)
def on_row_activated(self, widget, path, view_column):
treeiter=self.store.get_iter(path)
selected_title=self.store.get_value(treeiter, 4)
self.client.clear()
self.client.add(selected_title)
self.client.play()
def on_selection_change(self, widget):
treeiter=widget.get_selected()[1]
if not treeiter == None:
selected_title=self.store.get_value(treeiter, 4)
self.client.add(selected_title)
def populate_treeview(self, album, artist, year):
songs=self.client.find("album", album, "date", year, "albumartist", artist)
if not songs == []:
for song in songs:
try:
title=song["title"]
except:
title=_("Unknown Title")
try:
artist=song["artist"]
except:
artist=_("Unknown Artist")
try:
track=song["track"].zfill(2)
except:
track="00"
try:
dura=float(song["duration"])
except:
dura=0.0
duration=str(datetime.timedelta(seconds=int(dura)))
self.store.append([track, title, artist, duration, song["file"]] )
class ArtistView(Gtk.ScrolledWindow):
def __init__(self, client):
Gtk.ScrolledWindow.__init__(self)
@@ -113,7 +251,7 @@ class ArtistView(Gtk.ScrolledWindow):
#Old Name Column
renderer_text = Gtk.CellRendererText()
self.column_name = Gtk.TreeViewColumn(_("Artist"), renderer_text, text=0)
self.column_name = Gtk.TreeViewColumn(_("Album Artist"), renderer_text, text=0)
self.column_name.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_name.set_property("resizable", True)
self.column_name.set_sort_column_id(0)
@@ -161,28 +299,19 @@ class AlbumView(Gtk.ScrolledWindow):
self.add(self.iconview)
def gen_title_list(self, album, artist, year): #needed for tooltips
def gen_tooltip(self, album, artist, year):
if self.settings.get_boolean("show-album-view-tooltips"):
songs=self.client.find("album", album, "date", year, "albumartist", artist)
length=float(0)
title_list=""
for song in songs:
try:
title=song["title"]
dura=float(song["duration"])
except:
title=_("Unknown Title")
try:
track=song["track"].zfill(2)
except:
track="00"
length=length+float(song["duration"])
duration=str(datetime.timedelta(seconds=int(float(song["duration"]))))
title_list=title_list+"\n"+(track+" - "+title+" ("+duration+")")
if not year == "":
year=" ("+year+")"
dura=0.0
length=length+dura
length_human_readable=str(datetime.timedelta(seconds=int(length)))
title_list=(_("%(album)s%(year)s (tracks: %(total_tracks)i) (%(total_length)s):") % {"album": album, "year": year, "total_tracks": len(songs), "total_length": length_human_readable})+title_list
return title_list.replace("&", "") #& must not be in tooltips
tooltip=(_("%(total_tracks)i titles (%(total_length)s)") % {"total_tracks": len(songs), "total_length": length_human_readable})
return tooltip
else:
return None
@@ -202,9 +331,9 @@ class AlbumView(Gtk.ScrolledWindow):
cover=Cover(client=self.client, lib_path=self.settings.get_value("paths")[self.settings.get_int("active-profile")], song_file=song_file)
img=cover.get_pixbuf(size)
if album["year"] == "":
self.store.append([img, album["album"], self.gen_title_list(album["album"], artist, album["year"]), album["album"], album["year"]])
self.store.append([img, album["album"], self.gen_tooltip(album["album"], artist, album["year"]), album["album"], album["year"]])
else:
self.store.append([img, album["album"]+" ("+album["year"]+")", self.gen_title_list(album["album"], artist, album["year"]), album["album"], album["year"]])
self.store.append([img, album["album"]+" ("+album["year"]+")", self.gen_tooltip(album["album"], artist, album["year"]), album["album"], album["year"]])
while Gtk.events_pending():
Gtk.main_iteration_do(True)
@@ -227,7 +356,6 @@ class TrackView(Gtk.Box):
#TreeView
self.treeview = Gtk.TreeView(model=self.store)
self.treeview.set_search_column(-1)
self.treeview.set_tooltip_column(5)
self.treeview.columns_autosize()
#selection
@@ -269,6 +397,16 @@ class TrackView(Gtk.Box):
#audio infos
audio=AudioType(self.client)
#playlist info
self.playlist_info=Gtk.Label()
self.playlist_info.set_xalign(0)
self.playlist_info.set_ellipsize(Pango.EllipsizeMode.END)
#status bar
status_bar=Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
status_bar.pack_start(self.playlist_info, True, True, 0)
status_bar.pack_end(audio, False, False, 0)
#timeouts
GLib.timeout_add(1000, self.update_cover)
GLib.timeout_add(100, self.refresh)
@@ -283,7 +421,7 @@ class TrackView(Gtk.Box):
#packing
self.pack_start(self.cover, False, False, 0)
self.pack_start(scroll, True, True, 0)
self.pack_end(audio, False, False, 0)
self.pack_end(status_bar, False, False, 0)
def update_cover(self):
try:
@@ -302,14 +440,11 @@ class TrackView(Gtk.Box):
for song in songs:
self.client.add(song["file"])
else:
if self.settings.get_boolean("add-album") and not force:
if self.settings.get_boolean("add-album") and not force and not self.client.status()["state"] == "stop":
self.selection.handler_block(self.title_change)
status=self.client.status()
try:
self.client.moveid(status["songid"], 0) #bad song index possible
self.song_to_delete=self.client.playlistinfo()[0]["file"]
except:
pass
self.client.moveid(status["songid"], 0)
self.song_to_delete=self.client.playlistinfo()[0]["file"]
self.selection.handler_unblock(self.title_change)
try:
self.client.delete((1,)) # delete all songs, but the first. #bad song index possible
@@ -330,15 +465,17 @@ class TrackView(Gtk.Box):
self.client.clear()
for song in songs:
self.client.add(song["file"])
self.client.play(0)
self.client.play()
def refresh(self):
self.selection.handler_block(self.title_change)
if self.client.connected():
if self.client.playlist() != self.playlist:
self.playlist_info.set_text("")
self.store.clear()
songs=self.client.playlistinfo()
if not songs == []:
whole_length=float(0)
for song in songs:
try:
title=song["title"]
@@ -356,8 +493,15 @@ class TrackView(Gtk.Box):
album=song["album"]
except:
album=_("Unknown Album")
duration=str(datetime.timedelta(seconds=int(float(song["duration"]))))
try:
dura=float(song["duration"])
except:
dura=0.0
whole_length=whole_length+dura
duration=str(datetime.timedelta(seconds=int(dura )))
self.store.append([track, title, artist, album, duration, song["file"].replace("&", "")])
whole_length_human_readable=str(datetime.timedelta(seconds=int(whole_length)))
self.playlist_info.set_text(_("%(total_tracks)i titles (%(total_length)s)") % {"total_tracks": len(songs), "total_length": whole_length_human_readable})
self.playlist=self.client.playlist()
else:
if not self.song_to_delete == "":
@@ -373,8 +517,9 @@ class TrackView(Gtk.Box):
path = Gtk.TreePath(int(song))
self.selection.select_path(path)
except:
self.selection.select_path(Gtk.TreePath(0))
self.selection.unselect_all()
else:
self.playlist_info.set_text("")
self.store.clear()
self.playlist=[]
self.selection.handler_unblock(self.title_change)
@@ -417,12 +562,13 @@ class TrackView(Gtk.Box):
self.client.play(selected_title)
class Browser(Gtk.Box):
def __init__(self, client, settings):
def __init__(self, client, settings, window):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=3)
#adding vars
self.client=client
self.settings=settings
self.window=window
#widgets
self.artist_list=ArtistView(self.client)
@@ -502,14 +648,26 @@ class Browser(Gtk.Box):
def on_album_view_button_press_event(self, widget, event):
path = widget.get_path_at_pos(int(event.x), int(event.y))
if event.button == 3:
if not path == None:
if not path == None:
if not event.button == 1:
treeiter=self.album_list.store.get_iter(path)
selected_album=self.album_list.store.get_value(treeiter, 3)
selected_album_year=self.album_list.store.get_value(treeiter, 4)
treeiter=self.artist_list.selection.get_selected()[1]
selected_artist=self.artist_list.store.get_value(treeiter, 0)
if event.button == 2:
self.title_list.album_to_playlist(selected_album, selected_artist, selected_album_year, True)
elif event.button == 3:
if self.client.connected():
album = AlbumDialog(self.window, self.client, selected_album, selected_artist, selected_album_year)
response = album.run()
if response == Gtk.ResponseType.OK:
self.album_list.iconview.select_path(path)
elif response == Gtk.ResponseType.ACCEPT:
self.title_list.album_to_playlist(selected_album, selected_artist, selected_album_year, True)
elif response == Gtk.ResponseType.YES:
self.title_list.album_to_playlist(selected_album, selected_artist, selected_album_year, False, True)
album.destroy()
def on_album_selection_change(self, widget):
paths=widget.get_selected_items()
@@ -603,21 +761,6 @@ class ProfileSettings(Gtk.Grid):
self.attach_next_to(self.port_entry, port_label, Gtk.PositionType.RIGHT, 1, 1)
self.attach_next_to(self.path_select_button, path_label, Gtk.PositionType.RIGHT, 1, 1)
def settings_array_append(self, vtype, key, value): #append to Gio.Settings (self.settings) array
array=self.settings.get_value(key).unpack()
array.append(value)
self.settings.set_value(key, GLib.Variant(vtype, array))
def settings_array_delete(self, vtype, key, pos): #delete entry of Gio.Settings (self.settings) array
array=self.settings.get_value(key).unpack()
array.pop(pos)
self.settings.set_value(key, GLib.Variant(vtype, array))
def settings_array_modify(self, vtype, key, pos, value): #modify entry of Gio.Settings (self.settings) array
array=self.settings.get_value(key).unpack()
array[pos]=value
self.settings.set_value(key, GLib.Variant(vtype, array))
def profiles_combo_reload(self, *args):
self.profiles_combo.handler_block(self.profiles_combo_changed)
self.profile_entry.handler_block(self.profile_entry_changed)
@@ -635,33 +778,33 @@ class ProfileSettings(Gtk.Grid):
def on_add_button_clicked(self, *args):
pos=self.profiles_combo.get_active()
self.settings_array_append('as', "profiles", "new profile")
self.settings_array_append('as', "hosts", "localhost")
self.settings_array_append('ai', "ports", 6600)
self.settings_array_append('as', "paths", "")
self.settings.array_append('as', "profiles", "new profile")
self.settings.array_append('as', "hosts", "localhost")
self.settings.array_append('ai', "ports", 6600)
self.settings.array_append('as', "paths", "")
self.profiles_combo_reload()
self.profiles_combo.set_active(pos)
def on_delete_button_clicked(self, *args):
pos=self.profiles_combo.get_active()
self.settings_array_delete('as', "profiles", pos)
self.settings_array_delete('as', "hosts", pos)
self.settings_array_delete('ai', "ports", pos)
self.settings_array_delete('as', "paths", pos)
self.settings.array_delete('as', "profiles", pos)
self.settings.array_delete('as', "hosts", pos)
self.settings.array_delete('ai', "ports", pos)
self.settings.array_delete('as', "paths", pos)
self.profiles_combo_reload()
self.profiles_combo.set_active(0)
def on_profile_entry_changed(self, *args):
pos=self.profiles_combo.get_active()
self.settings_array_modify('as', "profiles", pos, self.profile_entry.get_text())
self.settings.array_modify('as', "profiles", pos, self.profile_entry.get_text())
self.profiles_combo_reload()
self.profiles_combo.set_active(pos)
def on_host_entry_changed(self, *args):
self.settings_array_modify('as', "hosts", self.profiles_combo.get_active(), self.host_entry.get_text())
self.settings.array_modify('as', "hosts", self.profiles_combo.get_active(), self.host_entry.get_text())
def on_port_entry_changed(self, *args):
self.settings_array_modify('ai', "ports", self.profiles_combo.get_active(), self.port_entry.get_int())
self.settings.array_modify('ai', "ports", self.profiles_combo.get_active(), self.port_entry.get_int())
def on_path_select_button_clicked(self, widget, parent):
dialog = Gtk.FileChooserDialog(title=_("Choose directory"), transient_for=parent, action=Gtk.FileChooserAction.SELECT_FOLDER)
@@ -671,7 +814,7 @@ class ProfileSettings(Gtk.Grid):
dialog.set_current_folder(self.settings.get_value("paths")[self.profiles_combo.get_active()])
response = dialog.run()
if response == Gtk.ResponseType.OK:
self.settings_array_modify('as', "paths", self.profiles_combo.get_active(), dialog.get_filename())
self.settings.array_modify('as', "paths", self.profiles_combo.get_active(), dialog.get_filename())
dialog.destroy()
def on_profiles_changed(self, *args):
@@ -708,10 +851,19 @@ class GeneralSettings(Gtk.Grid):
track_cover_size=IntEntry(self.settings.get_int("track-cover"), 100, 1200)
album_cover_size=IntEntry(self.settings.get_int("album-cover"), 50, 600)
icon_size_label=Gtk.Label(label=_("Button icon size (restart required):"))
icon_size_label.set_xalign(1)
icon_size_combo=Gtk.ComboBoxText()
icon_size_combo.set_entry_text_column(0)
sizes=[16, 32, 48]
for i in sizes:
icon_size_combo.append_text(str(i))
icon_size_combo.set_active(sizes.index(self.settings.get_int("icon-size")))
show_stop=Gtk.CheckButton(label=_("Show stop button"))
show_stop.set_active(self.settings.get_boolean("show-stop"))
show_album_view_tooltips=Gtk.CheckButton(label=_("Show title list as tooltip in album view"))
show_album_view_tooltips=Gtk.CheckButton(label=_("Show tooltips in album view"))
show_album_view_tooltips.set_active(self.settings.get_boolean("show-album-view-tooltips"))
send_notify=Gtk.CheckButton(label=_("Send notification on title change"))
@@ -726,6 +878,7 @@ class GeneralSettings(Gtk.Grid):
#connect
track_cover_size.connect("value-changed", self.on_int_changed, "track-cover")
album_cover_size.connect("value-changed", self.on_int_changed, "album-cover")
icon_size_combo.connect("changed", self.on_icon_size_changed)
show_stop.connect("toggled", self.on_toggled, "show-stop")
show_album_view_tooltips.connect("toggled", self.on_toggled, "show-album-view-tooltips")
send_notify.connect("toggled", self.on_toggled, "send-notify")
@@ -735,9 +888,11 @@ class GeneralSettings(Gtk.Grid):
#packing
self.add(track_cover_label)
self.attach_next_to(album_cover_label, track_cover_label, Gtk.PositionType.BOTTOM, 1, 1)
self.attach_next_to(icon_size_label, album_cover_label, Gtk.PositionType.BOTTOM, 1, 1)
self.attach_next_to(track_cover_size, track_cover_label, Gtk.PositionType.RIGHT, 1, 1)
self.attach_next_to(album_cover_size, album_cover_label, Gtk.PositionType.RIGHT, 1, 1)
self.attach_next_to(show_stop, album_cover_label, Gtk.PositionType.BOTTOM, 2, 1)
self.attach_next_to(icon_size_combo, icon_size_label, Gtk.PositionType.RIGHT, 1, 1)
self.attach_next_to(show_stop, icon_size_label, Gtk.PositionType.BOTTOM, 2, 1)
self.attach_next_to(show_album_view_tooltips, show_stop, Gtk.PositionType.BOTTOM, 2, 1)
self.attach_next_to(send_notify, show_album_view_tooltips, Gtk.PositionType.BOTTOM, 2, 1)
self.attach_next_to(add_album, send_notify, Gtk.PositionType.BOTTOM, 2, 1)
@@ -749,6 +904,10 @@ class GeneralSettings(Gtk.Grid):
def on_toggled(self, widget, key):
self.settings.set_boolean(key, widget.get_active())
def on_icon_size_changed(self, box):
active_size=int(box.get_active_text())
self.settings.set_int("icon-size", active_size)
class SettingsDialog(Gtk.Dialog):
def __init__(self, parent, settings):
Gtk.Dialog.__init__(self, title=_("Settings"), transient_for=parent)
@@ -767,6 +926,7 @@ class SettingsDialog(Gtk.Dialog):
tabs.append_page(general, Gtk.Label(label=_("General")))
tabs.append_page(profiles, Gtk.Label(label=_("Profiles")))
self.vbox.pack_start(tabs, True, True, 0) #vbox default widget of dialogs
self.vbox.set_spacing(6)
self.show_all()
@@ -777,12 +937,13 @@ class ClientControl(Gtk.ButtonBox):
#adding vars
self.client=client
self.settings=settings
self.icon_size=self.settings.get_gtk_icon_size("icon-size")
#widgets
self.play_button = Gtk.Button(image=Gtk.Image.new_from_icon_name("media-playback-start", Gtk.IconSize.DND))
self.stop_button = Gtk.Button(image=Gtk.Image.new_from_icon_name("media-playback-stop", Gtk.IconSize.DND))
self.prev_button = Gtk.Button(image=Gtk.Image.new_from_icon_name("media-skip-backward", Gtk.IconSize.DND))
self.next_button = Gtk.Button(image=Gtk.Image.new_from_icon_name("media-skip-forward", Gtk.IconSize.DND))
self.play_button = Gtk.Button(image=Gtk.Image.new_from_icon_name("media-playback-start-symbolic", self.icon_size))
self.stop_button = Gtk.Button(image=Gtk.Image.new_from_icon_name("media-playback-stop-symbolic", self.icon_size))
self.prev_button = Gtk.Button(image=Gtk.Image.new_from_icon_name("media-skip-backward-symbolic", self.icon_size))
self.next_button = Gtk.Button(image=Gtk.Image.new_from_icon_name("media-skip-forward-symbolic", self.icon_size))
#connect
self.play_button.connect("clicked", self.on_play_clicked)
@@ -805,15 +966,15 @@ class ClientControl(Gtk.ButtonBox):
if self.client.connected():
status=self.client.status()
if status["state"] == "play":
self.play_button.set_image(Gtk.Image.new_from_icon_name("media-playback-pause", Gtk.IconSize.DND))
self.play_button.set_image(Gtk.Image.new_from_icon_name("media-playback-pause-symbolic", self.icon_size))
self.prev_button.set_sensitive(True)
self.next_button.set_sensitive(True)
elif status["state"] == "pause":
self.play_button.set_image(Gtk.Image.new_from_icon_name("media-playback-start", Gtk.IconSize.DND))
self.play_button.set_image(Gtk.Image.new_from_icon_name("media-playback-start-symbolic", self.icon_size))
self.prev_button.set_sensitive(True)
self.next_button.set_sensitive(True)
else:
self.play_button.set_image(Gtk.Image.new_from_icon_name("media-playback-start", Gtk.IconSize.DND))
self.play_button.set_image(Gtk.Image.new_from_icon_name("media-playback-start-symbolic", self.icon_size))
self.prev_button.set_sensitive(False)
self.next_button.set_sensitive(False)
return True
@@ -830,7 +991,7 @@ class ClientControl(Gtk.ButtonBox):
self.client.play(status["song"])
except:
try:
self.client.play(0) #bad song index possible
self.client.play()
except:
pass
self.update()
@@ -865,7 +1026,9 @@ class SeekBar(Gtk.Box):
#widgets
self.elapsed=Gtk.Label()
self.elapsed.set_width_chars(7)
self.rest=Gtk.Label()
self.rest.set_width_chars(8)
self.scale=Gtk.Scale.new_with_range(orientation=Gtk.Orientation.HORIZONTAL, min=0, max=100, step=0.001)
self.scale.set_draw_value(False)
@@ -882,13 +1045,10 @@ class SeekBar(Gtk.Box):
def seek(self, range, scroll, value):
status=self.client.status()
try:
duration=float(status["duration"])
factor=(value/100)
pos=(duration*factor)
self.client.seekcur(pos)
except:
pass
duration=float(status["duration"])
factor=(value/100)
pos=(duration*factor)
self.client.seekcur(pos)
def update(self):
try:
@@ -908,22 +1068,25 @@ class SeekBar(Gtk.Box):
return True
class PlaybackOptions(Gtk.Box):
def __init__(self, client):
def __init__(self, client, settings):
Gtk.Box.__init__(self)
#adding vars
self.client=client
self.settings=settings
self.icon_size=self.settings.get_gtk_icon_size("icon-size")
#widgets
self.random=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("media-playlist-shuffle-symbolic", Gtk.IconSize.DND))
self.random=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("media-playlist-shuffle-symbolic", self.icon_size))
self.random.set_tooltip_text(_("Random mode"))
self.repeat=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("media-playlist-repeat-symbolic", Gtk.IconSize.DND))
self.repeat=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("media-playlist-repeat-symbolic", self.icon_size))
self.repeat.set_tooltip_text(_("Repeat mode"))
self.single=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("zoom-original-symbolic", Gtk.IconSize.DND))
self.single=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("zoom-original-symbolic", self.icon_size))
self.single.set_tooltip_text(_("Single mode"))
self.consume=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("edit-cut-symbolic", Gtk.IconSize.DND))
self.consume=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("edit-cut-symbolic", self.icon_size))
self.consume.set_tooltip_text(_("Consume mode"))
self.volume=Gtk.VolumeButton()
self.volume.set_property("size", self.icon_size)
#connect
self.random_toggled=self.random.connect("toggled", self.set_random)
@@ -1024,6 +1187,7 @@ class AudioType(Gtk.EventBox):
#widgets
self.label=Gtk.Label()
self.label.set_xalign(1)
self.label.set_ellipsize(Pango.EllipsizeMode.END)
self.popover=Gtk.Popover()
#Store
@@ -1098,16 +1262,18 @@ class ProfileSelect(Gtk.ComboBoxText):
self.client=client
self.settings=settings
#connect
self.changed=self.connect("changed", self.on_changed)
self.reload()
self.set_active(self.settings.get_int("active-profile"))
self.settings.connect("changed::profiles", self.on_settings_changed)
self.settings.connect("changed::hosts", self.on_settings_changed)
self.settings.connect("changed::ports", self.on_settings_changed)
self.settings.connect("changed::paths", self.on_settings_changed)
self.reload()
self.handler_block(self.changed)
self.set_active(self.settings.get_int("active-profile"))
self.handler_unblock(self.changed)
def reload(self, *args):
self.handler_block(self.changed)
self.remove_all()
@@ -1121,11 +1287,8 @@ class ProfileSelect(Gtk.ComboBoxText):
def on_changed(self, *args):
active=self.get_active()
self.settings.set_int("active-profile", active)
try:
self.client.disconnect()
self.client.connect(self.settings.get_value("hosts")[active], self.settings.get_value("ports")[active])
except:
pass
self.client.disconnect()
self.client.try_connect_default()
class ServerStats(Gtk.Dialog):
def __init__(self, parent, client):
@@ -1168,7 +1331,7 @@ class ServerStats(Gtk.Dialog):
class Search(Gtk.Dialog):
def __init__(self, parent, client):
Gtk.Dialog.__init__(self, title=_("Search"), transient_for=parent)
self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
self.set_default_size(800, 600)
#adding vars
@@ -1180,7 +1343,6 @@ class Search(Gtk.Dialog):
#search entry
self.search_entry=Gtk.SearchEntry()
self.search_entry.connect("search-changed", self.on_search_changed)
#label
self.label=Gtk.Label()
@@ -1193,7 +1355,6 @@ class Search(Gtk.Dialog):
#TreeView
self.treeview = Gtk.TreeView(model=self.store)
self.treeview.set_search_column(-1)
self.treeview.set_tooltip_column(5)
self.treeview.columns_autosize()
self.selection = self.treeview.get_selection()
@@ -1205,31 +1366,37 @@ class Search(Gtk.Dialog):
self.column_track = Gtk.TreeViewColumn(_("No"), renderer_text, text=0)
self.column_track.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_track.set_property("resizable", False)
self.column_track.set_sort_column_id(0)
self.treeview.append_column(self.column_track)
self.column_title = Gtk.TreeViewColumn(_("Title"), renderer_text, text=1)
self.column_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_title.set_property("resizable", False)
self.column_title.set_sort_column_id(1)
self.treeview.append_column(self.column_title)
self.column_artist = Gtk.TreeViewColumn(_("Artist"), renderer_text, text=2)
self.column_artist.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_artist.set_property("resizable", False)
self.column_artist.set_sort_column_id(2)
self.treeview.append_column(self.column_artist)
self.column_album = Gtk.TreeViewColumn(_("Album"), renderer_text, text=3)
self.column_album.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_album.set_property("resizable", False)
self.column_album.set_sort_column_id(3)
self.treeview.append_column(self.column_album)
self.column_time = Gtk.TreeViewColumn(_("Length"), renderer_text, text=4)
self.column_time.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
self.column_time.set_property("resizable", False)
self.column_time.set_sort_column_id(4)
self.treeview.append_column(self.column_time)
#connect
self.title_activated=self.treeview.connect("row-activated", self.on_row_activated)
self.title_change=self.selection.connect("changed", self.on_selection_change)
self.search_entry.connect("search-changed", self.on_search_changed)
#packing
scroll.add(self.treeview)
@@ -1243,7 +1410,7 @@ class Search(Gtk.Dialog):
selected_title=self.store.get_value(treeiter, 5)
self.client.clear()
self.client.add(selected_title)
self.client.play(0)
self.client.play()
def on_selection_change(self, widget):
treeiter=widget.get_selected()[1]
@@ -1253,7 +1420,7 @@ class Search(Gtk.Dialog):
def on_search_changed(self, widget):
self.store.clear()
for song in self.client.search("title", self.search_entry.get_text()):
for song in self.client.search("any", self.search_entry.get_text()):
try:
title=song["title"]
except:
@@ -1270,7 +1437,11 @@ class Search(Gtk.Dialog):
album=song["album"]
except:
album=_("Unknown Album")
duration=str(datetime.timedelta(seconds=int(float(song["duration"]))))
try:
dura=float(song["duration"])
except:
dura=0.0
duration=str(datetime.timedelta(seconds=int(dura)))
self.store.append([track, title, artist, album, duration, song["file"].replace("&", "")] )
self.label.set_text(_("Hits: %i") % (len(self.store)))
@@ -1281,15 +1452,11 @@ class LyricsWindow(Gtk.Window): #Lyrics view with own client because MPDClient i
self.set_default_size(450, 800)
#adding vars
self.client=Client()
self.settings=settings
self.client=Client(self.settings)
#connect client
active=self.settings.get_int("active-profile")
try:
self.client.connect(self.settings.get_value("hosts")[active], self.settings.get_value("ports")[active])
except:
pass
self.client.try_connect_default()
self.current_song={}
#widgets
@@ -1316,7 +1483,7 @@ class LyricsWindow(Gtk.Window): #Lyrics view with own client because MPDClient i
def update_loop():
while not self.stop:
try:
if self.client.connected():
cs=self.client.currentsong()
cs.pop("pos") #avoid unnecessary reloads caused by position change of current title
if cs != self.current_song:
@@ -1327,7 +1494,7 @@ class LyricsWindow(Gtk.Window): #Lyrics view with own client because MPDClient i
text=_("not found")
GLib.idle_add(update_label, text)
self.current_song=cs
except:
else:
self.current_song={}
GLib.idle_add(update_label, _("not connected"))
time.sleep(1)
@@ -1371,12 +1538,8 @@ class LyricsWindow(Gtk.Window): #Lyrics view with own client because MPDClient i
return output.encode('utf-8')
def on_settings_changed(self, *args):
active=self.settings.get_int("active-profile")
try:
self.client.disconnect()
self.client.connect(self.settings.get_value("hosts")[active], self.settings.get_value("ports")[active])
except:
pass
self.client.disconnect()
self.client.try_connect_default()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, app, client, settings):
@@ -1388,6 +1551,7 @@ class MainWindow(Gtk.ApplicationWindow):
#adding vars
self.client=client
self.songid_playing=None
self.icon_size=self.settings.get_gtk_icon_size("icon-size")
#actions
save_action = Gio.SimpleAction.new("save", None)
@@ -1407,18 +1571,18 @@ class MainWindow(Gtk.ApplicationWindow):
self.add_action(update_action)
#widgets
self.browser=Browser(self.client, self.settings)
self.browser=Browser(self.client, self.settings, self)
self.profiles=ProfileSelect(self.client, self.settings)
self.profiles.set_tooltip_text(_("Select profile"))
self.control=ClientControl(self.client, self.settings)
self.progress=SeekBar(self.client)
self.go_home_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("go-home", Gtk.IconSize.DND))
self.go_home_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("go-home-symbolic", self.icon_size))
self.go_home_button.set_tooltip_text(_("Return to album of current title"))
self.search_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("system-search", Gtk.IconSize.DND))
self.search_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("system-search-symbolic", self.icon_size))
self.search_button.set_tooltip_text(_("Title search"))
self.lyrics_button=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("media-view-subtitles", Gtk.IconSize.DND))
self.lyrics_button=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("media-view-subtitles-symbolic", self.icon_size))
self.lyrics_button.set_tooltip_text(_("Show lyrics"))
self.play_opts=PlaybackOptions(self.client)
self.play_opts=PlaybackOptions(self.client, self.settings)
#info bar
self.info_bar=Gtk.InfoBar.new()
@@ -1471,6 +1635,11 @@ class MainWindow(Gtk.ApplicationWindow):
self.add(self.vbox)
#connect client
self.client.try_connect_default()
self.show_all()
def update(self, app): #update title and send notify
if self.client.connected():
self.info_bar.set_revealed(False)
@@ -1509,11 +1678,7 @@ class MainWindow(Gtk.ApplicationWindow):
def on_info_bar_response(self, info_bar, response_id):
if response_id == Gtk.ResponseType.OK:
active=self.settings.get_int("active-profile")
try:
self.client.connect(self.settings.get_value("hosts")[active], self.settings.get_value("ports")[active])
except:
pass
self.client.try_connect_default()
info_bar.set_revealed(False)
def on_search_clicked(self, widget):
@@ -1558,17 +1723,17 @@ class MainWindow(Gtk.ApplicationWindow):
self.client.update()
class mpdevil(Gtk.Application):
BASE_KEY = "org.mpdevil"
def __init__(self, *args, **kwargs):
super().__init__(*args, application_id="org.mpdevil", flags=Gio.ApplicationFlags.FLAGS_NONE, **kwargs)
self.client=Client()
self.settings = Gio.Settings.new(self.BASE_KEY)
self.settings = Settings()
self.client=Client(self.settings)
self.window=None
def do_activate(self):
self.window = MainWindow(self, self.client, self.settings)
self.window.connect("delete-event", self.on_delete_event)
self.window.show_all()
if not self.window: #allow just one instance
self.window = MainWindow(self, self.client, self.settings)
self.window.connect("delete-event", self.on_delete_event)
self.window.present()
def do_startup(self):
Gtk.Application.do_startup(self)
@@ -1592,6 +1757,7 @@ class mpdevil(Gtk.Application):
dialog.set_version(VERSION)
dialog.set_comments(_("A small MPD client written in python"))
dialog.set_authors(["Martin Wagner"])
dialog.set_website("https://github.com/SoongNoonien/mpdevil")
dialog.set_logo_icon_name(PACKAGE)
dialog.run()
dialog.destroy()

View File

@@ -1,7 +1,7 @@
dnl -*- Mode: autoconf -*-
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ([2.68])
AC_INIT([mpdevil], [0.3])
AC_INIT([mpdevil], [0.4.2])
AC_CONFIG_SRCDIR([bin/mpdevil.py])
AM_INIT_AUTOMAKE
AC_CONFIG_MACRO_DIR([m4])
@@ -50,7 +50,7 @@ if $PYTHON -c "$prog" 1>&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD; then
AC_MSG_RESULT(found)
else
AC_MSG_RESULT(not found)
AC_MSG_ERROR(MPDClient not found)
AC_MSG_ERROR(python module mpd not found)
fi
dnl Check for beautifulsoup
@@ -62,7 +62,19 @@ if $PYTHON -c "$prog" 1>&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD; then
AC_MSG_RESULT(found)
else
AC_MSG_RESULT(not found)
AC_MSG_ERROR(beautifulsoup not found)
AC_MSG_ERROR(python module bs4 not found)
fi
dnl Check for requests
AC_MSG_CHECKING(for requests installed)
prog="
import requests
"
if $PYTHON -c "$prog" 1>&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD; then
AC_MSG_RESULT(found)
else
AC_MSG_RESULT(not found)
AC_MSG_ERROR(python module requests not found)
fi
AC_CONFIG_FILES([Makefile

View File

@@ -31,6 +31,11 @@
<summary>Size of main cover</summary>
<description></description>
</key>
<key type="i" name="icon-size">
<default>16</default>
<summary>Size of button icons in control bar</summary>
<description></description>
</key>
<key type="b" name="show-stop">
<default>false</default>
<summary>Show stop button</summary>

144
po/de.po
View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-06 00:53+0100\n"
"PO-Revision-Date: 2020-01-06 00:55+0100\n"
"POT-Creation-Date: 2020-01-28 19:56+0100\n"
"PO-Revision-Date: 2020-01-28 19:58+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de\n"
@@ -18,136 +18,144 @@ msgstr ""
"X-Generator: Poedit 2.2.4\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: mpdevil.py:116 mpdevil.py:250 mpdevil.py:1215
msgid "Artist"
msgstr "Künstler"
#: mpdevil.py:173 mpdevil.py:346 mpdevil.py:1260
msgid "Unknown Title"
msgstr "Unbekannter Titel"
#: mpdevil.py:184
#, python-format
msgid "%(album)s%(year)s (tracks: %(total_tracks)i) (%(total_length)s):"
msgstr "%(album)s%(year)s (Titel: %(total_tracks)i) (%(total_length)s):"
#: mpdevil.py:240 mpdevil.py:1205
#: mpdevil.py:159 mpdevil.py:368 mpdevil.py:1366
msgid "No"
msgstr "Nr."
#: mpdevil.py:245 mpdevil.py:1210
#: mpdevil.py:164 mpdevil.py:373 mpdevil.py:1372
msgid "Title"
msgstr "Titel"
#: mpdevil.py:255 mpdevil.py:1225
#: mpdevil.py:169 mpdevil.py:378 mpdevil.py:1378
msgid "Artist"
msgstr "Interpret"
#: mpdevil.py:174 mpdevil.py:383 mpdevil.py:1390
msgid "Length"
msgstr "Länge"
#: mpdevil.py:354 mpdevil.py:1268
#: mpdevil.py:214 mpdevil.py:483 mpdevil.py:1427
msgid "Unknown Title"
msgstr "Unbekannter Titel"
#: mpdevil.py:218 mpdevil.py:491 mpdevil.py:1435
msgid "Unknown Artist"
msgstr "Unbekannter Künstler"
#: mpdevil.py:358 mpdevil.py:1272
#: mpdevil.py:254
msgid "Album Artist"
msgstr "Albuminterpret"
#: mpdevil.py:313 mpdevil.py:504
#, python-format
msgid "%(total_tracks)i titles (%(total_length)s)"
msgstr "%(total_tracks)i Titel (%(total_length)s)"
#: mpdevil.py:495 mpdevil.py:1439
msgid "Unknown Album"
msgstr "Unbekanntes Album"
#: mpdevil.py:567
#: mpdevil.py:725
msgid "Select"
msgstr "Auswählen"
#: mpdevil.py:569
#: mpdevil.py:727
msgid "Profile:"
msgstr "Profil:"
#: mpdevil.py:571
#: mpdevil.py:729
msgid "Name:"
msgstr "Name:"
#: mpdevil.py:573
#: mpdevil.py:731
msgid "Host:"
msgstr "Host:"
#: mpdevil.py:575
#: mpdevil.py:733
msgid "Port:"
msgstr "Port:"
#: mpdevil.py:577
#: mpdevil.py:735
msgid "Music lib:"
msgstr "Musikverzeichnis:"
#: mpdevil.py:667
#: mpdevil.py:810
msgid "Choose directory"
msgstr "Verzeichnis Wählen"
#: mpdevil.py:703
#: mpdevil.py:846
msgid "Main cover size:"
msgstr "Größe des Haupt-Covers:"
#: mpdevil.py:705
#: mpdevil.py:848
msgid "Album-view cover size:"
msgstr "Covergröße in Albumansicht:"
#: mpdevil.py:711
#: mpdevil.py:854
msgid "Button icon size (restart required):"
msgstr "Symbolgröße der Knöpfe (Neustart erforderlich):"
#: mpdevil.py:863
msgid "Show stop button"
msgstr "Zeige Stopp-Knopf"
#: mpdevil.py:714
msgid "Show title list as tooltip in album view"
msgstr "Zeige Titellisten als Tooltips in Albumansicht"
#: mpdevil.py:866
msgid "Show tooltips in album view"
msgstr "Zeige Tooltips in Albumansicht"
#: mpdevil.py:717
#: mpdevil.py:869
msgid "Send notification on title change"
msgstr "Sende Benachrichtigung bei Titelwechsel"
#: mpdevil.py:720
#: mpdevil.py:872
msgid "Stop playback on quit"
msgstr "Wiedergabe beim Beenden stoppen"
#: mpdevil.py:723
#: mpdevil.py:875
msgid "Play selected album after current title"
msgstr "Ausgewähltes Album hinter aktuellem Titel einreihen"
#: mpdevil.py:754 mpdevil.py:1432
#: mpdevil.py:913 mpdevil.py:1596
msgid "Settings"
msgstr "Einstellungen"
#: mpdevil.py:767
#: mpdevil.py:926
msgid "General"
msgstr "Allgemein"
#: mpdevil.py:768
#: mpdevil.py:927
msgid "Profiles"
msgstr "Profile"
#: mpdevil.py:919
#: mpdevil.py:1081
msgid "Random mode"
msgstr "Zufallsmodus"
#: mpdevil.py:921
#: mpdevil.py:1083
msgid "Repeat mode"
msgstr "Dauerschleife"
#: mpdevil.py:923
#: mpdevil.py:1085
msgid "Single mode"
msgstr "Einzelstückmodus"
#: mpdevil.py:925
#: mpdevil.py:1087
msgid "Consume mode"
msgstr "Playliste verbrauchen"
#: mpdevil.py:1019
#: mpdevil.py:1182
msgid "Right click to show additional information"
msgstr "Rechtsclick für weitere Informationen"
#: mpdevil.py:1042
#: mpdevil.py:1206
msgid "MPD-Tag"
msgstr "MPD-Tag"
#: mpdevil.py:1045 mpdevil.py:1153
#: mpdevil.py:1209 mpdevil.py:1316
msgid "Value"
msgstr "Wert"
#: mpdevil.py:1066
#: mpdevil.py:1230
#, python-format
msgid ""
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)s bit, %(channels)s "
@@ -156,88 +164,88 @@ msgstr ""
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)s bit, %(channels)s "
"Kanäle, %(file_type)s"
#: mpdevil.py:1132
#: mpdevil.py:1295
msgid "Stats"
msgstr "Statistik"
#: mpdevil.py:1150
#: mpdevil.py:1313
msgid "Tag"
msgstr "Tag"
#: mpdevil.py:1170
#: mpdevil.py:1333
msgid "Search"
msgstr "Suche"
#: mpdevil.py:1220
#: mpdevil.py:1384
msgid "Album"
msgstr "Album"
#: mpdevil.py:1275
#: mpdevil.py:1446
#, python-format
msgid "Hits: %i"
msgstr "Treffer: %i"
#: mpdevil.py:1279
#: mpdevil.py:1450
msgid "Lyrics"
msgstr "Liedtext"
#: mpdevil.py:1323
#: mpdevil.py:1490
msgid "searching..."
msgstr "suche..."
#: mpdevil.py:1327
#: mpdevil.py:1494
msgid "not found"
msgstr "nicht gefunden"
#: mpdevil.py:1332
#: mpdevil.py:1499
msgid "not connected"
msgstr "nicht verbunden"
#: mpdevil.py:1412
#: mpdevil.py:1576
msgid "Select profile"
msgstr "Profil auswählen"
#: mpdevil.py:1416
#: mpdevil.py:1580
msgid "Return to album of current title"
msgstr "Zu Album des aktuellen Titels zurückkehren"
#: mpdevil.py:1418
#: mpdevil.py:1582
msgid "Title search"
msgstr "Titelsuche"
#: mpdevil.py:1420
#: mpdevil.py:1584
msgid "Show lyrics"
msgstr "Zeige Liedtext"
#: mpdevil.py:1427
#: mpdevil.py:1591
msgid "Not connected to MPD-server. Reconnect?"
msgstr "Nicht mit MPD-Server verbunden. Verbindung wiederherstellen?"
#: mpdevil.py:1431
#: mpdevil.py:1595
msgid "Save window size"
msgstr "Fenstergröße speichern"
#: mpdevil.py:1433
#: mpdevil.py:1597
msgid "Update database"
msgstr "Datenbank aktualisieren"
#: mpdevil.py:1434
#: mpdevil.py:1598
msgid "Server stats"
msgstr "Serverstatistik"
#: mpdevil.py:1435
#: mpdevil.py:1599
msgid "About"
msgstr "Über"
#: mpdevil.py:1436
#: mpdevil.py:1600
msgid "Quit"
msgstr "Beenden"
#: mpdevil.py:1441
#: mpdevil.py:1605
msgid "Main menu"
msgstr "Hauptmenu"
#: mpdevil.py:1593
#: mpdevil.py:1758
msgid "A small MPD client written in python"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-06 00:53+0100\n"
"POT-Creation-Date: 2020-01-28 19:56+0100\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,223 +17,231 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: mpdevil.py:116 mpdevil.py:250 mpdevil.py:1215
msgid "Artist"
msgstr ""
#: mpdevil.py:173 mpdevil.py:346 mpdevil.py:1260
msgid "Unknown Title"
msgstr ""
#: mpdevil.py:184
#, python-format
msgid "%(album)s%(year)s (tracks: %(total_tracks)i) (%(total_length)s):"
msgstr ""
#: mpdevil.py:240 mpdevil.py:1205
#: mpdevil.py:159 mpdevil.py:368 mpdevil.py:1366
msgid "No"
msgstr ""
#: mpdevil.py:245 mpdevil.py:1210
#: mpdevil.py:164 mpdevil.py:373 mpdevil.py:1372
msgid "Title"
msgstr ""
#: mpdevil.py:255 mpdevil.py:1225
#: mpdevil.py:169 mpdevil.py:378 mpdevil.py:1378
msgid "Artist"
msgstr ""
#: mpdevil.py:174 mpdevil.py:383 mpdevil.py:1390
msgid "Length"
msgstr ""
#: mpdevil.py:354 mpdevil.py:1268
#: mpdevil.py:214 mpdevil.py:483 mpdevil.py:1427
msgid "Unknown Title"
msgstr ""
#: mpdevil.py:218 mpdevil.py:491 mpdevil.py:1435
msgid "Unknown Artist"
msgstr ""
#: mpdevil.py:358 mpdevil.py:1272
#: mpdevil.py:254
msgid "Album Artist"
msgstr ""
#: mpdevil.py:313 mpdevil.py:504
#, python-format
msgid "%(total_tracks)i titles (%(total_length)s)"
msgstr ""
#: mpdevil.py:495 mpdevil.py:1439
msgid "Unknown Album"
msgstr ""
#: mpdevil.py:567
#: mpdevil.py:725
msgid "Select"
msgstr ""
#: mpdevil.py:569
#: mpdevil.py:727
msgid "Profile:"
msgstr ""
#: mpdevil.py:571
#: mpdevil.py:729
msgid "Name:"
msgstr ""
#: mpdevil.py:573
#: mpdevil.py:731
msgid "Host:"
msgstr ""
#: mpdevil.py:575
#: mpdevil.py:733
msgid "Port:"
msgstr ""
#: mpdevil.py:577
#: mpdevil.py:735
msgid "Music lib:"
msgstr ""
#: mpdevil.py:667
#: mpdevil.py:810
msgid "Choose directory"
msgstr ""
#: mpdevil.py:703
#: mpdevil.py:846
msgid "Main cover size:"
msgstr ""
#: mpdevil.py:705
#: mpdevil.py:848
msgid "Album-view cover size:"
msgstr ""
#: mpdevil.py:711
#: mpdevil.py:854
msgid "Button icon size (restart required):"
msgstr ""
#: mpdevil.py:863
msgid "Show stop button"
msgstr ""
#: mpdevil.py:714
msgid "Show title list as tooltip in album view"
#: mpdevil.py:866
msgid "Show tooltips in album view"
msgstr ""
#: mpdevil.py:717
#: mpdevil.py:869
msgid "Send notification on title change"
msgstr ""
#: mpdevil.py:720
#: mpdevil.py:872
msgid "Stop playback on quit"
msgstr ""
#: mpdevil.py:723
#: mpdevil.py:875
msgid "Play selected album after current title"
msgstr ""
#: mpdevil.py:754 mpdevil.py:1432
#: mpdevil.py:913 mpdevil.py:1596
msgid "Settings"
msgstr ""
#: mpdevil.py:767
#: mpdevil.py:926
msgid "General"
msgstr ""
#: mpdevil.py:768
#: mpdevil.py:927
msgid "Profiles"
msgstr ""
#: mpdevil.py:919
#: mpdevil.py:1081
msgid "Random mode"
msgstr ""
#: mpdevil.py:921
#: mpdevil.py:1083
msgid "Repeat mode"
msgstr ""
#: mpdevil.py:923
#: mpdevil.py:1085
msgid "Single mode"
msgstr ""
#: mpdevil.py:925
#: mpdevil.py:1087
msgid "Consume mode"
msgstr ""
#: mpdevil.py:1019
#: mpdevil.py:1182
msgid "Right click to show additional information"
msgstr ""
#: mpdevil.py:1042
#: mpdevil.py:1206
msgid "MPD-Tag"
msgstr ""
#: mpdevil.py:1045 mpdevil.py:1153
#: mpdevil.py:1209 mpdevil.py:1316
msgid "Value"
msgstr ""
#: mpdevil.py:1066
#: mpdevil.py:1230
#, python-format
msgid ""
"%(bitrate)s kb/s, %(frequency)s kHz, %(resolution)s bit, %(channels)s "
"channels, %(file_type)s"
msgstr ""
#: mpdevil.py:1132
#: mpdevil.py:1295
msgid "Stats"
msgstr ""
#: mpdevil.py:1150
#: mpdevil.py:1313
msgid "Tag"
msgstr ""
#: mpdevil.py:1170
#: mpdevil.py:1333
msgid "Search"
msgstr ""
#: mpdevil.py:1220
#: mpdevil.py:1384
msgid "Album"
msgstr ""
#: mpdevil.py:1275
#: mpdevil.py:1446
#, python-format
msgid "Hits: %i"
msgstr ""
#: mpdevil.py:1279
#: mpdevil.py:1450
msgid "Lyrics"
msgstr ""
#: mpdevil.py:1323
#: mpdevil.py:1490
msgid "searching..."
msgstr ""
#: mpdevil.py:1327
#: mpdevil.py:1494
msgid "not found"
msgstr ""
#: mpdevil.py:1332
#: mpdevil.py:1499
msgid "not connected"
msgstr ""
#: mpdevil.py:1412
#: mpdevil.py:1576
msgid "Select profile"
msgstr ""
#: mpdevil.py:1416
#: mpdevil.py:1580
msgid "Return to album of current title"
msgstr ""
#: mpdevil.py:1418
#: mpdevil.py:1582
msgid "Title search"
msgstr ""
#: mpdevil.py:1420
#: mpdevil.py:1584
msgid "Show lyrics"
msgstr ""
#: mpdevil.py:1427
#: mpdevil.py:1591
msgid "Not connected to MPD-server. Reconnect?"
msgstr ""
#: mpdevil.py:1431
#: mpdevil.py:1595
msgid "Save window size"
msgstr ""
#: mpdevil.py:1433
#: mpdevil.py:1597
msgid "Update database"
msgstr ""
#: mpdevil.py:1434
#: mpdevil.py:1598
msgid "Server stats"
msgstr ""
#: mpdevil.py:1435
#: mpdevil.py:1599
msgid "About"
msgstr ""
#: mpdevil.py:1436
#: mpdevil.py:1600
msgid "Quit"
msgstr ""
#: mpdevil.py:1441
#: mpdevil.py:1605
msgid "Main menu"
msgstr ""
#: mpdevil.py:1593
#: mpdevil.py:1758
msgid "A small MPD client written in python"
msgstr ""

BIN
screenshots/mainwindow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 KiB