diff -Nru instant-lyrics-0.1.0/data/icons/instant-lyrics-old.svg instant-lyrics-0.3.1/data/icons/instant-lyrics-old.svg --- instant-lyrics-0.1.0/data/icons/instant-lyrics-old.svg 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/data/icons/instant-lyrics-old.svg 2017-04-16 04:38:27.000000000 +0000 @@ -0,0 +1,59 @@ + +image/svg+xml \ No newline at end of file diff -Nru instant-lyrics-0.1.0/data/icons/instant-lyrics.svg instant-lyrics-0.3.1/data/icons/instant-lyrics.svg --- instant-lyrics-0.1.0/data/icons/instant-lyrics.svg 2017-04-16 04:38:27.000000000 +0000 +++ instant-lyrics-0.3.1/data/icons/instant-lyrics.svg 2017-05-01 04:56:18.000000000 +0000 @@ -1,59 +1,83 @@ + + image/svg+xml \ No newline at end of file + id="defs49" /> \ No newline at end of file diff -Nru instant-lyrics-0.1.0/data/icons/spotify-logo-symbolic.svg instant-lyrics-0.3.1/data/icons/spotify-logo-symbolic.svg --- instant-lyrics-0.1.0/data/icons/spotify-logo-symbolic.svg 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/data/icons/spotify-logo-symbolic.svg 2017-05-01 04:56:18.000000000 +0000 @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru instant-lyrics-0.1.0/debian/changelog instant-lyrics-0.3.1/debian/changelog --- instant-lyrics-0.1.0/debian/changelog 2017-04-17 05:53:36.000000000 +0000 +++ instant-lyrics-0.3.1/debian/changelog 2017-05-20 08:09:20.000000000 +0000 @@ -1,3 +1,22 @@ +instant-lyrics (0.3.1-0extras17.04.0) zesty; urgency=medium + + * added python3-cssselect dependency + * added a button to save lyrics + + -- Lorenzo Carbonell Sat, 20 May 2017 10:09:20 +0200 + +instant-lyrics (0.3.0-0extras16.04.0) xenial; urgency=medium + + * Added Rhytmbox + + -- Lorenzo Carbonell Mon, 01 May 2017 08:36:01 +0200 + +instant-lyrics (0.2.0-0extras16.04.0) xenial; urgency=medium + + * Instant updates + + -- Lorenzo Carbonell Mon, 01 May 2017 07:48:34 +0200 + instant-lyrics (0.1.0-0extras16.04.2) xenial; urgency=medium * First release diff -Nru instant-lyrics-0.1.0/debian/control instant-lyrics-0.3.1/debian/control --- instant-lyrics-0.1.0/debian/control 2017-04-17 05:14:27.000000000 +0000 +++ instant-lyrics-0.3.1/debian/control 2017-05-20 07:42:12.000000000 +0000 @@ -13,6 +13,7 @@ python3-gi, gir1.2-gtk-3.0, python3-lxml, + python3-cssselect, python3-requests Description: Instantly fetches the lyrics of the currently of any song Instantly fetches the lyrics of the currently playing spotify song, or any song, diff -Nru instant-lyrics-0.1.0/debian/install instant-lyrics-0.3.1/debian/install --- instant-lyrics-0.1.0/debian/install 2017-04-17 05:53:18.000000000 +0000 +++ instant-lyrics-0.3.1/debian/install 2017-05-01 05:11:13.000000000 +0000 @@ -1,5 +1,7 @@ src/*.py /opt/extras.ubuntu.com/instant-lyrics/share/instant-lyrics +src/ui/*.ui /opt/extras.ubuntu.com/instant-lyrics/share/instant-lyrics/ui data/icons/instant-lyrics.svg /opt/extras.ubuntu.com/instant-lyrics/share/icons +data/icons/spotify-logo-symbolic.svg /opt/extras.ubuntu.com/instant-lyrics/share/icons data/instant-lyrics.desktop /usr/share/applications data/instant-lyrics-autostart.desktop /opt/extras.ubuntu.com/instant-lyrics/share/instant-lyrics debian/changelog /opt/extras.ubuntu.com/instant-lyrics/share/instant-lyrics diff -Nru instant-lyrics-0.1.0/.idea/dictionaries/amirhosein.xml instant-lyrics-0.3.1/.idea/dictionaries/amirhosein.xml --- instant-lyrics-0.1.0/.idea/dictionaries/amirhosein.xml 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/.idea/dictionaries/amirhosein.xml 2017-05-01 04:56:18.000000000 +0000 @@ -0,0 +1,7 @@ + + + + spotify + + + \ No newline at end of file diff -Nru instant-lyrics-0.1.0/.idea/inspectionProfiles/profiles_settings.xml instant-lyrics-0.3.1/.idea/inspectionProfiles/profiles_settings.xml --- instant-lyrics-0.1.0/.idea/inspectionProfiles/profiles_settings.xml 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/.idea/inspectionProfiles/profiles_settings.xml 2017-05-01 04:56:18.000000000 +0000 @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff -Nru instant-lyrics-0.1.0/.idea/Instant-Lyrics.iml instant-lyrics-0.3.1/.idea/Instant-Lyrics.iml --- instant-lyrics-0.1.0/.idea/Instant-Lyrics.iml 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/.idea/Instant-Lyrics.iml 2017-05-01 04:56:18.000000000 +0000 @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff -Nru instant-lyrics-0.1.0/.idea/misc.xml instant-lyrics-0.3.1/.idea/misc.xml --- instant-lyrics-0.1.0/.idea/misc.xml 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/.idea/misc.xml 2017-05-01 04:56:18.000000000 +0000 @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff -Nru instant-lyrics-0.1.0/.idea/modules.xml instant-lyrics-0.3.1/.idea/modules.xml --- instant-lyrics-0.1.0/.idea/modules.xml 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/.idea/modules.xml 2017-05-01 04:56:18.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff -Nru instant-lyrics-0.1.0/.idea/vcs.xml instant-lyrics-0.3.1/.idea/vcs.xml --- instant-lyrics-0.1.0/.idea/vcs.xml 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/.idea/vcs.xml 2017-05-01 04:56:18.000000000 +0000 @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff -Nru instant-lyrics-0.1.0/README.md instant-lyrics-0.3.1/README.md --- instant-lyrics-0.1.0/README.md 2017-04-16 04:38:27.000000000 +0000 +++ instant-lyrics-0.3.1/README.md 2017-05-01 05:55:50.000000000 +0000 @@ -5,12 +5,8 @@ A linux application with a very convinient GUI. Build with Python Gtk+3 (gi). # Screenshot -![Screenshot](https://cloud.githubusercontent.com/assets/6123105/23824316/3fe58044-069a-11e7-804e-180ea4041002.jpeg) - -# Working -### GIF -![working](https://cloud.githubusercontent.com/assets/6123105/23824730/e0e0829e-06a1-11e7-8d57-3235c4266f2c.gif) - +![Screenshot](https://github.com/kazemnejad/Instant-Lyrics/raw/master/screenshots/01.png) +![Screenshot](https://github.com/kazemnejad/Instant-Lyrics/raw/master/screenshots/02.png) # Compatibility @@ -43,21 +39,21 @@ ### For Ubuntu/Debian based systems: ``` sh -sudo apt install python-gi python-dbus gir1.2-appindicator3-0.1 python-requests python-bs4 python-lxml +sudo apt install python3-gi python3-dbus gir1.2-appindicator3-0.1 python3-requests python3-lxml ``` -(requests, lxml and bs4 can be install from `pip` also: `pip install requests lxml beautifiulsoup4`) +(requests and lxml can be install from `pip` also: `pip install requests lxml beautifiulsoup4`) ### For Arch users ``` sh -sudo pacman -S python2-dbus python2-requests python2-lxml python2-beautifulsoup4 python2-gobject libappindicator-gtk3 +sudo pacman -S python3-dbus python3-requests python3-lxml python3-gobject libappindicator-gtk3 ``` ### Fedora ``` sh -sudo dnf install dbus-python python-gobject libappindicator-gtk3 python2-requests python-beautifulsoup4 python2-lxml +sudo dnf install dbus-python3 python3-gobject libappindicator-gtk3 python3-requests python3-lxml ``` ## Install from source @@ -65,11 +61,11 @@ After you've installed the dependencies, open terminal and go to the directory where you want to install. Enter the commands: ``` sh -git clone https://github.com/bhrigu123/Instant-Lyrics.git +git clone https://github.com/kazemnejad/Instant-Lyrics.git cd Instant-Lyrics/ -python InstantLyrics.py +python instantlyric.py ``` The icon will appear in the system tray (indicator panel). You can start using the application from there. Binary files /tmp/tmp8M8zW6/0k3lOGoiAo/instant-lyrics-0.1.0/screenshots/01.png and /tmp/tmp8M8zW6/S66mPKoOnB/instant-lyrics-0.3.1/screenshots/01.png differ Binary files /tmp/tmp8M8zW6/0k3lOGoiAo/instant-lyrics-0.1.0/screenshots/02.png and /tmp/tmp8M8zW6/S66mPKoOnB/instant-lyrics-0.3.1/screenshots/02.png differ diff -Nru instant-lyrics-0.1.0/src/comun.py instant-lyrics-0.3.1/src/comun.py --- instant-lyrics-0.1.0/src/comun.py 2017-04-16 06:09:42.000000000 +0000 +++ instant-lyrics-0.3.1/src/comun.py 2017-05-01 05:21:44.000000000 +0000 @@ -39,6 +39,7 @@ ICONDIR = os.path.join(ROOTDIR, 'share/icons') AUTOSTART = os.path.join(APPDIR, 'instant-lyrics-autostart.desktop') + UIDIR = os.path.join(APPDIR, 'ui') else: ROOTDIR = os.path.dirname(__file__) APPDIR = ROOTDIR @@ -47,7 +48,7 @@ ICONDIR = os.path.normpath(os.path.join(ROOTDIR, '../data/icons')) AUTOSTART = os.path.join(APPDIR, 'instant-lyrics-autostart.desktop') - + UIDIR = os.path.join(APPDIR, 'ui') f = open(CHANGELOG, 'r') line = f.readline() f.close() @@ -58,4 +59,6 @@ VERSION = VERSION + '-src' ICON = os.path.join(ICONDIR, APP + '.svg') +SPOTIFY_ICON = os.path.join(ICONDIR, 'spotify-logo-symbolic.svg') +UIFILE = os.path.join(UIDIR, 'ToolbarInfo.ui') APPINDICATOR_ID = 'lyricsappindicator' diff -Nru instant-lyrics-0.1.0/src/__init__.py instant-lyrics-0.3.1/src/__init__.py --- instant-lyrics-0.1.0/src/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/src/__init__.py 2017-05-01 05:08:48.000000000 +0000 @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- \ No newline at end of file diff -Nru instant-lyrics-0.1.0/src/instant-lyrics.py instant-lyrics-0.3.1/src/instant-lyrics.py --- instant-lyrics-0.1.0/src/instant-lyrics.py 2017-04-16 06:03:43.000000000 +0000 +++ instant-lyrics-0.3.1/src/instant-lyrics.py 2017-05-01 05:11:10.000000000 +0000 @@ -1,6 +1,5 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- - import gi try: gi.require_version('Gtk', '3.0') @@ -8,69 +7,59 @@ except Exception as e: print(e) exit(-1) -from gi.repository import AppIndicator3 as appindicator from gi.repository import Gtk - +from gi.repository import AppIndicator3 as appindicator +import os import signal -import threading - -from windows import LyricsWindow, PreferenceWindow -import utils +from lyricwindow import LyricsWindow import comun +APPINDICATOR_ID = 'lyricsappindicator' -class AppIndicator(): +class AppIndicator: def __init__(self): signal.signal(signal.SIGINT, signal.SIG_DFL) - indicator = appindicator.Indicator.new( - comun.APPINDICATOR_ID, - comun.ICON, - appindicator.IndicatorCategory.SYSTEM_SERVICES) - indicator.set_status(appindicator.IndicatorStatus.ACTIVE) - indicator.set_menu(self.build_menu()) + self.main_window = None - self.Config = utils.get_config() - Gtk.main() + self.indicator = appindicator.Indicator.new( + APPINDICATOR_ID, comun.ICON, + appindicator.IndicatorCategory.SYSTEM_SERVICES) + self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) + self.indicator.set_menu(self.build_menu()) def build_menu(self): menu = Gtk.Menu() - get_lyrics = Gtk.MenuItem('Get Lyrics') - get_lyrics.connect('activate', self.fetch_lyrics) - - spotify_lyrics = Gtk.MenuItem('Spotify Lyrics') - spotify_lyrics.connect('activate', self.spotify_lyrics) - - preferences = Gtk.MenuItem('Preferences') - preferences.connect('activate', self.preferences) + item_open = Gtk.MenuItem('Open') + item_open.connect('activate', self.open) item_quit = Gtk.MenuItem('Quit') item_quit.connect('activate', self.quit) - menu.append(get_lyrics) - menu.append(spotify_lyrics) - menu.append(preferences) + menu.append(item_open) menu.append(item_quit) menu.show_all() - return menu - def fetch_lyrics(self, source): - LyricsWindow("get", self) - - def spotify_lyrics(self, source): - win = LyricsWindow("spotify", self) - thread = threading.Thread(target=win.get_spotify) - thread.daemon = True - thread.start() + return menu - def preferences(self, source): - PreferenceWindow(self) + def open(self, source): + self.main_window = LyricsWindow() + self.main_window.show_all() def quit(self, source): + if self.main_window: + self.main_window.destroy() + Gtk.main_quit() + os.kill(os.getpid(), signal.SIGUSR1) + + +def main(): + AppIndicator() + Gtk.main() if __name__ == '__main__': - app = AppIndicator() + main() diff -Nru instant-lyrics-0.1.0/src/lyrics.py instant-lyrics-0.3.1/src/lyrics.py --- instant-lyrics-0.1.0/src/lyrics.py 2017-04-16 05:39:49.000000000 +0000 +++ instant-lyrics-0.3.1/src/lyrics.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import requests -from lxml.html import fromstring -from urllib.parse import quote_plus - - -def get_lyrics(song_name): - - song_name += ' metrolyrics' - name = quote_plus(song_name) - url = 'http://www.google.com/search?q=' + name - result = requests.get(url).text - link_start = result.find('http://www.metrolyrics.com') - if(link_start == -1): - return('Lyrics not found on Metrolyrics') - link_end = result.find('html', link_start + 1) - link = result[link_start:link_end + 4] - - r = requests.get(link) - lyrics_html = r.content.decode('UTF-8') - - doc = fromstring(lyrics_html) - ly = '\n'.join(verso.text_content() for verso in doc.cssselect('p.verse')) - return (ly) - - -if __name__ == '__main__': - print(get_lyrics('Despacito')) diff -Nru instant-lyrics-0.1.0/src/lyricwindow.py instant-lyrics-0.3.1/src/lyricwindow.py --- instant-lyrics-0.1.0/src/lyricwindow.py 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/src/lyricwindow.py 2017-05-20 08:08:20.000000000 +0000 @@ -0,0 +1,350 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import gi +try: + gi.require_version('Gtk', '3.0') + gi.require_version('AppIndicator3', '0.1') +except Exception as e: + print(e) + exit(-1) +from threading import Thread +from requests import RequestException +from gi.repository import Gtk, Gdk, Gio, GdkPixbuf +from periodicthread import PeriodicThread +from songdata import get_song_data, get_pixbuf, get_lyrics +from spotify import get_current_playing_song +from messages import Error +import comun + + +def bind_accelerator(accelerators, widget, accelerator, signal='clicked'): + key, mod = Gtk.accelerator_parse(accelerator) + widget.add_accelerator(signal, accelerators, key, mod, + Gtk.AccelFlags.VISIBLE) + + +class LyricsWindow(Gtk.Window): + def __init__(self): + Gtk.Window.__init__(self, title="Lyrics") + self.set_icon_from_file(comun.ICON) + # self.set_icon_name('spotify-logo-symbolic') + self.set_border_width(0) + self.set_default_size(470, 650) + self.set_position(Gtk.WindowPosition.CENTER) + + self.init_spotify_checker() + + accelerators = Gtk.AccelGroup() + self.add_accel_group(accelerators) + self.connect('key_press_event', self._check_escape) + + self.spinner = Gtk.Spinner() + + self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, + spacing=6) + self.main_box.set_size_request(350, 700) + + lyrics_vbox = self._create_lyrics_box() + self.main_box.pack_start(lyrics_vbox, True, True, 0) + + scrolled = Gtk.ScrolledWindow() + scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scrolled.add(self.main_box) + + self.toolbar = Toolbar(self.on_lyric_requested, self.on_cancel_spotify, + self.on_download_lyric, accelerators, scrolled) + self.set_titlebar(self.toolbar) + + self.add(scrolled) + self.toolbar.btn_spotify.emit('clicked') + + def init_spotify_checker(self): + self.spotify_checker = PeriodicThread(self.load_from_spotify, 0.5) + self._last_song_hash = '' + + def on_download_lyric(self): + dialog = Gtk.FileChooserDialog('Select output file', + self, Gtk.FileChooserAction.SAVE, + (Gtk.STOCK_CANCEL, + Gtk.ResponseType.REJECT, + Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)) + dialog.set_position(Gtk.WindowPosition.CENTER_ALWAYS) + filter = Gtk.FileFilter() + filter.set_name('Text file') + filter.add_mime_type('txt/plain') + dialog.add_filter(filter) + if dialog.run() == Gtk.ResponseType.ACCEPT: + dialog.hide() + filename = dialog.get_filename() + if not filename.endswith('.txt'): + filename += '.txt' + lyrics = self.lyrics.get_text() + with open(filename, 'w') as text_file: + text_file.write(lyrics) + dialog.destroy() + + def on_lyric_requested(self, is_from_spotify, song, artist_name=''): + if is_from_spotify: + if not self.spotify_checker.isAlive(): + self.spotify_checker = PeriodicThread(self.load_from_spotify, + 0.5) + self.spotify_checker.start() + else: + self.spotify_checker.cancel() + self._last_song_hash = '' + self.toolbar.info.clear() + self.start_fetching_song_data(song, artist_name) + + def on_cancel_spotify(self): + self.spotify_checker.cancel() + + def show_error_msg(self, title, message): + self.status.set_markup('' + title + '') + self.message.set_text(message) + + def clear_error_msg(self): + self.show_error_msg('', '') + + def clear_box(self): + self.lyrics.set_text('') + self.clear_error_msg() + + def _check_escape(self, widget, event): + if event.keyval == Gdk.KEY_Escape: + result = True + result &= self.toolbar.do_escape() + return result + + return False + + def _create_lyrics_box(self): + lyrics_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + + self.title = Gtk.Label() + self.title.set_justify(Gtk.Justification.CENTER) + + self.lyrics = Gtk.Label() + self.lyrics.set_justify(Gtk.Justification.LEFT) + self.lyrics.set_property("margin_left", 50) + self.lyrics.set_property("margin_right", 50) + self.lyrics.set_line_wrap(True) + + self.status = Gtk.Label() + self.status.set_justify(Gtk.Justification.CENTER) + self.status.set_property("margin_left", 50) + self.status.set_property("margin_right", 50) + + self.message = Gtk.Label() + self.message.set_justify(Gtk.Justification.CENTER) + self.message.set_property("margin_left", 50) + self.message.set_property("margin_right", 50) + + lyrics_vbox.pack_start(self.title, False, False, 5) + lyrics_vbox.pack_start(self.spinner, False, False, 5) + lyrics_vbox.pack_start(self.lyrics, False, False, 5) + lyrics_vbox.pack_start(self.status, False, False, 5) + lyrics_vbox.pack_start(self.message, False, False, 5) + lyrics_vbox.set_size_request(350, 700) + + return lyrics_vbox + + def load_from_spotify(self): + try: + song, artist = get_current_playing_song() + song_hash = hash((song, artist)) + if self._last_song_hash == song_hash: + return + self._last_song_hash = song_hash + self.toolbar.info.set_song(song, artist) + except Exception as e: + print(e) + self.clear_box() + self.show_error_msg(Error.Spotify.title, Error.Spotify.message) + return + self.start_fetching_song_data(song, artist) + + def start_fetching_song_data(self, song_name, artist_name=''): + self.clear_box() + + t = Thread(target=self._load_song_data, args=[song_name, artist_name]) + t.daemon = True + t.start() + + def _load_song_data(self, song_name, artist_name): + self.spinner.start() + try: + song, artist, cover, lyrics_url = get_song_data( + song_name, artist_name) + self.toolbar.info.set_song(song, artist) + self.toolbar.btn_search.set_active(False) + + lt = Thread(target=lambda x: self.lyrics.set_text( + get_lyrics(lyrics_url)), args=[None]) + lt.start() + + ct = Thread(target=lambda x: self.toolbar.info.set_cover( + get_pixbuf(cover)), args=[None]) + ct.start() + + lt.join() + ct.join() + + self.spinner.stop() + except RequestException: + self.show_error_msg(Error.Request.title, Error.Request.message) + self._last_song_hash = '' + except Exception as e: + print(e) + self.show_error_msg(Error.NotFound.title, Error.NotFound.message) + + self.spinner.stop() + + +class Toolbar(Gtk.HeaderBar): + def __init__(self, lyric_request_callback, cancel_spotify_callback, + lyric_download_callback, accelerators, spinner, **properties): + super(Toolbar, self).__init__(**properties) + self.set_show_close_button(True) + self.set_custom_title(None) + + self.request_lyric = lyric_request_callback + self.cancel_spotify = cancel_spotify_callback + self.download_lyric = lyric_download_callback + self.accelerators = accelerators + + self.an = Gtk.Entry() + self.searchbar = None + self.spinner = spinner + + self.info = ToolbarInfo() + self.pack_start(self.info) + + self.btn_search = self._create_search_btn() + self.pack_end(self.btn_search) + + self.btn_spotify = self._create_spotify_btn() + self.pack_end(self.btn_spotify) + + self.btn_download = self._create_download_btn() + self.pack_end(self.btn_download) + + def do_escape(self): + if self.btn_search.get_active(): + self.btn_search.emit('clicked') + return True + + return False + + def show_search_bar(self): + self.info.hide() + if self.searchbar: + self.searchbar.show() + + def show_info_bar(self): + if self.searchbar: + self.searchbar.hide() + self.info.show() + + def _on_search_btn_clicked(self, widget): + if not self.searchbar: + self.searchbar = self._create_search_bar() + self.pack_start(self.searchbar) + + if self.btn_search.get_active(): + self.show_search_bar() + self.searchbar.set_property('margin_top', 3) + self.searchbar.set_property('margin_bottom', 2) + self.searchbar.grab_focus() + else: + self.show_info_bar() + + def _on_download_btn_clicked(self, widget): + self.download_lyric() + + def _on_spotify_btn_clicked(self, widget): + if self.btn_spotify.get_active(): + self.info.clear() + self.show_info_bar() + self.request_lyric(True, '') + self.btn_search.set_active(False) + else: + self.cancel_spotify() + + def _on_key_release(self, widget, ev, data=None): + if ev.keyval == Gdk.KEY_Return: + if len(self.searchbar.get_text()) > 0: + lst = self.searchbar.get_text().split('/') + self.request_lyric( + False, unicode(lst[0].strip()), + unicode(lst[1].strip()) if len(lst) > 1 else '') + self.btn_spotify.set_active(False) + self.spinner.grab_focus() + + def _create_download_btn(self): + btn_download = Gtk.Button() + btn_download.add(Gtk.Image.new_from_gicon( + Gio.ThemedIcon(name="folder-download-symbolic.symbolic"), Gtk.IconSize.BUTTON)) + btn_download.connect('clicked', self._on_download_btn_clicked) + bind_accelerator(self.accelerators, btn_download, 'd') + + return btn_download + + def _create_search_btn(self): + btn_search = Gtk.ToggleButton() + btn_search.add(Gtk.Image.new_from_gicon( + Gio.ThemedIcon(name="edit-find-symbolic"), Gtk.IconSize.BUTTON)) + btn_search.connect('clicked', self._on_search_btn_clicked) + bind_accelerator(self.accelerators, btn_search, 'f') + + return btn_search + + def _create_spotify_btn(self): + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( + comun.SPOTIFY_ICON, 16, 16) + Gtk.IconTheme.add_builtin_icon("spotify-logo-symbolic", -1, pixbuf) + + btn_spotify = Gtk.ToggleButton() + btn_spotify.add(Gtk.Image.new_from_gicon( + Gio.ThemedIcon(name="spotify-logo-symbolic"), Gtk.IconSize.BUTTON)) + btn_spotify.connect('clicked', self._on_spotify_btn_clicked) + bind_accelerator(self.accelerators, btn_spotify, 'o') + + return btn_spotify + + def _create_search_bar(self): + searchbar = Gtk.Entry() + searchbar.connect('key-release-event', self._on_key_release) + searchbar.set_property('margin_top', 2) + + return searchbar + + +class ToolbarInfo(Gtk.Bin): + def __init__(self, **properties): + super(ToolbarInfo, self).__init__(**properties) + + builder = Gtk.Builder() + builder.add_from_file(comun.UIFILE) + + self.infobox = builder.get_object('info') + self.add(self.infobox) + + self.labels_container = builder.get_object('nowplaying_labels') + + self.cover = builder.get_object('cover') + self.song = builder.get_object('title') + self.artist = builder.get_object('artist') + + def set_song(self, song, artist=''): + self.clear() + + self.song.set_text(song) + self.artist.set_text(artist) + + def set_cover(self, cover=None): + self.cover.set_from_pixbuf(cover) + + def clear(self): + self.song.set_text('') + self.artist.set_text('') + self.set_cover(None) diff -Nru instant-lyrics-0.1.0/src/messages.py instant-lyrics-0.3.1/src/messages.py --- instant-lyrics-0.1.0/src/messages.py 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/src/messages.py 2017-05-01 05:15:13.000000000 +0000 @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +class Error: + class NotFound: + title = 'No lyrics :(' + message = 'Requested song is not available on Genius database.' + + class Request: + title = 'Unable to reach to server' + message = 'Connection timeout, Please try again.' + + class Spotify: + title = 'No playing song on spotify' + message = "Could not get current spotify song. \n Either spotify is\ + not running or no song is playing on spotify " diff -Nru instant-lyrics-0.1.0/src/periodicthread.py instant-lyrics-0.3.1/src/periodicthread.py --- instant-lyrics-0.1.0/src/periodicthread.py 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/src/periodicthread.py 2017-05-01 05:14:38.000000000 +0000 @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import logging +import threading + + +class PeriodicThread(object): + """ + Python periodic Thread using Timer with instant cancellation + """ + + def __init__(self, callback=None, period=1, name=None, *args, **kwargs): + self.name = name + self.args = args + self.kwargs = kwargs + self.callback = callback + self.period = period + self.stop = False + self.current_timer = None + self.schedule_lock = threading.Lock() + self.first_thread = None + + def start(self): + """ + Mimics Thread standard start method + """ + # self.first_thread = threading.Thread(None, self._first_run, + # self.name, *self.args, **self.kwargs) + # self.first_thread.start() + self.schedule_timer() + + def run(self): + """ + By default run callback. Override it if you want to use inheritance + """ + if self.callback is not None: + self.callback() + + def _run(self): + """ + Run desired callback and then reschedule Timer + (if thread is not stopped) + """ + try: + self.run() + except Exception as e: + print(e) + logging.exception("Exception in running periodic thread") + finally: + with self.schedule_lock: + if not self.stop: + self.schedule_timer() + + def schedule_timer(self): + """ + Schedules next Timer run + """ + self.current_timer = threading.Timer( + self.period, self._run, *self.args, **self.kwargs) + if self.name: + self.current_timer.name = self.name + + self.current_timer.start() + + def _first_run(self): + self._run() + self.schedule_timer() + + def cancel(self): + """ + Mimics Timer standard cancel method + """ + with self.schedule_lock: + self.stop = True + if self.current_timer is not None: + self.current_timer.cancel() + + def join(self): + """ + Mimics Thread standard join method + """ + self.current_timer.join() + + def isAlive(self): + return (self.first_thread is not None and + self.first_thread.isAlive()) or ( + self.current_timer is not None and + self.current_timer.isAlive()) diff -Nru instant-lyrics-0.1.0/src/songdata.py instant-lyrics-0.3.1/src/songdata.py --- instant-lyrics-0.1.0/src/songdata.py 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/src/songdata.py 2017-05-01 05:47:14.000000000 +0000 @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import gi +try: + gi.require_version('GdkPixbuf', '2.0') +except Exception as e: + print(e) + exit(-1) +from gi.repository import GdkPixbuf +import lxml.html as lh +import requests + + +GENIUS_TOKEN = '\ +M36xh_HEiv83IjGTvJmZPy6I2hLhW1TBEdDzpgC1UlB-C9r9jK5juvFzMdo4cRc3' + + +def get_song_data(song_name, artist_name=''): + headers = {'Authorization': 'Bearer ' + GENIUS_TOKEN} + params = {'q': song_name.strip()} + json = requests.get('http://api.genius.com/search', params=params, + headers=headers).json() + for hit in json["response"]["hits"]: + if artist_name == '' or \ + hit["result"]["primary_artist"]["name"].strip().lower() == \ + artist_name.strip().lower(): + song_info = hit['result'] + break + song = song_info['title'] + artist = song_info['primary_artist']['name'] + cover_url = song_info['song_art_image_thumbnail_url'] + lyrics_url = song_info['url'] + return song, artist, cover_url, lyrics_url + # + # print query + # response = requests.get('https://www.googleapis.com/customsearch/v1', + # params=params).json() + # print response + # items = response['items'] + # print items[0] + # url = items[0]['link'] + # print url + # + # page = requests.get(url) + # + # html = BeautifulSoup(page.text, "html.parser") + # + # # remove script tags that they put in the middle of the lyrics + # [h.extract() for h in html('script')] + # + # # at least Genius is nice and has a tag called 'lyrics'! + # lyrics = html.select("lyrics p")[0].text + # + # cover_url = html.find('img', class_='cover_art-image')['src'] + # song = html.find('h1', class_='song_header-primary_info-title').text + # artist = html.find( + # 'a', class_='song_header-primary_info-primary_artist').text + # + # return song, artist, cover_url, lyrics + + +''' +def get_lyrics2(url): + print('============') + print(url) + page = requests.get(url) + + html = BeautifulSoup(page.text, "html.parser") + + # remove script tags that they put in the middle of the lyrics + [h.extract() for h in html('script')] + + return html.select("lyrics p")[0].text +''' + + +def get_lyrics(url): + phrases = [] + page = requests.get(url) + doc = lh.fromstring(page.text) + for div in doc.cssselect('.lyrics'): + phrases.append(div.text_content().strip()) + ans = ''.join(phrases) + return ans + + +def get_pixbuf(cover_url, cover_image_size=30): + loader = GdkPixbuf.PixbufLoader() + loader.set_size(cover_image_size, cover_image_size) + loader.write(requests.get(cover_url).content) + loader.close() + + return loader.get_pixbuf() + + +if __name__ == '__main__': + # get_song_data('shape of you ed sheeran') + print(get_lyrics( + 'https://genius.com/Amaia-montero-los-abrazos-rotos-lyrics')) diff -Nru instant-lyrics-0.1.0/src/spotify.py instant-lyrics-0.3.1/src/spotify.py --- instant-lyrics-0.1.0/src/spotify.py 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/src/spotify.py 2017-05-01 06:33:33.000000000 +0000 @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import dbus + + +def get_current_playing_song(): + session_bus = dbus.SessionBus() + ans = get_current_playing_song_spotify(session_bus) + if ans is None: + ans = get_current_playing_song_rhythmbox(session_bus) + if ans is None: + raise(Exception) + return ans + + +def get_current_playing_song_spotify(session_bus): + session_bus = dbus.SessionBus() + try: + spotify_bus = session_bus.get_object("org.mpris.MediaPlayer2.spotify", + "/org/mpris/MediaPlayer2") + spotify_properties = dbus.Interface( + spotify_bus, "org.freedesktop.DBus.Properties") + metadata = spotify_properties.Get("org.mpris.MediaPlayer2.Player", + "Metadata") + song = str(metadata['xesam:title']) + artist = str(metadata['xesam:artist'][0]) + return song, artist + except Exception as e: + print(e) + return None + + +def get_current_playing_song_rhythmbox(session_bus): + session_bus = dbus.SessionBus() + try: + rhythmbox_bus = session_bus.get_object( + "org.mpris.MediaPlayer2.rhythmbox", + "/org/mpris/MediaPlayer2") + rhythmbox_properties = dbus.Interface( + rhythmbox_bus, "org.freedesktop.DBus.Properties") + metadata = rhythmbox_properties.Get("org.mpris.MediaPlayer2.Player", + "Metadata") + song = str(metadata['xesam:title']) + artist = str(metadata['xesam:artist'][0]) + return song, artist + except Exception as e: + print(e) + return None + + +if __name__ == '__main__': + print(get_current_playing_song()) \ No newline at end of file diff -Nru instant-lyrics-0.1.0/src/ui/ToolbarInfo.ui instant-lyrics-0.3.1/src/ui/ToolbarInfo.ui --- instant-lyrics-0.1.0/src/ui/ToolbarInfo.ui 1970-01-01 00:00:00.000000000 +0000 +++ instant-lyrics-0.3.1/src/ui/ToolbarInfo.ui 2017-05-01 04:56:18.000000000 +0000 @@ -0,0 +1,79 @@ + + + + + + False + start + + + + True + False + start + center + + + True + center + center + 10 + + + False + True + 0 + + + + + True + False + center + vertical + + + True + False + start + end + True + + + + False + False + 0 + + + + + True + False + start + end + True + + + + False + False + 1 + + + + + False + True + 2 + + + + + + diff -Nru instant-lyrics-0.1.0/src/utils.py instant-lyrics-0.3.1/src/utils.py --- instant-lyrics-0.1.0/src/utils.py 2017-04-16 06:09:25.000000000 +0000 +++ instant-lyrics-0.3.1/src/utils.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import os -import configparser -from comun import CONFIG_PATH - - -def create_default_config(): - if not os.path.isdir(os.path.dirname(CONFIG_PATH)): - os.makedirs(os.path.dirname(CONFIG_PATH)) - - Config = configparser.ConfigParser() - Config.add_section('Main') - Config.set('Main', "window width", "350") - Config.set('Main', "window height", "650") - - with open(CONFIG_PATH, 'w') as config_file: - Config.write(config_file) - - -def get_config(): - if not os.path.isfile(CONFIG_PATH): - create_default_config() - Config = configparser.ConfigParser() - Config.read(CONFIG_PATH) - return Config - - -if __name__ == '__main__': - print(CONFIG_PATH) - if not os.path.isdir(os.path.dirname(CONFIG_PATH)): - os.makedirs(os.path.dirname(CONFIG_PATH)) - config = get_config() - print(config) diff -Nru instant-lyrics-0.1.0/src/windows.py instant-lyrics-0.3.1/src/windows.py --- instant-lyrics-0.1.0/src/windows.py 2017-04-16 06:19:14.000000000 +0000 +++ instant-lyrics-0.3.1/src/windows.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,274 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import gi -try: - gi.require_version('Gtk', '3.0') -except Exception as e: - print(e) - exit(-1) -from gi.repository import Gtk, Gdk - -import dbus -import threading -from lyrics import get_lyrics -from comun import CONFIG_PATH -import comun -import utils - - -class LyricsWindow(Gtk.Window): - - def __init__(self, type, app): - Gtk.Window.__init__(self, title="Lyrics") - self.set_icon_from_file(comun.ICON) - self.set_border_width(20) - self.set_default_size( - int(app.Config.get('Main', 'window width')), - int(app.Config.get('Main', 'window height'))) - self.set_position(Gtk.WindowPosition.CENTER) - - self.main_box = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=6) - self.main_box.set_size_request( - int(app.Config.get('Main', 'window width')), - int(app.Config.get('Main', 'window height'))) - - if(type == "get"): - entry_hbox = self.create_input_box() - self.main_box.pack_start(entry_hbox, False, False, 10) - - lyrics_vbox = self.create_lyrics_box(app) - self.main_box.pack_start(lyrics_vbox, True, True, 0) - - scrolled = Gtk.ScrolledWindow() - scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - scrolled.add(self.main_box) - - self.add(scrolled) - self.show_all() - - def on_key_release(self, widget, ev, data=None): - if ev.keyval == Gdk.KEY_Return: - self.fetch_lyrics() - - def create_input_box(self): - entry_hbox = Gtk.Box( - orientation=Gtk.Orientation.HORIZONTAL, spacing=50) - entry_hbox.set_property("margin", 10) - - self.input = Gtk.Entry() - self.input.set_text("song/artist") - self.input.connect("key-release-event", self.on_key_release) - entry_hbox.pack_start(self.input, True, True, 0) - - submit = Gtk.Button.new_with_label("Get Lyrics") - submit.connect("clicked", self.fetch_lyrics) - entry_hbox.pack_start(submit, True, True, 0) - - return entry_hbox - - def create_lyrics_box(self, app): - lyrics_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - - self.title = Gtk.Label() - self.title.set_justify(Gtk.Justification.CENTER) - - self.lyrics = Gtk.Label() - self.lyrics.set_justify(Gtk.Justification.CENTER) - self.lyrics.set_property("margin_left", 40) - self.lyrics.set_property("margin_right", 40) - self.lyrics.set_line_wrap(True) - - self.spinner = Gtk.Spinner() - - lyrics_vbox.pack_start(self.title, False, False, 5) - lyrics_vbox.pack_start(self.spinner, False, False, 5) - lyrics_vbox.pack_start(self.lyrics, False, False, 5) - lyrics_vbox.set_size_request( - int(app.Config.get('Main', 'window width')), - int(app.Config.get('Main', 'window height'))) - - return lyrics_vbox - - def put_lyrics(self, song): - self.spinner.start() - - self.lyrics.set_text("") - lyrics = get_lyrics(song) - self.lyrics.set_text(lyrics) - - self.spinner.stop() - - def fetch_lyrics(self, source=None): - input = self.input.get_text() - text = "" + input + "" - self.title.set_markup(text) - - thread = threading.Thread( - target=self.put_lyrics, kwargs={'song': input}) - thread.daemon = True - thread.start() - - def get_spotify_song_data(self): - session_bus = dbus.SessionBus() - - spotify_bus = session_bus.get_object( - "org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2") - spotify_properties = dbus.Interface( - spotify_bus, "org.freedesktop.DBus.Properties") - metadata = spotify_properties.Get( - "org.mpris.MediaPlayer2.Player", "Metadata") - - title = metadata['xesam:title'].encode( - 'utf-8').decode('utf-8').replace("&", "&") - artist = metadata['xesam:artist'][0].encode( - 'utf-8').decode('utf-8').replace("&", "&") - return {'title': title, 'artist': artist} - - def get_spotify(self): - - try: - song_data = self.get_spotify_song_data() - song = song_data['title'] - artist = song_data['artist'] - except: - self.title.set_markup("Error") - message = ("Could not get current spotify song\n" - "Either spotify is not running or\n" - "no song is playing on spotify.\n\n" - "Else, report an issue here") - - self.lyrics.set_markup(message) - return - - title = "" + song + "\n" + artist + "" - self.title.set_markup(title) - - self.put_lyrics(song + " " + artist) - -class PreferenceWindow(Gtk.Window): - - def __init__(self, app): - Gtk.Window.__init__(self, title="Instant-Lyrics Prefenreces") - self.set_icon_from_file(comun.ICON) - self.set_border_width(20) - #self.set_default_size(350, 550) - self.set_position(Gtk.WindowPosition.CENTER) - - self.main_box = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=6) - - self.save = Gtk.Button.new_with_label("Save") - self.save.set_sensitive(False) - self.save.connect("clicked", self.save_config, app) - - pref_box = self.create_pref_box(app) - self.main_box.pack_start(pref_box, True, True, 0) - - reset = Gtk.Button.new_with_label("Reset to default") - reset.connect("clicked", self.reset_config, app) - - button_hbox = Gtk.Box(spacing=10) - button_hbox.pack_start(reset, True, True, 0) - button_hbox.pack_start(self.save, True, True, 0) - - self.message = Gtk.Label() - - self.main_box.pack_start(button_hbox, False, False, 0) - self.main_box.pack_start(self.message, True, True, 0) - - self.add(self.main_box) - self.show_all() - - def create_pref_box(self, app): - listbox = Gtk.ListBox() - listbox.set_selection_mode(Gtk.SelectionMode.NONE) - - row = Gtk.ListBoxRow() - hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50) - row.add(hbox) - width = Gtk.Label("Lyrics window width", xalign=0) - self.width_val = Gtk.Entry() - self.width_val.set_text(app.Config.get('Main', 'window width')) - self.width_val.connect("changed", self.entry_change) - - hbox.pack_start(width, True, True, 0) - hbox.pack_start(self.width_val, False, True, 0) - - listbox.add(row) - - row = Gtk.ListBoxRow() - hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50) - row.add(hbox) - height = Gtk.Label("Lyrics window height", xalign=0) - self.height_val = Gtk.Entry() - self.height_val.set_text(app.Config.get('Main', 'window height')) - self.height_val.connect("changed", self.entry_change) - - - hbox.pack_start(height, True, True, 0) - hbox.pack_start(self.height_val, False, True, 0) - - listbox.add(row) - - """ TODO: autostart - row = Gtk.ListBoxRow() - hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50) - row.add(hbox) - label = Gtk.Label("Auto start", xalign=0) - self.switch = Gtk.Switch() - self.switch.connect("state-set", self.entry_change) - self.switch.props.valign = Gtk.Align.CENTER - switch_val = app.Config.getboolean('Main', 'auto start') - if(switch_val): - self.switch.set_active(True) - else: - self.switch.set_active(False) - - hbox.pack_start(label, True, True, 0) - hbox.pack_start(self.switch, False, True, 0) - - listbox.add(row) - """ - - return listbox - - def save_config(self, source, *arg): - self.save.set_sensitive(False) - self.message.set_markup("") - app = arg[0] - - new_width = self.width_val.get_text() - new_height = self.height_val.get_text() - - if(new_width.isdigit() and new_height.isdigit()): - app.Config.set('Main', "window width", new_width) - app.Config.set('Main', "window height", new_height) - - with open(CONFIG_PATH, 'w') as config_file: - app.Config.write(config_file) - - return - - msg = ("Invalid values of height and width\n" - "Please add valid positive integers") - - self.show_message(msg) - - def entry_change(self, source): - self.save.set_sensitive(True) - - def reset_config(self, source, *arg): - utils.create_default_config() - app = arg[0] - app.Config = utils.get_config() - - self.width_val.set_text(app.Config.get('Main', 'window width')) - self.height_val.set_text(app.Config.get('Main', 'window height')) - self.save.set_sensitive(False) - - def show_message(self, msg): - self.message.set_markup(msg)