split playlist and cover

This commit is contained in:
Martin Wagner 2021-10-04 17:35:51 +02:00
parent cf4b867a7e
commit 8170401ec4

View File

@ -2556,186 +2556,9 @@ class Browser(Gtk.Paned):
self._genres_button.get_image().set_text(genre)
self._genres_button.set_active(False)
######################
# playlist and cover #
######################
class LyricsWindow(FocusFrame):
def __init__(self, client, settings):
super().__init__()
self._settings=settings
self._client=client
self._displayed_song_file=None
# text view
self._text_view=Gtk.TextView(
editable=False,
cursor_visible=False,
wrap_mode=Gtk.WrapMode.WORD,
justification=Gtk.Justification.CENTER,
opacity=0.9
)
self._text_view.set_left_margin(5)
self._text_view.set_right_margin(5)
self._text_view.set_bottom_margin(5)
self._text_view.set_top_margin(3)
self.set_widget(self._text_view)
# text buffer
self._text_buffer=self._text_view.get_buffer()
# scroll
scroll=Gtk.ScrolledWindow()
scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll.add(self._text_view)
# connect
self._client.emitter.connect("disconnected", self._on_disconnected)
self._song_changed=self._client.emitter.connect("current_song_changed", self._refresh)
self._client.emitter.handler_block(self._song_changed)
# packing
self.add(scroll)
def enable(self, *args):
current_song=self._client.currentsong()
if current_song:
if current_song["file"] != self._displayed_song_file:
self._refresh()
else:
if self._displayed_song_file is not None:
self._refresh()
self._client.emitter.handler_unblock(self._song_changed)
GLib.idle_add(self._text_view.grab_focus) # focus textview
def disable(self, *args):
self._client.emitter.handler_block(self._song_changed)
def _get_lyrics(self, title, artist):
replaces=((" ", "+"),(".", "_"),("@", "_"),(",", "_"),(";", "_"),("&", "_"),("\\", "_"),("/", "_"),('"', "_"),("(", "_"),(")", "_"))
for char1, char2 in replaces:
title=title.replace(char1, char2)
artist=artist.replace(char1, char2)
req=requests.get(f"https://www.letras.mus.br/winamp.php?musica={title}&artista={artist}")
soup=BeautifulSoup(req.text, "html.parser")
soup=soup.find(id="letra-cnt")
if soup is None:
raise ValueError("Not found")
paragraphs=[i for i in soup.children][1] # remove unneded paragraphs (NavigableString)
lyrics=""
for paragraph in paragraphs:
for line in paragraph.stripped_strings:
lyrics+=line+"\n"
lyrics+="\n"
output=lyrics[:-2] # omit last two newlines
if output:
return output
else: # assume song is instrumental when lyrics are empty
return "Instrumental"
def _display_lyrics(self, current_song):
GLib.idle_add(self._text_buffer.set_text, _("searching…"), -1)
try:
text=self._get_lyrics(current_song["title"][0], current_song["artist"][0])
except requests.exceptions.ConnectionError:
self._displayed_song_file=None
text=_("connection error")
except ValueError:
text=_("lyrics not found")
GLib.idle_add(self._text_buffer.set_text, text, -1)
def _refresh(self, *args):
current_song=self._client.currentsong()
if current_song:
self._displayed_song_file=current_song["file"]
update_thread=threading.Thread(
target=self._display_lyrics,
kwargs={"current_song": current_song},
daemon=True
)
update_thread.start()
else:
self._displayed_song_file=None
self._text_buffer.set_text("", -1)
def _on_disconnected(self, *args):
self._displayed_song_file=None
self._text_buffer.set_text("", -1)
class CoverEventBox(Gtk.EventBox):
def __init__(self, client, settings):
super().__init__()
self._client=client
self._settings=settings
# album popover
self._album_popover=AlbumPopover(self._client, self._settings)
# connect
self._button_press_event=self.connect("button-press-event", self._on_button_press_event)
self._client.emitter.connect("disconnected", self._on_disconnected)
def _on_button_press_event(self, widget, event):
if self._settings.get_boolean("mini-player"):
if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
window=self.get_toplevel()
window.begin_move_drag(1, event.x_root, event.y_root, Gdk.CURRENT_TIME)
else:
if self._client.connected():
song=self._client.currentsong()
if song:
artist=song[self._settings.get_artist_type()][0]
album=song["album"][0]
year=song["date"][0]
if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
self._client.album_to_playlist(album, artist, year, None)
elif event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
self._client.album_to_playlist(album, artist, year, None, "play")
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
self._client.album_to_playlist(album, artist, year, None, "append")
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
self._album_popover.open(album, artist, year, None, widget, event.x, event.y)
def _on_disconnected(self, *args):
self._album_popover.popdown()
class MainCover(Gtk.Image):
def __init__(self, client, settings):
super().__init__()
self._client=client
self._settings=settings
# set default size
size=self._settings.get_int("track-cover")
self.set_size_request(size, size)
# connect
self._client.emitter.connect("current_song_changed", self._refresh)
self._client.emitter.connect("disconnected", self._on_disconnected)
self._client.emitter.connect("reconnected", self._on_reconnected)
self._settings.connect("changed::track-cover", self._on_settings_changed)
def _clear(self):
size=self._settings.get_int("track-cover")
self.set_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, size, size))
def _refresh(self, *args):
song=self._client.currentsong()
if song:
self.set_from_pixbuf(self._client.get_cover(song).get_pixbuf(self._settings.get_int("track-cover")))
else:
self._clear()
def _on_disconnected(self, *args):
self.set_sensitive(False)
self._clear()
def _on_reconnected(self, *args):
self.set_sensitive(True)
def _on_settings_changed(self, *args):
size=self._settings.get_int("track-cover")
self.set_size_request(size, size)
self._refresh()
############
# playlist #
############
class PlaylistWindow(Gtk.Overlay):
selected_path=GObject.Property(type=Gtk.TreePath, default=None) # currently marked song (bold text)
@ -3035,7 +2858,188 @@ class PlaylistWindow(Gtk.Overlay):
self.set_property("no-show-all", False)
self.show_all()
class CoverPlaylistWindow(Gtk.Paned):
####################
# cover and lyrics #
####################
class LyricsWindow(FocusFrame):
def __init__(self, client, settings):
super().__init__()
self._settings=settings
self._client=client
self._displayed_song_file=None
# text view
self._text_view=Gtk.TextView(
editable=False,
cursor_visible=False,
wrap_mode=Gtk.WrapMode.WORD,
justification=Gtk.Justification.CENTER,
opacity=0.9
)
self._text_view.set_left_margin(5)
self._text_view.set_right_margin(5)
self._text_view.set_bottom_margin(5)
self._text_view.set_top_margin(3)
self.set_widget(self._text_view)
# text buffer
self._text_buffer=self._text_view.get_buffer()
# scroll
scroll=Gtk.ScrolledWindow()
scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll.add(self._text_view)
# connect
self._client.emitter.connect("disconnected", self._on_disconnected)
self._song_changed=self._client.emitter.connect("current_song_changed", self._refresh)
self._client.emitter.handler_block(self._song_changed)
# packing
self.add(scroll)
def enable(self, *args):
current_song=self._client.currentsong()
if current_song:
if current_song["file"] != self._displayed_song_file:
self._refresh()
else:
if self._displayed_song_file is not None:
self._refresh()
self._client.emitter.handler_unblock(self._song_changed)
GLib.idle_add(self._text_view.grab_focus) # focus textview
def disable(self, *args):
self._client.emitter.handler_block(self._song_changed)
def _get_lyrics(self, title, artist):
replaces=((" ", "+"),(".", "_"),("@", "_"),(",", "_"),(";", "_"),("&", "_"),("\\", "_"),("/", "_"),('"', "_"),("(", "_"),(")", "_"))
for char1, char2 in replaces:
title=title.replace(char1, char2)
artist=artist.replace(char1, char2)
req=requests.get(f"https://www.letras.mus.br/winamp.php?musica={title}&artista={artist}")
soup=BeautifulSoup(req.text, "html.parser")
soup=soup.find(id="letra-cnt")
if soup is None:
raise ValueError("Not found")
paragraphs=[i for i in soup.children][1] # remove unneded paragraphs (NavigableString)
lyrics=""
for paragraph in paragraphs:
for line in paragraph.stripped_strings:
lyrics+=line+"\n"
lyrics+="\n"
output=lyrics[:-2] # omit last two newlines
if output:
return output
else: # assume song is instrumental when lyrics are empty
return "Instrumental"
def _display_lyrics(self, current_song):
GLib.idle_add(self._text_buffer.set_text, _("searching…"), -1)
try:
text=self._get_lyrics(current_song["title"][0], current_song["artist"][0])
except requests.exceptions.ConnectionError:
self._displayed_song_file=None
text=_("connection error")
except ValueError:
text=_("lyrics not found")
GLib.idle_add(self._text_buffer.set_text, text, -1)
def _refresh(self, *args):
current_song=self._client.currentsong()
if current_song:
self._displayed_song_file=current_song["file"]
update_thread=threading.Thread(
target=self._display_lyrics,
kwargs={"current_song": current_song},
daemon=True
)
update_thread.start()
else:
self._displayed_song_file=None
self._text_buffer.set_text("", -1)
def _on_disconnected(self, *args):
self._displayed_song_file=None
self._text_buffer.set_text("", -1)
class CoverEventBox(Gtk.EventBox):
def __init__(self, client, settings):
super().__init__()
self._client=client
self._settings=settings
# album popover
self._album_popover=AlbumPopover(self._client, self._settings)
# connect
self._button_press_event=self.connect("button-press-event", self._on_button_press_event)
self._client.emitter.connect("disconnected", self._on_disconnected)
def _on_button_press_event(self, widget, event):
if self._settings.get_boolean("mini-player"):
if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
window=self.get_toplevel()
window.begin_move_drag(1, event.x_root, event.y_root, Gdk.CURRENT_TIME)
else:
if self._client.connected():
song=self._client.currentsong()
if song:
artist=song[self._settings.get_artist_type()][0]
album=song["album"][0]
year=song["date"][0]
if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
self._client.album_to_playlist(album, artist, year, None)
elif event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
self._client.album_to_playlist(album, artist, year, None, "play")
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
self._client.album_to_playlist(album, artist, year, None, "append")
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
self._album_popover.open(album, artist, year, None, widget, event.x, event.y)
def _on_disconnected(self, *args):
self._album_popover.popdown()
class MainCover(Gtk.Image):
def __init__(self, client, settings):
super().__init__()
self._client=client
self._settings=settings
# set default size
size=self._settings.get_int("track-cover")
self.set_size_request(size, size)
# connect
self._client.emitter.connect("current_song_changed", self._refresh)
self._client.emitter.connect("disconnected", self._on_disconnected)
self._client.emitter.connect("reconnected", self._on_reconnected)
self._settings.connect("changed::track-cover", self._on_settings_changed)
def _clear(self):
size=self._settings.get_int("track-cover")
self.set_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, size, size))
def _refresh(self, *args):
song=self._client.currentsong()
if song:
self.set_from_pixbuf(self._client.get_cover(song).get_pixbuf(self._settings.get_int("track-cover")))
else:
self._clear()
def _on_disconnected(self, *args):
self.set_sensitive(False)
self._clear()
def _on_reconnected(self, *args):
self.set_sensitive(True)
def _on_settings_changed(self, *args):
size=self._settings.get_int("track-cover")
self.set_size_request(size, size)
self._refresh()
class CoverLyricsWindow(Gtk.Overlay):
def __init__(self, client, settings):
super().__init__()
self._client=client
@ -3045,9 +3049,6 @@ class CoverPlaylistWindow(Gtk.Paned):
main_cover=MainCover(self._client, self._settings)
self._cover_event_box=CoverEventBox(self._client, self._settings)
# playlist
self._playlist_window=PlaylistWindow(self._client, self._settings)
# lyrics button
self.lyrics_button=Gtk.ToggleButton(
image=Gtk.Image.new_from_icon_name("media-view-subtitles-symbolic", Gtk.IconSize.BUTTON),
@ -3081,12 +3082,9 @@ class CoverPlaylistWindow(Gtk.Paned):
self._client.emitter.connect("reconnected", self._on_reconnected)
# packing
overlay=Gtk.Overlay()
overlay.add(main_cover)
overlay.add_overlay(self._stack)
overlay.add_overlay(self._lyrics_button_revealer)
self.pack1(overlay, False, False)
self.pack2(self._playlist_window, True, False)
self.add(main_cover)
self.add_overlay(self._stack)
self.add_overlay(self._lyrics_button_revealer)
def _on_reconnected(self, *args):
self.lyrics_button.set_sensitive(True)
@ -3742,10 +3740,12 @@ class MainWindow(Gtk.ApplicationWindow):
shortcuts_window.set_modal(False)
# widgets
self._paned=Gtk.Paned()
self._paned0=Gtk.Paned()
self._paned2=Gtk.Paned()
self._browser=Browser(self._client, self._settings)
self._search_window=SearchWindow(self._client)
self._cover_playlist_window=CoverPlaylistWindow(self._client, self._settings)
self._cover_lyrics_window=CoverLyricsWindow(self._client, self._settings)
playlist_window=PlaylistWindow(self._client, self._settings)
playback_control=PlaybackControl(self._client, self._settings)
seek_bar=SeekBar(self._client)
audio=AudioFormat(self._client, self._settings)
@ -3816,8 +3816,10 @@ class MainWindow(Gtk.ApplicationWindow):
# packing
self._on_playlist_pos_changed() # set orientation
self._paned.pack1(self._stack, True, False)
self._paned.pack2(self._cover_playlist_window, False, False)
self._paned0.pack1(self._cover_lyrics_window, False, False)
self._paned0.pack2(playlist_window, True, False)
self._paned2.pack1(self._stack, True, False)
self._paned2.pack2(self._paned0, False, False)
action_bar=Gtk.ActionBar()
if self._use_csd:
self._header_bar=Gtk.HeaderBar()
@ -3836,7 +3838,7 @@ class MainWindow(Gtk.ApplicationWindow):
action_bar.pack_start(playback_options)
action_bar.pack_start(volume_button)
vbox=Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
vbox.pack_start(self._paned, True, True, 0)
vbox.pack_start(self._paned2, True, True, 0)
vbox.pack_start(action_bar, False, False, 0)
overlay=Gtk.Overlay()
overlay.add(vbox)
@ -3854,9 +3856,9 @@ class MainWindow(Gtk.ApplicationWindow):
while Gtk.events_pending(): # ensure window is visible
Gtk.main_iteration_do(True)
# restore paned settings when window is visible (fixes a bug when window is maximized)
self._settings.bind("paned0", self._cover_playlist_window, "position", Gio.SettingsBindFlags.DEFAULT)
self._settings.bind("paned0", self._paned0, "position", Gio.SettingsBindFlags.DEFAULT)
self._settings.bind("paned1", self._browser, "position", Gio.SettingsBindFlags.DEFAULT)
self._settings.bind("paned2", self._paned, "position", Gio.SettingsBindFlags.DEFAULT)
self._settings.bind("paned2", self._paned2, "position", Gio.SettingsBindFlags.DEFAULT)
# start client
def callback(*args):
@ -3865,7 +3867,7 @@ class MainWindow(Gtk.ApplicationWindow):
GLib.idle_add(callback)
def _on_toggle_lyrics(self, action, param):
self._cover_playlist_window.lyrics_button.set_active(not(self._cover_playlist_window.lyrics_button.get_active()))
self._cover_lyrics_window.lyrics_button.emit("clicked")
def _on_back_to_current_album(self, action, param):
self._back_button.emit("clicked")
@ -4000,11 +4002,11 @@ class MainWindow(Gtk.ApplicationWindow):
def _on_playlist_pos_changed(self, *args):
if self._settings.get_boolean("playlist-right"):
self._cover_playlist_window.set_orientation(Gtk.Orientation.VERTICAL)
self._paned.set_orientation(Gtk.Orientation.HORIZONTAL)
self._paned0.set_orientation(Gtk.Orientation.VERTICAL)
self._paned2.set_orientation(Gtk.Orientation.HORIZONTAL)
else:
self._cover_playlist_window.set_orientation(Gtk.Orientation.HORIZONTAL)
self._paned.set_orientation(Gtk.Orientation.VERTICAL)
self._paned0.set_orientation(Gtk.Orientation.HORIZONTAL)
self._paned2.set_orientation(Gtk.Orientation.VERTICAL)
###################
# Gtk application #