From 510615a520f4e3b51f8e52ba4a3b867ccb47ace3 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Sat, 1 Apr 2017 17:55:36 +0200 Subject: [PATCH 01/25] Make Instant-Lyrics app-agnostic --- src/appIndicator.py | 62 +++++++++++++++++++++++++++++---------------- src/windows.py | 24 ++++++++---------- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/appIndicator.py b/src/appIndicator.py index 632c05b..6f6853c 100644 --- a/src/appIndicator.py +++ b/src/appIndicator.py @@ -11,48 +11,66 @@ from . import utils from src.settings import APPINDICATOR_ID, CONFIG_PATH +import dbus +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) class AppIndicator(): - def __init__(self): signal.signal(signal.SIGINT, signal.SIG_DFL) - - indicator = appindicator.Indicator.new(APPINDICATOR_ID, utils.get_icon_path( - '../icons/instant-lyrics-24.png'), appindicator.IndicatorCategory.SYSTEM_SERVICES) - indicator.set_status(appindicator.IndicatorStatus.ACTIVE) - indicator.set_menu(self.build_menu()) - + + bus = dbus.SessionBus() + + bus.add_signal_receiver(self.build_menu, path="/org/mpris/MediaPlayer2") + + self.indicator = appindicator.Indicator.new(APPINDICATOR_ID, utils.get_icon_path( +'../icons/instant-lyrics-24.png'), appindicator.IndicatorCategory.SYSTEM_SERVICES) + self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) + self.build_menu() + self.Config = utils.get_config() + Gtk.main() - def build_menu(self): - menu = Gtk.Menu() + def list_apps(self): + apps = [] + session_bus = dbus.SessionBus() + for service in session_bus.list_names(): + if service[:22] == "org.mpris.MediaPlayer2": + apps.append(service[23:]) + + return apps - get_lyrics = Gtk.MenuItem('Get Lyrics') - get_lyrics.connect('activate', self.fetch_lyrics) + def build_menu(self, *args): + menu = Gtk.Menu() - spotify_lyrics = Gtk.MenuItem('Spotify Lyrics') - spotify_lyrics.connect('activate', self.spotify_lyrics) + item_lyrics = Gtk.MenuItem('Custom Lyrics') + item_lyrics.connect('activate', self.fetch_lyrics) + menu.append(item_lyrics) + apps = self.list_apps() + for app in apps: + current = Gtk.MenuItem(app.capitalize()+" lyrics") + current.connect('activate', self.app_lyrics, app) + menu.insert(current, 1) + preferences = Gtk.MenuItem('Preferences') preferences.connect('activate', self.preferences) + menu.append(preferences) 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_quit) + menu.show_all() - return menu + self.indicator.set_menu(menu) def fetch_lyrics(self, source): win = LyricsWindow("get", self) - def spotify_lyrics(self, source): - win = LyricsWindow("spotify", self) - thread = threading.Thread(target=win.get_spotify) + def app_lyrics(self, source, app): + win = LyricsWindow("app", self) + thread = threading.Thread(target=win.get_lyrics, args=(app,)) thread.daemon = True thread.start() @@ -60,4 +78,4 @@ def preferences(self, source): win = PreferenceWindow(self) def quit(self, source): - Gtk.main_quit() \ No newline at end of file + Gtk.main_quit() diff --git a/src/windows.py b/src/windows.py index e842706..1ef2ebb 100644 --- a/src/windows.py +++ b/src/windows.py @@ -55,7 +55,7 @@ def create_input_box(self): entry_hbox.set_property("margin", 10) self.input = Gtk.Entry() - self.input.set_text("song/artist") + self.input.set_placeholder_text("song/artist") self.input.connect("key-release-event", self.on_key_release) entry_hbox.pack_start(self.input, True, True, 0) @@ -107,14 +107,14 @@ def fetch_lyrics(self, source=None): thread.daemon = True thread.start() - def get_spotify_song_data(self): + def get_song_data(self, app): 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( + app_bus = session_bus.get_object( + "org.mpris.MediaPlayer2."+app, "/org/mpris/MediaPlayer2") + app_properties = dbus.Interface( + app_bus, "org.freedesktop.DBus.Properties") + metadata = app_properties.Get( "org.mpris.MediaPlayer2.Player", "Metadata") title = metadata['xesam:title'].encode( @@ -123,17 +123,15 @@ def get_spotify_song_data(self): 'utf-8').decode('utf-8').replace("&", "&") return {'title': title, 'artist': artist} - def get_spotify(self): - + def get_lyrics(self, app): try: - song_data = self.get_spotify_song_data() + song_data = self.get_song_data(app) 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" + message = ("Could not get current "+app+" song\n" + "no song is playing on "+app+".\n\n" "Else, report an issue here") From ccabe27a0c86fc0964c07db9c55f619edde1a866 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Sat, 1 Apr 2017 18:03:09 +0200 Subject: [PATCH 02/25] - Correct an encoding problem with MetroLyrics lyrics - More coherent HTTP request headers --- src/lyrics.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/lyrics.py b/src/lyrics.py index b6f995c..7c432dd 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -12,12 +12,13 @@ def get_lyrics(song_name): song_name += ' metrolyrics' name = quote_plus(song_name) - hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11' - '(KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11', - 'Accept-Language': 'en-US,en;q=0.8', + hdr = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0', + 'Accept-Encoding': 'gzip, deflate, br', + 'DNT': '1', + 'UPGRADE-INSECURE-REQUESTS': '1', 'Connection': 'keep-alive'} - url = 'http://www.google.com/search?q=' + name + url = 'https://www.google.com/search?q=' + name result = requests.get(url, headers=hdr).text link_start = result.find('http://www.metrolyrics.com') @@ -25,20 +26,15 @@ def get_lyrics(song_name): 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] + link_end = result.find('html', link_start + 1) + 4 + link = result[link_start:link_end] - - lyrics_html = requests.get(link, headers={ - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel' - 'Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, ' - 'like Gecko) Chrome/55.0.2883.95 Safari/537.36' - } - ).text + r = requests.get(link, headers=hdr) + r.encoding = "utf-8" + lyrics_html = r.text soup = BeautifulSoup(lyrics_html, "lxml") raw_lyrics = (soup.findAll('p', attrs={'class': 'verse'})) - paras = [] try: final_lyrics = unicode.join(u'\n', map(unicode, raw_lyrics)) except NameError: From 51c72a17d51bc8952eb50dd4de3278cd3db16924 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Sat, 1 Apr 2017 18:07:06 +0200 Subject: [PATCH 03/25] Add python encoding headers to files --- InstantLyrics.py | 3 +++ src/appIndicator.py | 3 +++ src/lyrics.py | 3 +++ src/settings.py | 3 +++ src/utils.py | 5 ++++- src/windows.py | 3 +++ 6 files changed, 19 insertions(+), 1 deletion(-) diff --git a/InstantLyrics.py b/InstantLyrics.py index 653c693..04b641c 100644 --- a/InstantLyrics.py +++ b/InstantLyrics.py @@ -1,3 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + from src.appIndicator import AppIndicator if __name__ == "__main__": diff --git a/src/appIndicator.py b/src/appIndicator.py index 6f6853c..920a294 100644 --- a/src/appIndicator.py +++ b/src/appIndicator.py @@ -1,3 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + import gi gi.require_version('Gtk', '3.0') gi.require_version('AppIndicator3', '0.1') diff --git a/src/lyrics.py b/src/lyrics.py index 7c432dd..7b00bdc 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -1,3 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + import requests from bs4 import BeautifulSoup import os diff --git a/src/settings.py b/src/settings.py index 55f4d59..31106a7 100644 --- a/src/settings.py +++ b/src/settings.py @@ -1,3 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + import os APPINDICATOR_ID = 'lyricsappindicator' diff --git a/src/utils.py b/src/utils.py index 64c1731..3052b1e 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,3 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + import os try: import configparser @@ -58,4 +61,4 @@ def create_desktop_entry(): file.write(ic) file.write(ex) file.write(cm) - file.write(tm) \ No newline at end of file + file.write(tm) diff --git a/src/windows.py b/src/windows.py index 1ef2ebb..ed79240 100644 --- a/src/windows.py +++ b/src/windows.py @@ -1,3 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk From cbe4082cac0caf5823f32d9b12442e725f781593 Mon Sep 17 00:00:00 2001 From: Nicolas Guichard Date: Sat, 1 Apr 2017 18:10:09 +0200 Subject: [PATCH 04/25] Main app script made executable --- InstantLyrics.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 InstantLyrics.py diff --git a/InstantLyrics.py b/InstantLyrics.py old mode 100644 new mode 100755 From a2a5450c72083950e795795b8fd55e016ab67425 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 19 Sep 2018 12:10:35 +0200 Subject: [PATCH 05/25] refactor --- .gitignore | 2 ++ icons/instant-lyrics.svg | 1 - src/appIndicator.py | 44 ++++++++++++++--------- src/lyrics.py | 19 +++++----- src/utils.py | 18 ++++++---- src/windows.py | 76 ++++++++++++++++++---------------------- 6 files changed, 86 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index c538041..0994cb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/icons/instant-lyrics.svg b/icons/instant-lyrics.svg index 92d2872..2da7891 100644 --- a/icons/instant-lyrics.svg +++ b/icons/instant-lyrics.svg @@ -3,7 +3,6 @@ xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 97.536 97.536;" diff --git a/src/appIndicator.py b/src/appIndicator.py index 920a294..a9facd8 100644 --- a/src/appIndicator.py +++ b/src/appIndicator.py @@ -2,40 +2,48 @@ # -*- coding: utf-8 -*- import gi -gi.require_version('Gtk', '3.0') -gi.require_version('AppIndicator3', '0.1') -from gi.repository import AppIndicator3 as appindicator -from gi.repository import Gtk - import signal import threading from src.windows import LyricsWindow, PreferenceWindow from . import utils -from src.settings import APPINDICATOR_ID, CONFIG_PATH +from src.settings import APPINDICATOR_ID import dbus from dbus.mainloop.glib import DBusGMainLoop + +gi.require_version('Gtk', '3.0') +gi.require_version('AppIndicator3', '0.1') + +from gi.repository import AppIndicator3 as appindicator +from gi.repository import Gtk + + DBusGMainLoop(set_as_default=True) +ICON_PATH = '../icons/instant-lyrics-24.png' +ICON_PATH = utils.get_icon_path(ICON_PATH) # full path -class AppIndicator(): + +class AppIndicator: def __init__(self): signal.signal(signal.SIGINT, signal.SIG_DFL) - + bus = dbus.SessionBus() - + bus.add_signal_receiver(self.build_menu, path="/org/mpris/MediaPlayer2") - self.indicator = appindicator.Indicator.new(APPINDICATOR_ID, utils.get_icon_path( -'../icons/instant-lyrics-24.png'), appindicator.IndicatorCategory.SYSTEM_SERVICES) + self.indicator = appindicator.Indicator.new( + APPINDICATOR_ID, ICON_PATH, + appindicator.IndicatorCategory.SYSTEM_SERVICES) self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE) self.build_menu() - + self.Config = utils.get_config() - + Gtk.main() - def list_apps(self): + @staticmethod + def list_apps(): apps = [] session_bus = dbus.SessionBus() for service in session_bus.list_names(): @@ -53,10 +61,10 @@ def build_menu(self, *args): apps = self.list_apps() for app in apps: - current = Gtk.MenuItem(app.capitalize()+" lyrics") + current = Gtk.MenuItem(app.capitalize() + " lyrics") current.connect('activate', self.app_lyrics, app) menu.insert(current, 1) - + preferences = Gtk.MenuItem('Preferences') preferences.connect('activate', self.preferences) menu.append(preferences) @@ -70,15 +78,19 @@ def build_menu(self, *args): def fetch_lyrics(self, source): win = LyricsWindow("get", self) + win.show_all() def app_lyrics(self, source, app): win = LyricsWindow("app", self) + win.show_all() + thread = threading.Thread(target=win.get_lyrics, args=(app,)) thread.daemon = True thread.start() def preferences(self, source): win = PreferenceWindow(self) + win.show_all() def quit(self, source): Gtk.main_quit() diff --git a/src/lyrics.py b/src/lyrics.py index 7b00bdc..897820a 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -3,7 +3,6 @@ import requests from bs4 import BeautifulSoup -import os try: from urllib.parse import quote_plus @@ -12,23 +11,23 @@ def get_lyrics(song_name): - song_name += ' metrolyrics' name = quote_plus(song_name) - hdr = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0', - 'Accept-Encoding': 'gzip, deflate, br', - 'DNT': '1', - 'UPGRADE-INSECURE-REQUESTS': '1', - 'Connection': 'keep-alive'} + hdr = { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0', + 'Accept-Encoding': 'gzip, deflate, br', + 'DNT': '1', + 'UPGRADE-INSECURE-REQUESTS': '1', + 'Connection': 'keep-alive'} url = 'https://www.google.com/search?q=' + name result = requests.get(url, headers=hdr).text link_start = result.find('http://www.metrolyrics.com') - if(link_start == -1): - return("Lyrics not found on Metrolyrics") - + if (link_start == -1): + return ("Lyrics not found on Metrolyrics") + link_end = result.find('html', link_start + 1) + 4 link = result[link_start:link_end] diff --git a/src/utils.py b/src/utils.py index 3052b1e..475eeef 100644 --- a/src/utils.py +++ b/src/utils.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import os + try: import configparser except ImportError: @@ -9,12 +10,13 @@ from src.settings 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") @@ -22,22 +24,25 @@ def create_default_config(): 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 + def get_icon_path(rel_path): dir_of_py_file = os.path.dirname(__file__) rel_path_to_resource = os.path.join(dir_of_py_file, rel_path) abs_path_to_resource = os.path.abspath(rel_path_to_resource) return abs_path_to_resource + def create_desktop_entry(): - #path of scr folder + # path of scr folder src_path = os.path.dirname(os.path.realpath(__file__)) # path of base folder base_path = os.path.dirname(src_path) @@ -46,12 +51,13 @@ def create_desktop_entry(): v = "Version=1.0\n" tp = "Type=Application\n" nm = "Name=Instant Lyrics\n" - ic = "Icon="+base_path+"/icons/instant-lyrics-256.png\n" - ex = "Exec=python2 "+base_path+"/InstantLyrics.py\n" + ic = "Icon=" + base_path + "/icons/instant-lyrics-256.png\n" + ex = "Exec=python2 " + base_path + "/InstantLyrics.py\n" cm = "Comment=Shows lyrics of songs instantly\n" tm = "Terminal=false\n" - entry_path = os.getenv("HOME") + "/.local/share/applications/instant-lyrics.desktop" + entry_path = os.getenv( + "HOME") + "/.local/share/applications/instant-lyrics.desktop" with open(entry_path, 'w') as file: file.write(entry) diff --git a/src/windows.py b/src/windows.py index ed79240..77307b3 100644 --- a/src/windows.py +++ b/src/windows.py @@ -2,39 +2,35 @@ # -*- coding: utf-8 -*- import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gdk - import dbus import threading -try: - import configparser -except ImportError: - import ConfigParser as configparser + +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gdk from src.lyrics import get_lyrics from src.settings import CONFIG_PATH from . import utils -class LyricsWindow(Gtk.Window): +class LyricsWindow(Gtk.Window): def __init__(self, type, app): Gtk.Window.__init__(self, title="Lyrics") self.set_icon_from_file( - utils.get_icon_path('../icons/instant-lyrics-32.png')) + utils.get_icon_path('../icons/instant-lyrics-32.png')) self.set_border_width(20) self.set_default_size( - int(app.Config.get('Main', 'window width')), - int(app.Config.get('Main', 'window height'))) + 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'))) + int(app.Config.get('Main', 'window width')), + int(app.Config.get('Main', 'window height'))) - if(type == "get"): + if (type == "get"): entry_hbox = self.create_input_box() self.main_box.pack_start(entry_hbox, False, False, 10) @@ -46,7 +42,6 @@ def __init__(self, type, app): 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: @@ -86,8 +81,8 @@ def create_lyrics_box(self, app): 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'))) + int(app.Config.get('Main', 'window width')), + int(app.Config.get('Main', 'window height'))) return lyrics_vbox @@ -114,7 +109,7 @@ def get_song_data(self, app): session_bus = dbus.SessionBus() app_bus = session_bus.get_object( - "org.mpris.MediaPlayer2."+app, "/org/mpris/MediaPlayer2") + "org.mpris.MediaPlayer2." + app, "/org/mpris/MediaPlayer2") app_properties = dbus.Interface( app_bus, "org.freedesktop.DBus.Properties") metadata = app_properties.Get( @@ -133,11 +128,11 @@ def get_lyrics(self, app): artist = song_data['artist'] except: self.title.set_markup("Error") - message = ("Could not get current "+app+" song\n" - "no song is playing on "+app+".\n\n" - "Else, report an issue here") + message = ("Could not get current " + app + " song\n" + "no song is playing on " + app + ".\n\n" + "Else, report an issue here") self.lyrics.set_markup(message) return @@ -147,13 +142,14 @@ def get_lyrics(self, app): self.put_lyrics(song + " " + artist) -class PreferenceWindow(Gtk.Window): +class PreferenceWindow(Gtk.Window): def __init__(self, app): Gtk.Window.__init__(self, title="Instant-Lyrics Prefenreces") - self.set_icon_from_file(utils.get_icon_path('../icons/instant-lyrics-32.png')) + self.set_icon_from_file( + utils.get_icon_path('../icons/instant-lyrics-32.png')) self.set_border_width(20) - #self.set_default_size(350, 550) + # self.set_default_size(350, 550) self.set_position(Gtk.WindowPosition.CENTER) self.main_box = Gtk.Box( @@ -183,7 +179,6 @@ def __init__(self, app): 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() @@ -191,7 +186,7 @@ def create_pref_box(self, app): row = Gtk.ListBoxRow() hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50) - row.add(hbox) + 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')) @@ -199,7 +194,7 @@ def create_pref_box(self, app): hbox.pack_start(width, True, True, 0) hbox.pack_start(self.width_val, False, True, 0) - + listbox.add(row) row = Gtk.ListBoxRow() @@ -210,10 +205,9 @@ def create_pref_box(self, app): 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 @@ -235,7 +229,7 @@ def create_pref_box(self, app): listbox.add(row) """ - + return listbox def save_config(self, source, *arg): @@ -246,17 +240,17 @@ def save_config(self, source, *arg): new_width = self.width_val.get_text() new_height = self.height_val.get_text() - if(new_width.isdigit() and new_height.isdigit()): + 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") + "Please add valid positive integers") self.show_message(msg) @@ -274,12 +268,12 @@ def reset_config(self, source, *arg): def create_desktop_entry(self, source): utils.create_desktop_entry() - msg = ("Desktop entry created. You can now start the\n" - "application from your Applications Launcher.\n\n" - "If you ever change the location " - "of the Instant-Lyrics\nfolder, you will " - "need to create the Desktop Entry\nfrom " - "here again.") + msg = ("Desktop entry created. You can now start the\n" + "application from your Applications Launcher.\n\n" + "If you ever change the location " + "of the Instant-Lyrics\nfolder, you will " + "need to create the Desktop Entry\nfrom " + "here again.") self.show_message(msg) From b673aab4ddd4aced8094e1a7457972285586023d Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 19 Sep 2018 12:11:20 +0200 Subject: [PATCH 06/25] fix shebang lines --- InstantLyrics.py | 4 ++-- src/appIndicator.py | 14 +++++++------- src/lyrics.py | 4 ++-- src/settings.py | 4 ++-- src/utils.py | 4 ++-- src/windows.py | 9 +++++---- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/InstantLyrics.py b/InstantLyrics.py index 04b641c..850dd7e 100755 --- a/InstantLyrics.py +++ b/InstantLyrics.py @@ -1,5 +1,5 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- +# !/usr/bin/python3 +# coding: utf-8 from src.appIndicator import AppIndicator diff --git a/src/appIndicator.py b/src/appIndicator.py index a9facd8..b41414a 100644 --- a/src/appIndicator.py +++ b/src/appIndicator.py @@ -1,17 +1,17 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- +# !/usr/bin/python3 +# coding: utf-8 -import gi import signal import threading -from src.windows import LyricsWindow, PreferenceWindow -from . import utils -from src.settings import APPINDICATOR_ID - import dbus +import gi from dbus.mainloop.glib import DBusGMainLoop +from src.settings import APPINDICATOR_ID +from src.windows import LyricsWindow, PreferenceWindow +from . import utils + gi.require_version('Gtk', '3.0') gi.require_version('AppIndicator3', '0.1') diff --git a/src/lyrics.py b/src/lyrics.py index 897820a..b1b785b 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -1,5 +1,5 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- +# !/usr/bin/python3 +# coding: utf-8 import requests from bs4 import BeautifulSoup diff --git a/src/settings.py b/src/settings.py index 31106a7..c9a559c 100644 --- a/src/settings.py +++ b/src/settings.py @@ -1,5 +1,5 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- +# !/usr/bin/python3 +# coding: utf-8 import os diff --git a/src/utils.py b/src/utils.py index 475eeef..9807d8b 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,5 +1,5 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- +# !/usr/bin/python3 +# coding: utf-8 import os diff --git a/src/windows.py b/src/windows.py index 77307b3..7bc5e5e 100644 --- a/src/windows.py +++ b/src/windows.py @@ -1,10 +1,11 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- +# !/usr/bin/python3 +# coding: utf-8 -import gi -import dbus import threading +import dbus +import gi + gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk From 55c92a037a44abd1be7a9d20b377d005601fd40c Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 19 Sep 2018 12:13:41 +0200 Subject: [PATCH 07/25] fix gitignore --- .gitignore | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++-- _config.yml | 1 - 2 files changed, 212 insertions(+), 7 deletions(-) delete mode 100644 _config.yml diff --git a/.gitignore b/.gitignore index 0994cb6..a921afa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,60 @@ .idea/ +tmp/ +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml + +# Sensitive or high-churn files: +.idea/dataSources/ +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + + +### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -22,7 +77,6 @@ lib64/ parts/ sdist/ var/ -wheels/ *.egg-info/ .installed.cfg *.egg @@ -78,14 +132,11 @@ target/ # celery beat schedule file celerybeat-schedule -# SageMath parsed files -*.sage.py - # dotenv .env # virtualenv -.venv +.venv/ venv/ ENV/ @@ -93,4 +144,159 @@ ENV/ .spyderproject # Rope project settings -.ropeproject \ No newline at end of file +.ropeproject + + +### Django ### +*.pyc +db.sqlite3 +media + + +### Eclipse ### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml + +# Sensitive or high-churn files: +.idea/dataSources/ +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + + +### Intellij+iml ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: + +# Sensitive or high-churn files: + +# Gradle: + +# Mongo Explorer plugin: + +## File-based project format: + +## Plugin-specific files: + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +### Intellij+iml Patch ### +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 2f7efbe..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-minimal \ No newline at end of file From 0c9aafadc98bc8bdcec83d49d9e01985d5c1603d Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 19 Sep 2018 12:32:21 +0200 Subject: [PATCH 08/25] pretty readme --- README.md | 71 +++++++++++++++++++++---------------------------------- 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index f6a38e0..7f7347b 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,38 @@ -# Instant-Lyrics - -Instantly fetches the lyrics of the currently playing spotify song, or any song, and displays it on a window. - -A linux application with a very convinient GUI. Build with Python Gtk+3 (gi). +
+

Instant-Lyrics

+

Instantly fetches the lyrics of the currently playing song and displays it on a window.

+A linux application with a very convenient GUI. Built with Python 3 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) - +# Working example +![Working](https://cloud.githubusercontent.com/assets/6123105/23824730/e0e0829e-06a1-11e7-8d57-3235c4266f2c.gif) # Compatibility - -* Python 2/3 - -* Linux - +- Python 3 +- Linux # Installation -## From Source - -### Requirements - -* python-gi (PyGObject) - -* AppIndicator3 +### system requirements (use `apt`, `pacman`, `dnf` ... your package manager): +- python-gi (PyGObject) +- python-dbus +- AppIndicator3 -* python-dbus - -* requests - -* beautifulsoup4 - -* lxml +### python requirements (use `pip2` or `pip3`): +- requests +- beautifulsoup4 +- lxml First, install the requirements: ### For Ubuntu/Debian based systems: -``` sh +``` bash sudo apt install python-gi python-dbus gir1.2-appindicator3-0.1 python-requests python-bs4 python-lxml ``` @@ -50,26 +40,23 @@ sudo apt install python-gi python-dbus gir1.2-appindicator3-0.1 python-requests ### For Arch users -``` sh +``` bash sudo pacman -S python2-dbus python2-requests python2-lxml python2-beautifulsoup4 python2-gobject libappindicator-gtk3 ``` ### Fedora -``` sh +``` bash sudo dnf install dbus-python python-gobject libappindicator-gtk3 python2-requests python-beautifulsoup4 python2-lxml ``` -## Install from source +Then, enter the commands: -After you've installed the dependencies, open terminal and go to the directory where you want to install. Enter the commands: - -``` sh +``` bash git clone https://github.com/bhrigu123/Instant-Lyrics.git - cd Instant-Lyrics/ - -python InstantLyrics.py +python InstantLyrics.py # `python InstantLyrics.py &` to keep it running in +background ``` The icon will appear in the system tray (indicator panel). You can start using the application from there. @@ -79,9 +66,7 @@ The icon will appear in the system tray (indicator panel). You can start using t # Creating a launcher shortcut If you have installed from source, you can go to **Preferences** from the menu options, and click on the button `Create Desktop Entry`. - You should be able to see the `Instant Lyrics` application shortcut in your launcher menu. - You can also find several manual ways of doing so from the web. ![Launcher](https://cloud.githubusercontent.com/assets/6123105/23824317/4735e83e-069a-11e7-8b1e-2814632bb3aa.jpeg) @@ -90,12 +75,10 @@ You can also find several manual ways of doing so from the web. # Contribution Create an issue to discuss the changes/modifications before sending a PR. -====== -## Icon Credits +# Icon Credits Icon made by [Freepik](http://www.freepik.com/) from www.flaticon.com -====== - +# License ## The MIT License > Copyright (c) 2017 Bhrigu Srivastava http://bhrigu.me From 3eb81ba14267dde20010ad8e1cbca93ce6d61396 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 19 Sep 2018 12:55:09 +0200 Subject: [PATCH 09/25] add default icon getter --- src/appIndicator.py | 27 +++++++++++++-------------- src/utils.py | 4 ++++ src/windows.py | 7 +++---- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/appIndicator.py b/src/appIndicator.py index b41414a..2a82fee 100644 --- a/src/appIndicator.py +++ b/src/appIndicator.py @@ -10,6 +10,7 @@ from src.settings import APPINDICATOR_ID from src.windows import LyricsWindow, PreferenceWindow +from utils import get_default_icon_path from . import utils gi.require_version('Gtk', '3.0') @@ -20,8 +21,17 @@ DBusGMainLoop(set_as_default=True) -ICON_PATH = '../icons/instant-lyrics-24.png' -ICON_PATH = utils.get_icon_path(ICON_PATH) # full path +ICON_PATH = get_default_icon_path() # full path + + +def list_music_apps(): + apps = [] + session_bus = dbus.SessionBus() + for service in session_bus.list_names(): + if service[:22] == "org.mpris.MediaPlayer2": + apps.append(service[23:]) + + return apps class AppIndicator: @@ -42,16 +52,6 @@ def __init__(self): Gtk.main() - @staticmethod - def list_apps(): - apps = [] - session_bus = dbus.SessionBus() - for service in session_bus.list_names(): - if service[:22] == "org.mpris.MediaPlayer2": - apps.append(service[23:]) - - return apps - def build_menu(self, *args): menu = Gtk.Menu() @@ -59,8 +59,7 @@ def build_menu(self, *args): item_lyrics.connect('activate', self.fetch_lyrics) menu.append(item_lyrics) - apps = self.list_apps() - for app in apps: + for app in list_music_apps(): current = Gtk.MenuItem(app.capitalize() + " lyrics") current.connect('activate', self.app_lyrics, app) menu.insert(current, 1) diff --git a/src/utils.py b/src/utils.py index 9807d8b..d0aeb60 100644 --- a/src/utils.py +++ b/src/utils.py @@ -41,6 +41,10 @@ def get_icon_path(rel_path): return abs_path_to_resource +def get_default_icon_path(): + return get_icon_path('../icons/instant-lyrics-32.png') + + def create_desktop_entry(): # path of scr folder src_path = os.path.dirname(os.path.realpath(__file__)) diff --git a/src/windows.py b/src/windows.py index 7bc5e5e..eac308f 100644 --- a/src/windows.py +++ b/src/windows.py @@ -17,8 +17,7 @@ class LyricsWindow(Gtk.Window): def __init__(self, type, app): Gtk.Window.__init__(self, title="Lyrics") - self.set_icon_from_file( - utils.get_icon_path('../icons/instant-lyrics-32.png')) + self.set_icon_from_file(utils.get_default_icon_path()) self.set_border_width(20) self.set_default_size( int(app.Config.get('Main', 'window width')), @@ -31,7 +30,8 @@ def __init__(self, type, app): int(app.Config.get('Main', 'window width')), int(app.Config.get('Main', 'window height'))) - if (type == "get"): + if type == "get": + self.input = Gtk.Entry() entry_hbox = self.create_input_box() self.main_box.pack_start(entry_hbox, False, False, 10) @@ -53,7 +53,6 @@ def create_input_box(self): orientation=Gtk.Orientation.HORIZONTAL, spacing=50) entry_hbox.set_property("margin", 10) - self.input = Gtk.Entry() self.input.set_placeholder_text("song/artist") self.input.connect("key-release-event", self.on_key_release) entry_hbox.pack_start(self.input, True, True, 0) From 2151966ab52187f92b22d32aee10ef3fb66f39d1 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Tue, 25 Sep 2018 09:54:37 +0200 Subject: [PATCH 10/25] fix lyrics params --- src/lyrics.py | 69 ++++++++++++++++++++++++++++++++------------------ src/utils.py | 23 ++++++++++------- src/windows.py | 16 +++++------- 3 files changed, 64 insertions(+), 44 deletions(-) diff --git a/src/lyrics.py b/src/lyrics.py index b1b785b..d22af1e 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -1,48 +1,67 @@ # !/usr/bin/python3 # coding: utf-8 +import urllib.parse as urlparse +from urllib.parse import urlencode + import requests from bs4 import BeautifulSoup -try: - from urllib.parse import quote_plus -except ImportError: - from urllib import quote_plus +HEADERS = { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) ' + 'Gecko/20100101 Firefox/52.0', + 'UPGRADE-INSECURE-REQUESTS': '1', + 'Connection': 'keep-alive' +} +BASE_URL = "https://www.google.com/search" + +def add_params_to_url(url, params): + """ + :param url: str + Url to add params to + :param params: {} + List of params to add to url + :return: void + Adds params to url + """ -def get_lyrics(song_name): - song_name += ' metrolyrics' - name = quote_plus(song_name) - hdr = { - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0', - 'Accept-Encoding': 'gzip, deflate, br', - 'DNT': '1', - 'UPGRADE-INSECURE-REQUESTS': '1', - 'Connection': 'keep-alive'} + url_parts = list(urlparse.urlparse(url)) # get url parts + query = dict(urlparse.parse_qsl(url_parts[4])) # get url query + query.update(params) # add new params + url_parts[4] = urlencode(query) + return urlparse.urlunparse(url_parts) - url = 'https://www.google.com/search?q=' + name - result = requests.get(url, headers=hdr).text +def get_metrolyrics_from_google(query): + query += ' metrolyrics:' # search just metrolyrics + url = add_params_to_url(BASE_URL, { + "q": query + }) + response = requests.get(url, headers=HEADERS) + result = response.text link_start = result.find('http://www.metrolyrics.com') + return link_start, result + + +def get_lyrics(query): + link_start, result = get_metrolyrics_from_google(query) - if (link_start == -1): - return ("Lyrics not found on Metrolyrics") + if link_start == -1: + return "Lyrics not found" link_end = result.find('html', link_start + 1) + 4 link = result[link_start:link_end] - r = requests.get(link, headers=hdr) + r = requests.get(link, headers=HEADERS) r.encoding = "utf-8" lyrics_html = r.text soup = BeautifulSoup(lyrics_html, "lxml") raw_lyrics = (soup.findAll('p', attrs={'class': 'verse'})) - try: - final_lyrics = unicode.join(u'\n', map(unicode, raw_lyrics)) - except NameError: - final_lyrics = str.join(u'\n', map(str, raw_lyrics)) + final_lyrics = str.join(u'\n', map(str, raw_lyrics)) - final_lyrics = (final_lyrics.replace('

', '\n')) - final_lyrics = (final_lyrics.replace('
', ' ')) + final_lyrics = final_lyrics.replace('

', '\n') + final_lyrics = final_lyrics.replace('
', ' ') final_lyrics = final_lyrics.replace('

', ' ') - return (final_lyrics) + return final_lyrics diff --git a/src/utils.py b/src/utils.py index d0aeb60..60f00d5 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,15 +1,15 @@ # !/usr/bin/python3 # coding: utf-8 +import configparser import os -try: - import configparser -except ImportError: - import ConfigParser as configparser - from src.settings import CONFIG_PATH +BUG_ISSUES_REPORT_ERROR = "If that's not the case, report an issue here " + def create_default_config(): if not os.path.isdir(os.path.dirname(CONFIG_PATH)): @@ -45,11 +45,16 @@ def get_default_icon_path(): return get_icon_path('../icons/instant-lyrics-32.png') +def get_general_error(app): + out = "Could not get current " + app + " song" + out += "\nno song is playing on " + app + out += "\n\n" + BUG_ISSUES_REPORT_ERROR + return out + + def create_desktop_entry(): - # path of scr folder - src_path = os.path.dirname(os.path.realpath(__file__)) - # path of base folder - base_path = os.path.dirname(src_path) + src_path = os.path.dirname(os.path.realpath(__file__)) # path of scr folder + base_path = os.path.dirname(src_path) # path of base folder entry = "[Desktop Entry]\n" v = "Version=1.0\n" diff --git a/src/windows.py b/src/windows.py index eac308f..7b4a9b3 100644 --- a/src/windows.py +++ b/src/windows.py @@ -6,6 +6,8 @@ import dbus import gi +from utils import get_general_error + gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk @@ -86,11 +88,11 @@ def create_lyrics_box(self, app): return lyrics_vbox - def put_lyrics(self, song): + def put_lyrics(self, query): self.spinner.start() self.lyrics.set_text("") - lyrics = get_lyrics(song) + lyrics = get_lyrics(query) self.lyrics.set_text(lyrics) self.spinner.stop() @@ -101,7 +103,7 @@ def fetch_lyrics(self, source=None): self.title.set_markup(text) thread = threading.Thread( - target=self.put_lyrics, kwargs={'song': input}) + target=self.put_lyrics, kwargs={'query': input}) thread.daemon = True thread.start() @@ -128,18 +130,12 @@ def get_lyrics(self, app): artist = song_data['artist'] except: self.title.set_markup("Error") - message = ("Could not get current " + app + " song\n" - "no song is playing on " + app + ".\n\n" - "Else, report an issue here") - + message = get_general_error(app) self.lyrics.set_markup(message) return title = "" + song + "\n" + artist + "" self.title.set_markup(title) - self.put_lyrics(song + " " + artist) From 8f15e0e13b6973558558cefaecb7905f52d5ee5b Mon Sep 17 00:00:00 2001 From: sirfoga Date: Tue, 25 Sep 2018 10:37:48 +0200 Subject: [PATCH 11/25] add duckduckgo metrolyrics fetcher --- src/lyrics.py | 68 +++++++++++++++++++++++++++++++++++++++++--------- src/windows.py | 40 ++++++++++++++++++----------- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/lyrics.py b/src/lyrics.py index d22af1e..0b4af8b 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -1,6 +1,7 @@ # !/usr/bin/python3 # coding: utf-8 +import abc import urllib.parse as urlparse from urllib.parse import urlencode @@ -13,7 +14,7 @@ 'UPGRADE-INSECURE-REQUESTS': '1', 'Connection': 'keep-alive' } -BASE_URL = "https://www.google.com/search" +NOT_FOUND_MESSAGE = "Lyrics not found" def add_params_to_url(url, params): @@ -33,22 +34,60 @@ def add_params_to_url(url, params): return urlparse.urlunparse(url_parts) -def get_metrolyrics_from_google(query): - query += ' metrolyrics:' # search just metrolyrics - url = add_params_to_url(BASE_URL, { - "q": query - }) - response = requests.get(url, headers=HEADERS) - result = response.text - link_start = result.find('http://www.metrolyrics.com') - return link_start, result +class MetrolyricsFetcher: + """ + Define abstract primitive operations that concrete subclasses define + to implement steps of an algorithm. + Implement a template method defining the skeleton of an algorithm. + The template method calls primitive operations as well as operations + defined in AbstractClass or those of other objects. + """ + + def __init__(self, query): + self.query = query + + def get_lyrics(self): + query = self._get_query(self.query) + url = self._get_url(query) + response = requests.get(url, headers=HEADERS) + result = response.text + link_start = result.find('http://www.metrolyrics.com') + return link_start, result + + @abc.abstractmethod + def _get_query(self, query): + pass + + @abc.abstractmethod + def _get_url(self, query): + pass + + +class GoogleMetrolyricsFetcher(MetrolyricsFetcher): + def _get_query(self, query): + return query + ' metrolyrics:' # search just metrolyrics + + def _get_url(self, query): + return add_params_to_url("https://www.google.com/search", { + "q": query + }) + + +class DuckDuckGoMetrolyricsFetcher(MetrolyricsFetcher): + def _get_query(self, query): + return 'site:metrolyrics.com ' + query # search just metrolyrics + + def _get_url(self, query): + return add_params_to_url("https://duckduckgo.com/html", { + "q": query + }) def get_lyrics(query): - link_start, result = get_metrolyrics_from_google(query) + link_start, result = DuckDuckGoMetrolyricsFetcher(query).get_lyrics() if link_start == -1: - return "Lyrics not found" + return NOT_FOUND_MESSAGE link_end = result.find('html', link_start + 1) + 4 link = result[link_start:link_end] @@ -64,4 +103,9 @@ def get_lyrics(query): final_lyrics = final_lyrics.replace('

', '\n') final_lyrics = final_lyrics.replace('
', ' ') final_lyrics = final_lyrics.replace('

', ' ') + final_lyrics = final_lyrics.strip() + + if len(final_lyrics) < 10: + return NOT_FOUND_MESSAGE + return final_lyrics diff --git a/src/windows.py b/src/windows.py index 7b4a9b3..996332f 100644 --- a/src/windows.py +++ b/src/windows.py @@ -2,6 +2,7 @@ # coding: utf-8 import threading +import time import dbus import gi @@ -45,6 +46,8 @@ def __init__(self, type, app): scrolled.add(self.main_box) self.add(scrolled) + self.current_song = "" + self.current_artist = "" def on_key_release(self, widget, ev, data=None): if ev.keyval == Gdk.KEY_Return: @@ -107,7 +110,7 @@ def fetch_lyrics(self, source=None): thread.daemon = True thread.start() - def get_song_data(self, app): + def fetch_song_data(self, app): session_bus = dbus.SessionBus() app_bus = session_bus.get_object( @@ -121,22 +124,31 @@ def get_song_data(self, app): '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_lyrics(self, app): - try: - song_data = self.get_song_data(app) - song = song_data['title'] - artist = song_data['artist'] - except: - self.title.set_markup("Error") - message = get_general_error(app) - self.lyrics.set_markup(message) - return + self.current_song = title + self.current_artist = artist - title = "" + song + "\n" + artist + "" + def set_current_song_title(self): + title = "" + self.current_song + "\n" + \ + self.current_artist + "" self.title.set_markup(title) - self.put_lyrics(song + " " + artist) + + def set_current_song_lyrics(self): + self.put_lyrics(self.current_song + " " + self.current_artist) + + def get_lyrics(self, app): + while True: + try: + self.fetch_song_data(app) + except: + self.title.set_markup("Error") + message = get_general_error(app) + self.lyrics.set_markup(message) + return + + self.set_current_song_title() + self.set_current_song_lyrics() + time.sleep(2) class PreferenceWindow(Gtk.Window): From 8aca7f9b4b95c2b0df79fcef9109528020f818d3 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Tue, 25 Sep 2018 10:45:54 +0200 Subject: [PATCH 12/25] listen to song change, fix #10 --- src/lyrics.py | 7 +++---- src/windows.py | 25 +++++++++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/lyrics.py b/src/lyrics.py index 0b4af8b..c0b2abc 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -14,7 +14,6 @@ 'UPGRADE-INSECURE-REQUESTS': '1', 'Connection': 'keep-alive' } -NOT_FOUND_MESSAGE = "Lyrics not found" def add_params_to_url(url, params): @@ -87,7 +86,7 @@ def get_lyrics(query): link_start, result = DuckDuckGoMetrolyricsFetcher(query).get_lyrics() if link_start == -1: - return NOT_FOUND_MESSAGE + return None link_end = result.find('html', link_start + 1) + 4 link = result[link_start:link_end] @@ -105,7 +104,7 @@ def get_lyrics(query): final_lyrics = final_lyrics.replace('

', ' ') final_lyrics = final_lyrics.strip() - if len(final_lyrics) < 10: - return NOT_FOUND_MESSAGE + if len(final_lyrics) < 20: # too little to be lyrcs => not found + return None return final_lyrics diff --git a/src/windows.py b/src/windows.py index 996332f..4f65670 100644 --- a/src/windows.py +++ b/src/windows.py @@ -48,6 +48,7 @@ def __init__(self, type, app): self.add(scrolled) self.current_song = "" self.current_artist = "" + self.current_lyrics = None def on_key_release(self, widget, ev, data=None): if ev.keyval == Gdk.KEY_Return: @@ -95,8 +96,12 @@ def put_lyrics(self, query): self.spinner.start() self.lyrics.set_text("") - lyrics = get_lyrics(query) - self.lyrics.set_text(lyrics) + self.current_lyrics = get_lyrics(query) + + if self.current_lyrics is None: + self.lyrics.set_text("Lyrics not found") + else: + self.lyrics.set_text(self.current_lyrics) self.spinner.stop() @@ -139,16 +144,24 @@ def set_current_song_lyrics(self): def get_lyrics(self, app): while True: try: + previous_song = self.current_song + previous_artist = self.current_artist self.fetch_song_data(app) + + new_song = (previous_song != self.current_song) or ( + previous_artist != self.current_artist) + + print(previous_song, "VS", self.current_song) + + if new_song: # no new song => return + self.set_current_song_title() + self.set_current_song_lyrics() except: self.title.set_markup("Error") message = get_general_error(app) self.lyrics.set_markup(message) - return - self.set_current_song_title() - self.set_current_song_lyrics() - time.sleep(2) + time.sleep(5) class PreferenceWindow(Gtk.Window): From 88a57c87004d8e9ec65615a4fc82ec9b896ea3d8 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Tue, 25 Sep 2018 20:11:16 +0200 Subject: [PATCH 13/25] fix link finder --- src/appIndicator.py | 6 +- src/lyrics.py | 147 ++++++++++++++++++++++++++++++++++---------- src/windows.py | 2 - 3 files changed, 121 insertions(+), 34 deletions(-) diff --git a/src/appIndicator.py b/src/appIndicator.py index 2a82fee..2b2d1ce 100644 --- a/src/appIndicator.py +++ b/src/appIndicator.py @@ -18,10 +18,14 @@ from gi.repository import AppIndicator3 as appindicator from gi.repository import Gtk - +import os DBusGMainLoop(set_as_default=True) ICON_PATH = get_default_icon_path() # full path +LOCAL_LYRICS_PATH = os.path.join( + os.getenv("HOME"), + ".local", "Instant-Lyrics", "lyrics" +) def list_music_apps(): diff --git a/src/lyrics.py b/src/lyrics.py index c0b2abc..76197e5 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -33,25 +33,57 @@ def add_params_to_url(url, params): return urlparse.urlunparse(url_parts) -class MetrolyricsFetcher: +class LyricsFetcher: """ - Define abstract primitive operations that concrete subclasses define - to implement steps of an algorithm. - Implement a template method defining the skeleton of an algorithm. - The template method calls primitive operations as well as operations - defined in AbstractClass or those of other objects. + Searches for song lyrics in local folder """ def __init__(self, query): self.query = query + @abc.abstractclassmethod def get_lyrics(self): + return None + + +class InternetLyricsFetcher(LyricsFetcher): + """ + Searches internet for song lyrics + """ + + def __init__(self, query, search_engine): + LyricsFetcher.__init__(self, query) + self.url = search_engine + + def get_lyrics(self): + try: + link = self.get_lyrics_link() + r = requests.get(link, headers=HEADERS) + r.encoding = "utf-8" + result = r.text + return self._parse_result(result) + except: + return None + + def get_lyrics_link(self): query = self._get_query(self.query) url = self._get_url(query) response = requests.get(url, headers=HEADERS) result = response.text - link_start = result.find('http://www.metrolyrics.com') - return link_start, result + + link_start = result.find(self.url) + if link_start == -1: + return None + + link_end = min( + result.find('\'', link_start + 1), + result.find('"', link_start + 1) + ) + return result[link_start:link_end] + + @abc.abstractclassmethod + def _parse_result(self, result): + pass @abc.abstractmethod def _get_query(self, query): @@ -62,9 +94,36 @@ def _get_url(self, query): pass -class GoogleMetrolyricsFetcher(MetrolyricsFetcher): +class GeniusFetcher(InternetLyricsFetcher): + """ + Define abstract primitive operations that concrete subclasses define + to implement steps of an algorithm. + Implement a template method defining the skeleton of an algorithm. + The template method calls primitive operations as well as operations + defined in AbstractClass or those of other objects. + """ + + def __init__(self, query): + InternetLyricsFetcher.__init__( + self, query, 'https://genius.com' + ) + + def _parse_result(self, result): + soup = BeautifulSoup(result, "lxml") + raw = soup.findAll("div", {"class": "lyrics"})[0] + + parsed = raw.findAll("p")[0].text + # parsed = str.join(u'\n', map(str, parsed)) + + if len(parsed) < 20: # too little to be lyrcs => not found + return None + + return parsed + + +class GoogleGeniusFetcher(GeniusFetcher): def _get_query(self, query): - return query + ' metrolyrics:' # search just metrolyrics + return 'site:genius.com ' + query # search just genius def _get_url(self, query): return add_params_to_url("https://www.google.com/search", { @@ -72,9 +131,9 @@ def _get_url(self, query): }) -class DuckDuckGoMetrolyricsFetcher(MetrolyricsFetcher): +class DuckDuckGoGeniusFetcher(GeniusFetcher): def _get_query(self, query): - return 'site:metrolyrics.com ' + query # search just metrolyrics + return 'site:genius.com ' + query # search just metrolyrics def _get_url(self, query): return add_params_to_url("https://duckduckgo.com/html", { @@ -82,29 +141,55 @@ def _get_url(self, query): }) -def get_lyrics(query): - link_start, result = DuckDuckGoMetrolyricsFetcher(query).get_lyrics() +class MetrolyricsFetcher(InternetLyricsFetcher): + """ + Define abstract primitive operations that concrete subclasses define + to implement steps of an algorithm. + Implement a template method defining the skeleton of an algorithm. + The template method calls primitive operations as well as operations + defined in AbstractClass or those of other objects. + """ - if link_start == -1: - return None + def __init__(self, query): + InternetLyricsFetcher.__init__( + self, query, 'http://www.metrolyrics.com' + ) - link_end = result.find('html', link_start + 1) + 4 - link = result[link_start:link_end] + def _parse_result(self, result): + soup = BeautifulSoup(result, "lxml") + raw = (soup.findAll('p', attrs={'class': 'verse'})) - r = requests.get(link, headers=HEADERS) - r.encoding = "utf-8" - lyrics_html = r.text + parsed = str.join(u'\n', map(str, raw)) + parsed = parsed.replace('

', '\n') + parsed = parsed.replace('
', ' ') + parsed = parsed.replace('

', ' ') + parsed = parsed.strip() - soup = BeautifulSoup(lyrics_html, "lxml") - raw_lyrics = (soup.findAll('p', attrs={'class': 'verse'})) - final_lyrics = str.join(u'\n', map(str, raw_lyrics)) + if len(parsed) < 20: # too little to be lyrcs => not found + return None - final_lyrics = final_lyrics.replace('

', '\n') - final_lyrics = final_lyrics.replace('
', ' ') - final_lyrics = final_lyrics.replace('

', ' ') - final_lyrics = final_lyrics.strip() + return parsed - if len(final_lyrics) < 20: # too little to be lyrcs => not found - return None - return final_lyrics +class GoogleMetrolyricsFetcher(MetrolyricsFetcher): + def _get_query(self, query): + return 'site:metrolyrics.com ' + query # search just metrolyrics + + def _get_url(self, query): + return add_params_to_url("https://www.google.com/search", { + "q": query + }) + + +class DuckDuckGoMetrolyricsFetcher(MetrolyricsFetcher): + def _get_query(self, query): + return 'site:metrolyrics.com ' + query # search just metrolyrics + + def _get_url(self, query): + return add_params_to_url("https://duckduckgo.com/html", { + "q": query + }) + + +def get_lyrics(query): + return GoogleMetrolyricsFetcher(query).get_lyrics() diff --git a/src/windows.py b/src/windows.py index 4f65670..80dcc4b 100644 --- a/src/windows.py +++ b/src/windows.py @@ -151,8 +151,6 @@ def get_lyrics(self, app): new_song = (previous_song != self.current_song) or ( previous_artist != self.current_artist) - print(previous_song, "VS", self.current_song) - if new_song: # no new song => return self.set_current_song_title() self.set_current_song_lyrics() From 98caf7f98817516fe0506e45962901f7c5b5daa6 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Tue, 25 Sep 2018 20:15:33 +0200 Subject: [PATCH 14/25] refactor lyrics fetchers --- src/lyrics.py | 191 +------------------------------------- src/lyrics/__init__.py | 0 src/lyrics/genius.py | 53 +++++++++++ src/lyrics/metrolyrics.py | 56 +++++++++++ src/lyrics/models.py | 93 +++++++++++++++++++ 5 files changed, 204 insertions(+), 189 deletions(-) create mode 100644 src/lyrics/__init__.py create mode 100644 src/lyrics/genius.py create mode 100644 src/lyrics/metrolyrics.py create mode 100644 src/lyrics/models.py diff --git a/src/lyrics.py b/src/lyrics.py index 76197e5..74e87c4 100644 --- a/src/lyrics.py +++ b/src/lyrics.py @@ -1,195 +1,8 @@ # !/usr/bin/python3 # coding: utf-8 -import abc -import urllib.parse as urlparse -from urllib.parse import urlencode - -import requests -from bs4 import BeautifulSoup - -HEADERS = { - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) ' - 'Gecko/20100101 Firefox/52.0', - 'UPGRADE-INSECURE-REQUESTS': '1', - 'Connection': 'keep-alive' -} - - -def add_params_to_url(url, params): - """ - :param url: str - Url to add params to - :param params: {} - List of params to add to url - :return: void - Adds params to url - """ - - url_parts = list(urlparse.urlparse(url)) # get url parts - query = dict(urlparse.parse_qsl(url_parts[4])) # get url query - query.update(params) # add new params - url_parts[4] = urlencode(query) - return urlparse.urlunparse(url_parts) - - -class LyricsFetcher: - """ - Searches for song lyrics in local folder - """ - - def __init__(self, query): - self.query = query - - @abc.abstractclassmethod - def get_lyrics(self): - return None - - -class InternetLyricsFetcher(LyricsFetcher): - """ - Searches internet for song lyrics - """ - - def __init__(self, query, search_engine): - LyricsFetcher.__init__(self, query) - self.url = search_engine - - def get_lyrics(self): - try: - link = self.get_lyrics_link() - r = requests.get(link, headers=HEADERS) - r.encoding = "utf-8" - result = r.text - return self._parse_result(result) - except: - return None - - def get_lyrics_link(self): - query = self._get_query(self.query) - url = self._get_url(query) - response = requests.get(url, headers=HEADERS) - result = response.text - - link_start = result.find(self.url) - if link_start == -1: - return None - - link_end = min( - result.find('\'', link_start + 1), - result.find('"', link_start + 1) - ) - return result[link_start:link_end] - - @abc.abstractclassmethod - def _parse_result(self, result): - pass - - @abc.abstractmethod - def _get_query(self, query): - pass - - @abc.abstractmethod - def _get_url(self, query): - pass - - -class GeniusFetcher(InternetLyricsFetcher): - """ - Define abstract primitive operations that concrete subclasses define - to implement steps of an algorithm. - Implement a template method defining the skeleton of an algorithm. - The template method calls primitive operations as well as operations - defined in AbstractClass or those of other objects. - """ - - def __init__(self, query): - InternetLyricsFetcher.__init__( - self, query, 'https://genius.com' - ) - - def _parse_result(self, result): - soup = BeautifulSoup(result, "lxml") - raw = soup.findAll("div", {"class": "lyrics"})[0] - - parsed = raw.findAll("p")[0].text - # parsed = str.join(u'\n', map(str, parsed)) - - if len(parsed) < 20: # too little to be lyrcs => not found - return None - - return parsed - - -class GoogleGeniusFetcher(GeniusFetcher): - def _get_query(self, query): - return 'site:genius.com ' + query # search just genius - - def _get_url(self, query): - return add_params_to_url("https://www.google.com/search", { - "q": query - }) - - -class DuckDuckGoGeniusFetcher(GeniusFetcher): - def _get_query(self, query): - return 'site:genius.com ' + query # search just metrolyrics - - def _get_url(self, query): - return add_params_to_url("https://duckduckgo.com/html", { - "q": query - }) - - -class MetrolyricsFetcher(InternetLyricsFetcher): - """ - Define abstract primitive operations that concrete subclasses define - to implement steps of an algorithm. - Implement a template method defining the skeleton of an algorithm. - The template method calls primitive operations as well as operations - defined in AbstractClass or those of other objects. - """ - - def __init__(self, query): - InternetLyricsFetcher.__init__( - self, query, 'http://www.metrolyrics.com' - ) - - def _parse_result(self, result): - soup = BeautifulSoup(result, "lxml") - raw = (soup.findAll('p', attrs={'class': 'verse'})) - - parsed = str.join(u'\n', map(str, raw)) - parsed = parsed.replace('

', '\n') - parsed = parsed.replace('
', ' ') - parsed = parsed.replace('

', ' ') - parsed = parsed.strip() - - if len(parsed) < 20: # too little to be lyrcs => not found - return None - - return parsed - - -class GoogleMetrolyricsFetcher(MetrolyricsFetcher): - def _get_query(self, query): - return 'site:metrolyrics.com ' + query # search just metrolyrics - - def _get_url(self, query): - return add_params_to_url("https://www.google.com/search", { - "q": query - }) - - -class DuckDuckGoMetrolyricsFetcher(MetrolyricsFetcher): - def _get_query(self, query): - return 'site:metrolyrics.com ' + query # search just metrolyrics - - def _get_url(self, query): - return add_params_to_url("https://duckduckgo.com/html", { - "q": query - }) +from lyrics.genius import GoogleGeniusFetcher def get_lyrics(query): - return GoogleMetrolyricsFetcher(query).get_lyrics() + return GoogleGeniusFetcher(query).get_lyrics() diff --git a/src/lyrics/__init__.py b/src/lyrics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/lyrics/genius.py b/src/lyrics/genius.py new file mode 100644 index 0000000..63e8e8a --- /dev/null +++ b/src/lyrics/genius.py @@ -0,0 +1,53 @@ +# !/usr/bin/python3 +# coding: utf-8 + +from bs4 import BeautifulSoup + +from lyrics.models import InternetLyricsFetcher + + +class GeniusFetcher(InternetLyricsFetcher): + """ + Define abstract primitive operations that concrete subclasses define + to implement steps of an algorithm. + Implement a template method defining the skeleton of an algorithm. + The template method calls primitive operations as well as operations + defined in AbstractClass or those of other objects. + """ + + def __init__(self, query): + InternetLyricsFetcher.__init__( + self, query, 'https://genius.com' + ) + + def _parse_result(self, result): + soup = BeautifulSoup(result, "lxml") + raw = soup.findAll("div", {"class": "lyrics"})[0] + + parsed = raw.findAll("p")[0].text + # parsed = str.join(u'\n', map(str, parsed)) + + if len(parsed) < 20: # too little to be lyrcs => not found + return None + + return parsed + + +class GoogleGeniusFetcher(GeniusFetcher): + def _get_query(self, query): + return 'site:genius.com ' + query # search just genius + + def _get_url(self, query): + return self.add_params_to_url("https://www.google.com/search", { + "q": query + }) + + +class DuckDuckGoGeniusFetcher(GeniusFetcher): + def _get_query(self, query): + return 'site:genius.com ' + query # search just metrolyrics + + def _get_url(self, query): + return self.add_params_to_url("https://duckduckgo.com/html", { + "q": query + }) diff --git a/src/lyrics/metrolyrics.py b/src/lyrics/metrolyrics.py new file mode 100644 index 0000000..1c310c8 --- /dev/null +++ b/src/lyrics/metrolyrics.py @@ -0,0 +1,56 @@ +# !/usr/bin/python3 +# coding: utf-8 + +from bs4 import BeautifulSoup + +from lyrics.models import InternetLyricsFetcher + + +class MetrolyricsFetcher(InternetLyricsFetcher): + """ + Define abstract primitive operations that concrete subclasses define + to implement steps of an algorithm. + Implement a template method defining the skeleton of an algorithm. + The template method calls primitive operations as well as operations + defined in AbstractClass or those of other objects. + """ + + def __init__(self, query): + InternetLyricsFetcher.__init__( + self, query, 'http://www.metrolyrics.com' + ) + + def _parse_result(self, result): + soup = BeautifulSoup(result, "lxml") + raw = (soup.findAll('p', attrs={'class': 'verse'})) + + parsed = str.join(u'\n', map(str, raw)) + parsed = parsed.replace('

', '\n') + parsed = parsed.replace('
', ' ') + parsed = parsed.replace('

', ' ') + parsed = parsed.strip() + + if len(parsed) < 20: # too little to be lyrcs => not found + return None + + return parsed + + +class GoogleMetrolyricsFetcher(MetrolyricsFetcher): + def _get_query(self, query): + return 'site:metrolyrics.com ' + query # search just metrolyrics + + def _get_url(self, query): + return self.add_params_to_url("https://www.google.com/search", { + "q": query + }) + + +class DuckDuckGoMetrolyricsFetcher(MetrolyricsFetcher): + def _get_query(self, query): + return 'site:metrolyrics.com ' + query # search just metrolyrics + + def _get_url(self, query): + return self.add_params_to_url("https://duckduckgo.com/html", { + "q": query + }) diff --git a/src/lyrics/models.py b/src/lyrics/models.py new file mode 100644 index 0000000..4954bd7 --- /dev/null +++ b/src/lyrics/models.py @@ -0,0 +1,93 @@ +# !/usr/bin/python3 +# coding: utf-8 + +import abc +import urllib.parse as urlparse +from urllib.parse import urlencode + +import requests + +HEADERS = { + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) ' + 'Gecko/20100101 Firefox/52.0', + 'UPGRADE-INSECURE-REQUESTS': '1', + 'Connection': 'keep-alive' +} + + +class LyricsFetcher: + """ + Searches for song lyrics in local folder + """ + + def __init__(self, query): + self.query = query + + @abc.abstractclassmethod + def get_lyrics(self): + return None + + +class InternetLyricsFetcher(LyricsFetcher): + """ + Searches internet for song lyrics + """ + + def __init__(self, query, search_engine): + LyricsFetcher.__init__(self, query) + self.url = search_engine + + def get_lyrics(self): + try: + link = self.get_lyrics_link() + r = requests.get(link, headers=HEADERS) + r.encoding = "utf-8" + result = r.text + return self._parse_result(result) + except: + return None + + def get_lyrics_link(self): + query = self._get_query(self.query) + url = self._get_url(query) + response = requests.get(url, headers=HEADERS) + result = response.text + + link_start = result.find(self.url) + if link_start == -1: + return None + + link_end = min( + result.find('\'', link_start + 1), + result.find('"', link_start + 1) + ) + return result[link_start:link_end] + + @abc.abstractclassmethod + def _parse_result(self, result): + pass + + @abc.abstractmethod + def _get_query(self, query): + pass + + @abc.abstractmethod + def _get_url(self, query): + pass + + @staticmethod + def add_params_to_url(url, params): + """ + :param url: str + Url to add params to + :param params: {} + List of params to add to url + :return: void + Adds params to url + """ + + url_parts = list(urlparse.urlparse(url)) # get url parts + query = dict(urlparse.parse_qsl(url_parts[4])) # get url query + query.update(params) # add new params + url_parts[4] = urlencode(query) + return urlparse.urlunparse(url_parts) From 83d984b87dea3df4a163277122be87bd1b40660f Mon Sep 17 00:00:00 2001 From: sirfoga Date: Tue, 25 Sep 2018 20:26:07 +0200 Subject: [PATCH 15/25] add local lyrics finder --- src/lyrics.py | 8 -------- src/lyrics/genius.py | 2 +- src/lyrics/local.py | 37 +++++++++++++++++++++++++++++++++++++ src/lyrics/metrolyrics.py | 2 +- src/lyrics/models.py | 4 ---- src/windows.py | 4 ++-- 6 files changed, 41 insertions(+), 16 deletions(-) delete mode 100644 src/lyrics.py create mode 100644 src/lyrics/local.py diff --git a/src/lyrics.py b/src/lyrics.py deleted file mode 100644 index 74e87c4..0000000 --- a/src/lyrics.py +++ /dev/null @@ -1,8 +0,0 @@ -# !/usr/bin/python3 -# coding: utf-8 - -from lyrics.genius import GoogleGeniusFetcher - - -def get_lyrics(query): - return GoogleGeniusFetcher(query).get_lyrics() diff --git a/src/lyrics/genius.py b/src/lyrics/genius.py index 63e8e8a..a99e8a9 100644 --- a/src/lyrics/genius.py +++ b/src/lyrics/genius.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup -from lyrics.models import InternetLyricsFetcher +from .models import InternetLyricsFetcher class GeniusFetcher(InternetLyricsFetcher): diff --git a/src/lyrics/local.py b/src/lyrics/local.py new file mode 100644 index 0000000..fb6848a --- /dev/null +++ b/src/lyrics/local.py @@ -0,0 +1,37 @@ +# !/usr/bin/python3 +# coding: utf-8 + +import os + +from .models import LyricsFetcher + + +class LocalLyricsFetcher(LyricsFetcher): + """ + Searches for song lyrics in local folder + """ + + def __init__(self, query, folder): + LyricsFetcher.__init__(self, query) + self.search_folder = folder + + def get_available_lyrics(self): + folder_content = os.listdir(self.search_folder) + return [ + os.path.join(self.search_folder, f) # full path + for f in folder_content + if os.path.isfile(f) # just files + ] + + def get_lyrics(self): + lyrics_file = self.get_query_file() + if lyrics_file in self.get_available_lyrics(): + with open(lyrics_file, "r") as reader: + lines = reader.readlines() + return "\n".join(lines) # content + + return None + + def get_query_file(self): + file_name = self.query.replace(" ", "_") + return os.path.join(self.search_folder, file_name) diff --git a/src/lyrics/metrolyrics.py b/src/lyrics/metrolyrics.py index 1c310c8..147aa0f 100644 --- a/src/lyrics/metrolyrics.py +++ b/src/lyrics/metrolyrics.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup -from lyrics.models import InternetLyricsFetcher +from .models import InternetLyricsFetcher class MetrolyricsFetcher(InternetLyricsFetcher): diff --git a/src/lyrics/models.py b/src/lyrics/models.py index 4954bd7..70eabd8 100644 --- a/src/lyrics/models.py +++ b/src/lyrics/models.py @@ -16,10 +16,6 @@ class LyricsFetcher: - """ - Searches for song lyrics in local folder - """ - def __init__(self, query): self.query = query diff --git a/src/windows.py b/src/windows.py index 80dcc4b..bac6d4f 100644 --- a/src/windows.py +++ b/src/windows.py @@ -7,12 +7,12 @@ import dbus import gi +from lyrics.genius import GoogleGeniusFetcher from utils import get_general_error gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk -from src.lyrics import get_lyrics from src.settings import CONFIG_PATH from . import utils @@ -96,7 +96,7 @@ def put_lyrics(self, query): self.spinner.start() self.lyrics.set_text("") - self.current_lyrics = get_lyrics(query) + self.current_lyrics = GoogleGeniusFetcher(query).get_lyrics() if self.current_lyrics is None: self.lyrics.set_text("Lyrics not found") From 522b81f8f0b1429f23e743a6693cb45fc6707339 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 26 Sep 2018 09:29:19 +0200 Subject: [PATCH 16/25] add local + internet lyrics finders --- src/lyrics/genius.py | 10 ++++++++-- src/lyrics/local.py | 20 ++++++++++++-------- src/lyrics/metrolyrics.py | 10 ++++++++-- src/lyrics/models.py | 14 ++++++++------ src/lyrics/utils.py | 36 ++++++++++++++++++++++++++++++++++++ src/windows.py | 6 +++--- 6 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 src/lyrics/utils.py diff --git a/src/lyrics/genius.py b/src/lyrics/genius.py index a99e8a9..b263608 100644 --- a/src/lyrics/genius.py +++ b/src/lyrics/genius.py @@ -15,9 +15,9 @@ class GeniusFetcher(InternetLyricsFetcher): defined in AbstractClass or those of other objects. """ - def __init__(self, query): + def __init__(self): InternetLyricsFetcher.__init__( - self, query, 'https://genius.com' + self, 'https://genius.com' ) def _parse_result(self, result): @@ -51,3 +51,9 @@ def _get_url(self, query): return self.add_params_to_url("https://duckduckgo.com/html", { "q": query }) + + +LYRICS_FINDERS = [ + GoogleGeniusFetcher(), + DuckDuckGoGeniusFetcher() +] diff --git a/src/lyrics/local.py b/src/lyrics/local.py index fb6848a..4ee9d2a 100644 --- a/src/lyrics/local.py +++ b/src/lyrics/local.py @@ -6,13 +6,19 @@ from .models import LyricsFetcher +def get_local_lyrics_filename(query, folder): + file_name = query.replace(" ", "_") + file_name += ".txt" + return os.path.join(folder, file_name) + + class LocalLyricsFetcher(LyricsFetcher): """ Searches for song lyrics in local folder """ - def __init__(self, query, folder): - LyricsFetcher.__init__(self, query) + def __init__(self, folder): + LyricsFetcher.__init__(self) self.search_folder = folder def get_available_lyrics(self): @@ -23,15 +29,13 @@ def get_available_lyrics(self): if os.path.isfile(f) # just files ] - def get_lyrics(self): - lyrics_file = self.get_query_file() + def get_lyrics(self, query): + lyrics_file = get_local_lyrics_filename(self.query, self.search_folder) if lyrics_file in self.get_available_lyrics(): + print("Fetching lyrics from", lyrics_file) + with open(lyrics_file, "r") as reader: lines = reader.readlines() return "\n".join(lines) # content return None - - def get_query_file(self): - file_name = self.query.replace(" ", "_") - return os.path.join(self.search_folder, file_name) diff --git a/src/lyrics/metrolyrics.py b/src/lyrics/metrolyrics.py index 147aa0f..19283c5 100644 --- a/src/lyrics/metrolyrics.py +++ b/src/lyrics/metrolyrics.py @@ -15,9 +15,9 @@ class MetrolyricsFetcher(InternetLyricsFetcher): defined in AbstractClass or those of other objects. """ - def __init__(self, query): + def __init__(self): InternetLyricsFetcher.__init__( - self, query, 'http://www.metrolyrics.com' + self, 'http://www.metrolyrics.com' ) def _parse_result(self, result): @@ -54,3 +54,9 @@ def _get_url(self, query): return self.add_params_to_url("https://duckduckgo.com/html", { "q": query }) + + +LYRICS_FINDERS = [ + GoogleMetrolyricsFetcher(), + DuckDuckGoMetrolyricsFetcher() +] diff --git a/src/lyrics/models.py b/src/lyrics/models.py index 70eabd8..e6d0c7a 100644 --- a/src/lyrics/models.py +++ b/src/lyrics/models.py @@ -16,11 +16,11 @@ class LyricsFetcher: - def __init__(self, query): - self.query = query + def __init__(self): + self.query = None @abc.abstractclassmethod - def get_lyrics(self): + def get_lyrics(self, query): return None @@ -29,11 +29,13 @@ class InternetLyricsFetcher(LyricsFetcher): Searches internet for song lyrics """ - def __init__(self, query, search_engine): - LyricsFetcher.__init__(self, query) + def __init__(self, search_engine): + LyricsFetcher.__init__(self) self.url = search_engine - def get_lyrics(self): + def get_lyrics(self, query): + self.query = query + try: link = self.get_lyrics_link() r = requests.get(link, headers=HEADERS) diff --git a/src/lyrics/utils.py b/src/lyrics/utils.py new file mode 100644 index 0000000..f2e6829 --- /dev/null +++ b/src/lyrics/utils.py @@ -0,0 +1,36 @@ +# !/usr/bin/python3 +# coding: utf-8 + +import os + +from lyrics.genius import LYRICS_FINDERS as GENIUS_FINDERS +from lyrics.metrolyrics import LYRICS_FINDERS as METROLYRICS_FINDERS +from .local import LocalLyricsFetcher, get_local_lyrics_filename + +LOCAL_LYRICS_FOLDER = os.path.join( + os.getenv("HOME"), + ".local", "Instant-Lyrics", "lyrics" +) + +if not os.path.exists(LOCAL_LYRICS_FOLDER): + os.makedirs(LOCAL_LYRICS_FOLDER) # make sure there is a local folder + + +def save_lyrics(query, lyrics): + file_name = get_local_lyrics_filename(query, LOCAL_LYRICS_FOLDER) + with open(file_name, "w") as writer: + writer.write(lyrics) + + +def get_lyrics(query): + finders = [ + LocalLyricsFetcher(LOCAL_LYRICS_FOLDER) # local + ] + GENIUS_FINDERS + METROLYRICS_FINDERS # internet + + for finder in finders: + result = finder.get_lyrics(query) + if result is not None: + save_lyrics(query, result) # save to local + return result + + return None diff --git a/src/windows.py b/src/windows.py index bac6d4f..ebadc0c 100644 --- a/src/windows.py +++ b/src/windows.py @@ -7,13 +7,13 @@ import dbus import gi -from lyrics.genius import GoogleGeniusFetcher -from utils import get_general_error +from lyrics.utils import get_lyrics gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk from src.settings import CONFIG_PATH +from .utils import get_general_error from . import utils @@ -96,7 +96,7 @@ def put_lyrics(self, query): self.spinner.start() self.lyrics.set_text("") - self.current_lyrics = GoogleGeniusFetcher(query).get_lyrics() + self.current_lyrics = get_lyrics(query) if self.current_lyrics is None: self.lyrics.set_text("Lyrics not found") From da8e13dcd85fd5cf4b76cab1897ccf332b78e03e Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 26 Sep 2018 09:54:32 +0200 Subject: [PATCH 17/25] fix local finder, fix #11 --- src/lyrics/genius.py | 4 ++++ src/lyrics/local.py | 13 +++++++++---- src/lyrics/metrolyrics.py | 4 ++++ src/lyrics/models.py | 11 +++-------- src/lyrics/utils.py | 3 +++ src/utils.py | 8 +------- src/windows.py | 10 +++++----- 7 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/lyrics/genius.py b/src/lyrics/genius.py index b263608..c54b189 100644 --- a/src/lyrics/genius.py +++ b/src/lyrics/genius.py @@ -35,6 +35,8 @@ def _parse_result(self, result): class GoogleGeniusFetcher(GeniusFetcher): def _get_query(self, query): + print("Searching Google for Genius lyrics") + return 'site:genius.com ' + query # search just genius def _get_url(self, query): @@ -45,6 +47,8 @@ def _get_url(self, query): class DuckDuckGoGeniusFetcher(GeniusFetcher): def _get_query(self, query): + print("Searching DuckDuckGo for Genius lyrics") + return 'site:genius.com ' + query # search just metrolyrics def _get_url(self, query): diff --git a/src/lyrics/local.py b/src/lyrics/local.py index 4ee9d2a..bc2a9f1 100644 --- a/src/lyrics/local.py +++ b/src/lyrics/local.py @@ -23,17 +23,22 @@ def __init__(self, folder): def get_available_lyrics(self): folder_content = os.listdir(self.search_folder) - return [ + folder_content = [ os.path.join(self.search_folder, f) # full path for f in folder_content + ] + return [ + f + for f in folder_content if os.path.isfile(f) # just files ] def get_lyrics(self, query): - lyrics_file = get_local_lyrics_filename(self.query, self.search_folder) - if lyrics_file in self.get_available_lyrics(): - print("Fetching lyrics from", lyrics_file) + print("Searching local folder for lyrics") + available_lyrics = self.get_available_lyrics() + lyrics_file = get_local_lyrics_filename(query, self.search_folder) + if lyrics_file in available_lyrics: with open(lyrics_file, "r") as reader: lines = reader.readlines() return "\n".join(lines) # content diff --git a/src/lyrics/metrolyrics.py b/src/lyrics/metrolyrics.py index 19283c5..84f691a 100644 --- a/src/lyrics/metrolyrics.py +++ b/src/lyrics/metrolyrics.py @@ -38,6 +38,8 @@ def _parse_result(self, result): class GoogleMetrolyricsFetcher(MetrolyricsFetcher): def _get_query(self, query): + print("Searching Google for Metrolyrics lyrics") + return 'site:metrolyrics.com ' + query # search just metrolyrics def _get_url(self, query): @@ -48,6 +50,8 @@ def _get_url(self, query): class DuckDuckGoMetrolyricsFetcher(MetrolyricsFetcher): def _get_query(self, query): + print("Searching DuckDuckGo for Metrolyrics lyrics") + return 'site:metrolyrics.com ' + query # search just metrolyrics def _get_url(self, query): diff --git a/src/lyrics/models.py b/src/lyrics/models.py index e6d0c7a..5cc10b1 100644 --- a/src/lyrics/models.py +++ b/src/lyrics/models.py @@ -16,9 +16,6 @@ class LyricsFetcher: - def __init__(self): - self.query = None - @abc.abstractclassmethod def get_lyrics(self, query): return None @@ -34,10 +31,8 @@ def __init__(self, search_engine): self.url = search_engine def get_lyrics(self, query): - self.query = query - try: - link = self.get_lyrics_link() + link = self.get_lyrics_link(query) r = requests.get(link, headers=HEADERS) r.encoding = "utf-8" result = r.text @@ -45,8 +40,8 @@ def get_lyrics(self, query): except: return None - def get_lyrics_link(self): - query = self._get_query(self.query) + def get_lyrics_link(self, query): + query = self._get_query(query) url = self._get_url(query) response = requests.get(url, headers=HEADERS) result = response.text diff --git a/src/lyrics/utils.py b/src/lyrics/utils.py index f2e6829..9a51656 100644 --- a/src/lyrics/utils.py +++ b/src/lyrics/utils.py @@ -18,6 +18,9 @@ def save_lyrics(query, lyrics): file_name = get_local_lyrics_filename(query, LOCAL_LYRICS_FOLDER) + while "\n\n" in lyrics: # remove double line endings + lyrics = lyrics.replace("\n\n", "\n") + with open(file_name, "w") as writer: writer.write(lyrics) diff --git a/src/utils.py b/src/utils.py index 60f00d5..75ac779 100644 --- a/src/utils.py +++ b/src/utils.py @@ -9,6 +9,7 @@ BUG_ISSUES_REPORT_ERROR = "If that's not the case, report an issue here " +ERROR = "Error" def create_default_config(): @@ -45,13 +46,6 @@ def get_default_icon_path(): return get_icon_path('../icons/instant-lyrics-32.png') -def get_general_error(app): - out = "Could not get current " + app + " song" - out += "\nno song is playing on " + app - out += "\n\n" + BUG_ISSUES_REPORT_ERROR - return out - - def create_desktop_entry(): src_path = os.path.dirname(os.path.realpath(__file__)) # path of scr folder base_path = os.path.dirname(src_path) # path of base folder diff --git a/src/windows.py b/src/windows.py index ebadc0c..ecf6347 100644 --- a/src/windows.py +++ b/src/windows.py @@ -8,12 +8,12 @@ import gi from lyrics.utils import get_lyrics +from utils import ERROR gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk from src.settings import CONFIG_PATH -from .utils import get_general_error from . import utils @@ -151,13 +151,13 @@ def get_lyrics(self, app): new_song = (previous_song != self.current_song) or ( previous_artist != self.current_artist) - if new_song: # no new song => return + if new_song: + print("New song detected:") + print("\t", self.current_song + " " + self.current_artist) self.set_current_song_title() self.set_current_song_lyrics() except: - self.title.set_markup("Error") - message = get_general_error(app) - self.lyrics.set_markup(message) + self.title.set_markup(ERROR) time.sleep(5) From b1a10f9a37e77acf073980d5011cf39ed66c8e93 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 26 Sep 2018 10:48:41 +0200 Subject: [PATCH 18/25] add install script --- InstantLyrics.py | 9 +++++++-- setup.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100755 setup.py diff --git a/InstantLyrics.py b/InstantLyrics.py index 850dd7e..31268b6 100755 --- a/InstantLyrics.py +++ b/InstantLyrics.py @@ -1,7 +1,12 @@ # !/usr/bin/python3 # coding: utf-8 -from src.appIndicator import AppIndicator +from .src.appIndicator import AppIndicator -if __name__ == "__main__": + +def main(): app = AppIndicator() + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..caebcb1 --- /dev/null +++ b/setup.py @@ -0,0 +1,40 @@ +# !/usr/bin/python3 +# coding: utf_8 + +""" Setups library and install dependencies """ + +from setuptools import setup, find_packages + +DESCRIPTION = \ + "Instant-Lyrics\n\n\ + Instantly fetches the lyrics of the currently playing song and displays it on a window.\n\ + \n\ + Install\n\n\ + - $ pip3 install . --upgrade --force-reinstall, from the source\n\ + \n\ + Questions and issues\n\n\ + The Github issue tracker is only for bug reports and feature requests." + +setup( + name="Instant-Lyrics", + version="2.0", + author="sirfoga", + author_email='captain.bhrigu@gmail.com,nicolas.guichard@ensimag.fr,' + 'sirfoga@protonmail.com', + description="Instantly fetches the lyrics of the currently playing song " + "and displays it on a window.", + long_description=DESCRIPTION, + keywords="linux-app gtk3 pygobject lyrics spotify rhythmbox", + url="https://github.com/sirfoga/Instant-Lyrics", + packages=find_packages(exclude=["tests"]), + install_requires=[ + "requests", + "beautifulsoup4", + "lxml" + ], + entry_points={ + "console_scripts": [ + "instantlyrics = InstantLyrics:main" + ] + } +) From 1804f12541c1b9bfb1f6fa610b4532a2912ac880 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 26 Sep 2018 11:13:58 +0200 Subject: [PATCH 19/25] add lyricstranslate.com lyrics search engine --- {src => InstantLyrics}/__init__.py | 0 {src/lyrics => InstantLyrics/app}/__init__.py | 0 {src => InstantLyrics/app}/appIndicator.py | 12 ++-- {src => InstantLyrics/app}/settings.py | 0 {src => InstantLyrics/app}/windows.py | 10 ++- InstantLyrics.py => InstantLyrics/cmd.py | 4 +- InstantLyrics/lyrics/__init__.py | 0 {src => InstantLyrics}/lyrics/genius.py | 8 +-- {src => InstantLyrics}/lyrics/local.py | 2 +- InstantLyrics/lyrics/lyricstranslate.py | 63 +++++++++++++++++++ {src => InstantLyrics}/lyrics/metrolyrics.py | 6 +- {src => InstantLyrics}/lyrics/models.py | 0 {src => InstantLyrics}/lyrics/utils.py | 2 +- {src => InstantLyrics}/utils.py | 4 +- setup.py | 4 +- 15 files changed, 88 insertions(+), 27 deletions(-) rename {src => InstantLyrics}/__init__.py (100%) rename {src/lyrics => InstantLyrics/app}/__init__.py (100%) rename {src => InstantLyrics/app}/appIndicator.py (92%) rename {src => InstantLyrics/app}/settings.py (100%) rename {src => InstantLyrics/app}/windows.py (98%) rename InstantLyrics.py => InstantLyrics/cmd.py (57%) create mode 100644 InstantLyrics/lyrics/__init__.py rename {src => InstantLyrics}/lyrics/genius.py (87%) rename {src => InstantLyrics}/lyrics/local.py (96%) create mode 100644 InstantLyrics/lyrics/lyricstranslate.py rename {src => InstantLyrics}/lyrics/metrolyrics.py (90%) rename {src => InstantLyrics}/lyrics/models.py (100%) rename {src => InstantLyrics}/lyrics/utils.py (93%) rename {src => InstantLyrics}/utils.py (95%) diff --git a/src/__init__.py b/InstantLyrics/__init__.py similarity index 100% rename from src/__init__.py rename to InstantLyrics/__init__.py diff --git a/src/lyrics/__init__.py b/InstantLyrics/app/__init__.py similarity index 100% rename from src/lyrics/__init__.py rename to InstantLyrics/app/__init__.py diff --git a/src/appIndicator.py b/InstantLyrics/app/appIndicator.py similarity index 92% rename from src/appIndicator.py rename to InstantLyrics/app/appIndicator.py index 2b2d1ce..d96519b 100644 --- a/src/appIndicator.py +++ b/InstantLyrics/app/appIndicator.py @@ -8,11 +8,6 @@ import gi from dbus.mainloop.glib import DBusGMainLoop -from src.settings import APPINDICATOR_ID -from src.windows import LyricsWindow, PreferenceWindow -from utils import get_default_icon_path -from . import utils - gi.require_version('Gtk', '3.0') gi.require_version('AppIndicator3', '0.1') @@ -20,8 +15,13 @@ from gi.repository import Gtk import os +from app.settings import APPINDICATOR_ID +from app.windows import LyricsWindow, PreferenceWindow +from InstantLyrics import utils + + DBusGMainLoop(set_as_default=True) -ICON_PATH = get_default_icon_path() # full path +ICON_PATH = utils.get_default_icon_path() # full path LOCAL_LYRICS_PATH = os.path.join( os.getenv("HOME"), ".local", "Instant-Lyrics", "lyrics" diff --git a/src/settings.py b/InstantLyrics/app/settings.py similarity index 100% rename from src/settings.py rename to InstantLyrics/app/settings.py diff --git a/src/windows.py b/InstantLyrics/app/windows.py similarity index 98% rename from src/windows.py rename to InstantLyrics/app/windows.py index ecf6347..3b2f859 100644 --- a/src/windows.py +++ b/InstantLyrics/app/windows.py @@ -7,14 +7,12 @@ import dbus import gi -from lyrics.utils import get_lyrics -from utils import ERROR - gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk -from src.settings import CONFIG_PATH -from . import utils +from app.settings import CONFIG_PATH +from InstantLyrics.lyrics.utils import get_lyrics +from InstantLyrics import utils class LyricsWindow(Gtk.Window): @@ -157,7 +155,7 @@ def get_lyrics(self, app): self.set_current_song_title() self.set_current_song_lyrics() except: - self.title.set_markup(ERROR) + self.title.set_markup(utils.ERROR) time.sleep(5) diff --git a/InstantLyrics.py b/InstantLyrics/cmd.py similarity index 57% rename from InstantLyrics.py rename to InstantLyrics/cmd.py index 31268b6..277199e 100755 --- a/InstantLyrics.py +++ b/InstantLyrics/cmd.py @@ -1,11 +1,11 @@ # !/usr/bin/python3 # coding: utf-8 -from .src.appIndicator import AppIndicator +from app.appIndicator import AppIndicator def main(): - app = AppIndicator() + AppIndicator() if __name__ == "__main__": diff --git a/InstantLyrics/lyrics/__init__.py b/InstantLyrics/lyrics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/lyrics/genius.py b/InstantLyrics/lyrics/genius.py similarity index 87% rename from src/lyrics/genius.py rename to InstantLyrics/lyrics/genius.py index c54b189..e4daf83 100644 --- a/src/lyrics/genius.py +++ b/InstantLyrics/lyrics/genius.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup -from .models import InternetLyricsFetcher +from lyrics.models import InternetLyricsFetcher class GeniusFetcher(InternetLyricsFetcher): @@ -25,7 +25,7 @@ def _parse_result(self, result): raw = soup.findAll("div", {"class": "lyrics"})[0] parsed = raw.findAll("p")[0].text - # parsed = str.join(u'\n', map(str, parsed)) + parsed = str.join(u'\n', map(str, parsed)) if len(parsed) < 20: # too little to be lyrcs => not found return None @@ -35,7 +35,7 @@ def _parse_result(self, result): class GoogleGeniusFetcher(GeniusFetcher): def _get_query(self, query): - print("Searching Google for Genius lyrics") + print("Searching google.com for genius.com lyrics") return 'site:genius.com ' + query # search just genius @@ -47,7 +47,7 @@ def _get_url(self, query): class DuckDuckGoGeniusFetcher(GeniusFetcher): def _get_query(self, query): - print("Searching DuckDuckGo for Genius lyrics") + print("Searching duckduckgo.com for genius.com lyrics") return 'site:genius.com ' + query # search just metrolyrics diff --git a/src/lyrics/local.py b/InstantLyrics/lyrics/local.py similarity index 96% rename from src/lyrics/local.py rename to InstantLyrics/lyrics/local.py index bc2a9f1..740746c 100644 --- a/src/lyrics/local.py +++ b/InstantLyrics/lyrics/local.py @@ -3,7 +3,7 @@ import os -from .models import LyricsFetcher +from lyrics.models import LyricsFetcher def get_local_lyrics_filename(query, folder): diff --git a/InstantLyrics/lyrics/lyricstranslate.py b/InstantLyrics/lyrics/lyricstranslate.py new file mode 100644 index 0000000..658cbe6 --- /dev/null +++ b/InstantLyrics/lyrics/lyricstranslate.py @@ -0,0 +1,63 @@ +# !/usr/bin/python3 +# coding: utf-8 + +from bs4 import BeautifulSoup + +from lyrics.models import InternetLyricsFetcher + + +class LyricsTranslateFetcher(InternetLyricsFetcher): + """ + Define abstract primitive operations that concrete subclasses define + to implement steps of an algorithm. + Implement a template method defining the skeleton of an algorithm. + The template method calls primitive operations as well as operations + defined in AbstractClass or those of other objects. + """ + + def __init__(self): + InternetLyricsFetcher.__init__( + self, 'https://lyricstranslate.com/en/' + ) + + def _parse_result(self, result): + soup = BeautifulSoup(result, "lxml") + raw = soup.findAll("div", {"class": "ltf"})[0] + + parsed = raw.findAll("p")[0].text + parsed = str.join(u'\n', map(str, parsed)) + + if len(parsed) < 20: # too little to be lyrics => not found + return None + + return parsed + + +class GoogleLyricstranslateFetcher(LyricsTranslateFetcher): + def _get_query(self, query): + print("Searching google.com for lyricstranslate.com lyrics") + + return 'site:lyricstranslate.com ' + query # search just genius + + def _get_url(self, query): + return self.add_params_to_url("https://www.google.com/search", { + "q": query + }) + + +class DuckDuckGoLyricstranslateFetcher(LyricsTranslateFetcher): + def _get_query(self, query): + print("Searching duckduckgo.com for lyricstranslate.com lyrics") + + return 'site:lyricstranslate.com ' + query # search just metrolyrics + + def _get_url(self, query): + return self.add_params_to_url("https://duckduckgo.com/html", { + "q": query + }) + + +LYRICS_FINDERS = [ + GoogleLyricstranslateFetcher(), + DuckDuckGoLyricstranslateFetcher() +] diff --git a/src/lyrics/metrolyrics.py b/InstantLyrics/lyrics/metrolyrics.py similarity index 90% rename from src/lyrics/metrolyrics.py rename to InstantLyrics/lyrics/metrolyrics.py index 84f691a..9c43e9d 100644 --- a/src/lyrics/metrolyrics.py +++ b/InstantLyrics/lyrics/metrolyrics.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup -from .models import InternetLyricsFetcher +from lyrics.models import InternetLyricsFetcher class MetrolyricsFetcher(InternetLyricsFetcher): @@ -38,7 +38,7 @@ def _parse_result(self, result): class GoogleMetrolyricsFetcher(MetrolyricsFetcher): def _get_query(self, query): - print("Searching Google for Metrolyrics lyrics") + print("Searching google.com for metrolyrics.com lyrics") return 'site:metrolyrics.com ' + query # search just metrolyrics @@ -50,7 +50,7 @@ def _get_url(self, query): class DuckDuckGoMetrolyricsFetcher(MetrolyricsFetcher): def _get_query(self, query): - print("Searching DuckDuckGo for Metrolyrics lyrics") + print("Searching duckduckgo.com for metrolyrics.com lyrics") return 'site:metrolyrics.com ' + query # search just metrolyrics diff --git a/src/lyrics/models.py b/InstantLyrics/lyrics/models.py similarity index 100% rename from src/lyrics/models.py rename to InstantLyrics/lyrics/models.py diff --git a/src/lyrics/utils.py b/InstantLyrics/lyrics/utils.py similarity index 93% rename from src/lyrics/utils.py rename to InstantLyrics/lyrics/utils.py index 9a51656..c071771 100644 --- a/src/lyrics/utils.py +++ b/InstantLyrics/lyrics/utils.py @@ -4,8 +4,8 @@ import os from lyrics.genius import LYRICS_FINDERS as GENIUS_FINDERS +from lyrics.local import LocalLyricsFetcher, get_local_lyrics_filename from lyrics.metrolyrics import LYRICS_FINDERS as METROLYRICS_FINDERS -from .local import LocalLyricsFetcher, get_local_lyrics_filename LOCAL_LYRICS_FOLDER = os.path.join( os.getenv("HOME"), diff --git a/src/utils.py b/InstantLyrics/utils.py similarity index 95% rename from src/utils.py rename to InstantLyrics/utils.py index 75ac779..154dd0b 100644 --- a/src/utils.py +++ b/InstantLyrics/utils.py @@ -4,7 +4,7 @@ import configparser import os -from src.settings import CONFIG_PATH +from app.settings import CONFIG_PATH BUG_ISSUES_REPORT_ERROR = "If that's not the case, report an issue Date: Wed, 26 Sep 2018 11:22:23 +0200 Subject: [PATCH 20/25] fix local reader duplicate lines --- InstantLyrics/lyrics/genius.py | 6 ------ InstantLyrics/lyrics/local.py | 3 +-- InstantLyrics/lyrics/lyricstranslate.py | 10 ++-------- InstantLyrics/lyrics/metrolyrics.py | 7 +------ InstantLyrics/lyrics/models.py | 7 ++++++- InstantLyrics/lyrics/utils.py | 3 ++- 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/InstantLyrics/lyrics/genius.py b/InstantLyrics/lyrics/genius.py index e4daf83..3530a99 100644 --- a/InstantLyrics/lyrics/genius.py +++ b/InstantLyrics/lyrics/genius.py @@ -23,13 +23,7 @@ def __init__(self): def _parse_result(self, result): soup = BeautifulSoup(result, "lxml") raw = soup.findAll("div", {"class": "lyrics"})[0] - parsed = raw.findAll("p")[0].text - parsed = str.join(u'\n', map(str, parsed)) - - if len(parsed) < 20: # too little to be lyrcs => not found - return None - return parsed diff --git a/InstantLyrics/lyrics/local.py b/InstantLyrics/lyrics/local.py index 740746c..f6db60f 100644 --- a/InstantLyrics/lyrics/local.py +++ b/InstantLyrics/lyrics/local.py @@ -40,7 +40,6 @@ def get_lyrics(self, query): lyrics_file = get_local_lyrics_filename(query, self.search_folder) if lyrics_file in available_lyrics: with open(lyrics_file, "r") as reader: - lines = reader.readlines() - return "\n".join(lines) # content + return "".join(reader.readlines()) # content return None diff --git a/InstantLyrics/lyrics/lyricstranslate.py b/InstantLyrics/lyrics/lyricstranslate.py index 658cbe6..92e93b7 100644 --- a/InstantLyrics/lyrics/lyricstranslate.py +++ b/InstantLyrics/lyrics/lyricstranslate.py @@ -17,19 +17,13 @@ class LyricsTranslateFetcher(InternetLyricsFetcher): def __init__(self): InternetLyricsFetcher.__init__( - self, 'https://lyricstranslate.com/en/' + self, 'https://lyricstranslate.com/' ) def _parse_result(self, result): soup = BeautifulSoup(result, "lxml") raw = soup.findAll("div", {"class": "ltf"})[0] - - parsed = raw.findAll("p")[0].text - parsed = str.join(u'\n', map(str, parsed)) - - if len(parsed) < 20: # too little to be lyrics => not found - return None - + parsed = raw.text return parsed diff --git a/InstantLyrics/lyrics/metrolyrics.py b/InstantLyrics/lyrics/metrolyrics.py index 9c43e9d..7243b8d 100644 --- a/InstantLyrics/lyrics/metrolyrics.py +++ b/InstantLyrics/lyrics/metrolyrics.py @@ -23,16 +23,11 @@ def __init__(self): def _parse_result(self, result): soup = BeautifulSoup(result, "lxml") raw = (soup.findAll('p', attrs={'class': 'verse'})) - - parsed = str.join(u'\n', map(str, raw)) - parsed = parsed.replace('

', '\n') + parsed = raw.replace('

', '\n') parsed = parsed.replace('
', ' ') parsed = parsed.replace('

', ' ') parsed = parsed.strip() - if len(parsed) < 20: # too little to be lyrcs => not found - return None - return parsed diff --git a/InstantLyrics/lyrics/models.py b/InstantLyrics/lyrics/models.py index 5cc10b1..c0928a6 100644 --- a/InstantLyrics/lyrics/models.py +++ b/InstantLyrics/lyrics/models.py @@ -36,7 +36,12 @@ def get_lyrics(self, query): r = requests.get(link, headers=HEADERS) r.encoding = "utf-8" result = r.text - return self._parse_result(result) + parsed = self._parse_result(result) + + if len(parsed) < 20: # too little to be lyrics + return None + + return parsed except: return None diff --git a/InstantLyrics/lyrics/utils.py b/InstantLyrics/lyrics/utils.py index c071771..4debaa8 100644 --- a/InstantLyrics/lyrics/utils.py +++ b/InstantLyrics/lyrics/utils.py @@ -5,6 +5,7 @@ from lyrics.genius import LYRICS_FINDERS as GENIUS_FINDERS from lyrics.local import LocalLyricsFetcher, get_local_lyrics_filename +from lyrics.lyricstranslate import LYRICS_FINDERS as LYRICSTRANSLATE_FINDERS from lyrics.metrolyrics import LYRICS_FINDERS as METROLYRICS_FINDERS LOCAL_LYRICS_FOLDER = os.path.join( @@ -28,7 +29,7 @@ def save_lyrics(query, lyrics): def get_lyrics(query): finders = [ LocalLyricsFetcher(LOCAL_LYRICS_FOLDER) # local - ] + GENIUS_FINDERS + METROLYRICS_FINDERS # internet + ] + GENIUS_FINDERS + METROLYRICS_FINDERS + LYRICSTRANSLATE_FINDERS for finder in finders: result = finder.get_lyrics(query) From 3040b7eb88de067e656c935e421f76a8bcce5024 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 26 Sep 2018 11:39:28 +0200 Subject: [PATCH 21/25] fix images setup --- InstantLyrics/app/appIndicator.py | 4 ++-- InstantLyrics/app/windows.py | 2 +- InstantLyrics/cmd.py | 2 +- .../icons}/instant-lyrics-24.png | Bin .../icons}/instant-lyrics-256.png | Bin .../icons}/instant-lyrics-32.png | Bin .../icons}/instant-lyrics-512.png | Bin {icons => InstantLyrics/icons}/instant-lyrics.svg | 0 InstantLyrics/lyrics/genius.py | 2 +- InstantLyrics/lyrics/local.py | 2 +- InstantLyrics/lyrics/lyricstranslate.py | 2 +- InstantLyrics/lyrics/metrolyrics.py | 2 +- InstantLyrics/lyrics/utils.py | 11 +++++++---- InstantLyrics/utils.py | 4 ++-- setup.py | 12 +++++++++++- 15 files changed, 28 insertions(+), 15 deletions(-) rename {icons => InstantLyrics/icons}/instant-lyrics-24.png (100%) rename {icons => InstantLyrics/icons}/instant-lyrics-256.png (100%) rename {icons => InstantLyrics/icons}/instant-lyrics-32.png (100%) rename {icons => InstantLyrics/icons}/instant-lyrics-512.png (100%) rename {icons => InstantLyrics/icons}/instant-lyrics.svg (100%) diff --git a/InstantLyrics/app/appIndicator.py b/InstantLyrics/app/appIndicator.py index d96519b..8901083 100644 --- a/InstantLyrics/app/appIndicator.py +++ b/InstantLyrics/app/appIndicator.py @@ -15,8 +15,8 @@ from gi.repository import Gtk import os -from app.settings import APPINDICATOR_ID -from app.windows import LyricsWindow, PreferenceWindow +from InstantLyrics.app.settings import APPINDICATOR_ID +from InstantLyrics.app.windows import LyricsWindow, PreferenceWindow from InstantLyrics import utils diff --git a/InstantLyrics/app/windows.py b/InstantLyrics/app/windows.py index 3b2f859..a3241fa 100644 --- a/InstantLyrics/app/windows.py +++ b/InstantLyrics/app/windows.py @@ -10,7 +10,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk -from app.settings import CONFIG_PATH +from InstantLyrics.app.settings import CONFIG_PATH from InstantLyrics.lyrics.utils import get_lyrics from InstantLyrics import utils diff --git a/InstantLyrics/cmd.py b/InstantLyrics/cmd.py index 277199e..271b46b 100755 --- a/InstantLyrics/cmd.py +++ b/InstantLyrics/cmd.py @@ -1,7 +1,7 @@ # !/usr/bin/python3 # coding: utf-8 -from app.appIndicator import AppIndicator +from InstantLyrics.app.appIndicator import AppIndicator def main(): diff --git a/icons/instant-lyrics-24.png b/InstantLyrics/icons/instant-lyrics-24.png similarity index 100% rename from icons/instant-lyrics-24.png rename to InstantLyrics/icons/instant-lyrics-24.png diff --git a/icons/instant-lyrics-256.png b/InstantLyrics/icons/instant-lyrics-256.png similarity index 100% rename from icons/instant-lyrics-256.png rename to InstantLyrics/icons/instant-lyrics-256.png diff --git a/icons/instant-lyrics-32.png b/InstantLyrics/icons/instant-lyrics-32.png similarity index 100% rename from icons/instant-lyrics-32.png rename to InstantLyrics/icons/instant-lyrics-32.png diff --git a/icons/instant-lyrics-512.png b/InstantLyrics/icons/instant-lyrics-512.png similarity index 100% rename from icons/instant-lyrics-512.png rename to InstantLyrics/icons/instant-lyrics-512.png diff --git a/icons/instant-lyrics.svg b/InstantLyrics/icons/instant-lyrics.svg similarity index 100% rename from icons/instant-lyrics.svg rename to InstantLyrics/icons/instant-lyrics.svg diff --git a/InstantLyrics/lyrics/genius.py b/InstantLyrics/lyrics/genius.py index 3530a99..b34f345 100644 --- a/InstantLyrics/lyrics/genius.py +++ b/InstantLyrics/lyrics/genius.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup -from lyrics.models import InternetLyricsFetcher +from InstantLyrics.lyrics.models import InternetLyricsFetcher class GeniusFetcher(InternetLyricsFetcher): diff --git a/InstantLyrics/lyrics/local.py b/InstantLyrics/lyrics/local.py index f6db60f..b3b5248 100644 --- a/InstantLyrics/lyrics/local.py +++ b/InstantLyrics/lyrics/local.py @@ -3,7 +3,7 @@ import os -from lyrics.models import LyricsFetcher +from InstantLyrics.lyrics.models import LyricsFetcher def get_local_lyrics_filename(query, folder): diff --git a/InstantLyrics/lyrics/lyricstranslate.py b/InstantLyrics/lyrics/lyricstranslate.py index 92e93b7..62f25b4 100644 --- a/InstantLyrics/lyrics/lyricstranslate.py +++ b/InstantLyrics/lyrics/lyricstranslate.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup -from lyrics.models import InternetLyricsFetcher +from InstantLyrics.lyrics.models import InternetLyricsFetcher class LyricsTranslateFetcher(InternetLyricsFetcher): diff --git a/InstantLyrics/lyrics/metrolyrics.py b/InstantLyrics/lyrics/metrolyrics.py index 7243b8d..54f714c 100644 --- a/InstantLyrics/lyrics/metrolyrics.py +++ b/InstantLyrics/lyrics/metrolyrics.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup -from lyrics.models import InternetLyricsFetcher +from InstantLyrics.lyrics.models import InternetLyricsFetcher class MetrolyricsFetcher(InternetLyricsFetcher): diff --git a/InstantLyrics/lyrics/utils.py b/InstantLyrics/lyrics/utils.py index 4debaa8..0eec137 100644 --- a/InstantLyrics/lyrics/utils.py +++ b/InstantLyrics/lyrics/utils.py @@ -3,10 +3,13 @@ import os -from lyrics.genius import LYRICS_FINDERS as GENIUS_FINDERS -from lyrics.local import LocalLyricsFetcher, get_local_lyrics_filename -from lyrics.lyricstranslate import LYRICS_FINDERS as LYRICSTRANSLATE_FINDERS -from lyrics.metrolyrics import LYRICS_FINDERS as METROLYRICS_FINDERS +from InstantLyrics.lyrics.genius import LYRICS_FINDERS as GENIUS_FINDERS +from InstantLyrics.lyrics.local import LocalLyricsFetcher, \ + get_local_lyrics_filename +from InstantLyrics.lyrics.lyricstranslate import \ + LYRICS_FINDERS as LYRICSTRANSLATE_FINDERS +from InstantLyrics.lyrics.metrolyrics import \ + LYRICS_FINDERS as METROLYRICS_FINDERS LOCAL_LYRICS_FOLDER = os.path.join( os.getenv("HOME"), diff --git a/InstantLyrics/utils.py b/InstantLyrics/utils.py index 154dd0b..eddb319 100644 --- a/InstantLyrics/utils.py +++ b/InstantLyrics/utils.py @@ -4,7 +4,7 @@ import configparser import os -from app.settings import CONFIG_PATH +from InstantLyrics.app.settings import CONFIG_PATH BUG_ISSUES_REPORT_ERROR = "If that's not the case, report an issue
Date: Wed, 26 Sep 2018 11:46:49 +0200 Subject: [PATCH 22/25] fix app icon path --- InstantLyrics/app/windows.py | 2 +- InstantLyrics/utils.py | 15 ++++++++------- README.md | 10 ++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/InstantLyrics/app/windows.py b/InstantLyrics/app/windows.py index a3241fa..97520c3 100644 --- a/InstantLyrics/app/windows.py +++ b/InstantLyrics/app/windows.py @@ -164,7 +164,7 @@ class PreferenceWindow(Gtk.Window): def __init__(self, app): Gtk.Window.__init__(self, title="Instant-Lyrics Prefenreces") self.set_icon_from_file( - utils.get_icon_path('../icons/instant-lyrics-32.png')) + utils.get_default_icon_path()) self.set_border_width(20) # self.set_default_size(350, 550) self.set_position(Gtk.WindowPosition.CENTER) diff --git a/InstantLyrics/utils.py b/InstantLyrics/utils.py index eddb319..ea20aa3 100644 --- a/InstantLyrics/utils.py +++ b/InstantLyrics/utils.py @@ -46,21 +46,22 @@ def get_default_icon_path(): return get_icon_path('./icons/instant-lyrics-32.png') -def create_desktop_entry(): - src_path = os.path.dirname(os.path.realpath(__file__)) # path of scr folder - base_path = os.path.dirname(src_path) # path of base folder +def get_desktop_icon_path(): + return get_icon_path('./icons/instant-lyrics-256.png') + +def create_desktop_entry(): entry = "[Desktop Entry]\n" v = "Version=1.0\n" tp = "Type=Application\n" nm = "Name=Instant Lyrics\n" - ic = "Icon=" + base_path + "/icons/instant-lyrics-256.png\n" - ex = "Exec=python3 " + base_path + "/cmd.py\n" + ic = "Icon=" + get_desktop_icon_path() + "\n" + ex = "Exec=instantlyrics\n" cm = "Comment=Shows lyrics of songs instantly\n" tm = "Terminal=false\n" - entry_path = os.getenv( - "HOME") + "/.local/share/applications/instant-lyrics.desktop" + entry_path = os.getenv("HOME") + \ + "/.local/share/applications/Instant-Lyrics.desktop" with open(entry_path, 'w') as file: file.write(entry) diff --git a/README.md b/README.md index 7f7347b..5d19da3 100644 --- a/README.md +++ b/README.md @@ -36,18 +36,16 @@ First, install the requirements: sudo apt install python-gi python-dbus gir1.2-appindicator3-0.1 python-requests python-bs4 python-lxml ``` -(requests, lxml and bs4 can be install from `pip` also: `pip install requests lxml beautifiulsoup4`) - ### For Arch users ``` bash -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-beautifulsoup4 python3-gobject libappindicator-gtk3 ``` ### Fedora ``` bash -sudo dnf install dbus-python python-gobject libappindicator-gtk3 python2-requests python-beautifulsoup4 python2-lxml +sudo dnf install dbus-python python-gobject libappindicator-gtk3 python3-requests python-beautifulsoup4 python3-lxml ``` Then, enter the commands: @@ -55,8 +53,8 @@ Then, enter the commands: ``` bash git clone https://github.com/bhrigu123/Instant-Lyrics.git cd Instant-Lyrics/ -python InstantLyrics.py # `python InstantLyrics.py &` to keep it running in -background +pip3 install .# this installs requests, lxml and bs4 +instantlyrics # `instantlyrics.py &` to keep it running in background ``` The icon will appear in the system tray (indicator panel). You can start using the application from there. From e8d5c80d4cb59bdf3648d3a595f8e978e96d6b57 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Wed, 26 Sep 2018 11:53:39 +0200 Subject: [PATCH 23/25] add appimage recipe --- VERSION | 1 + appimage/Instant-Lyrics.yml | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 VERSION create mode 100644 appimage/Instant-Lyrics.yml diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..da05b92 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +Version: 0.7.0 (315666d826caffdfee31968cd6463488987db8f5 at 11:46:49 18-09-26 +0200) \ No newline at end of file diff --git a/appimage/Instant-Lyrics.yml b/appimage/Instant-Lyrics.yml new file mode 100644 index 0000000..ac9285d --- /dev/null +++ b/appimage/Instant-Lyrics.yml @@ -0,0 +1,20 @@ +app: instantlyrics +binpatch: true + +ingredients: + dist: trusty + sources: + - deb http://archive.ubuntu.com/ubuntu/ trusty main universe + packages: + - python-gi + - python-dbus + - gir1.2-appindicator3-0.1 + - python-requests + - python-bs4 + - python-lxml + script: + - wget -c https://github.com/sirfoga/Instant-Lyrics/archive/master.zip + - unzip master.zip + +script: + - pip3 install . From 66ff73bf65a2034ccd8fb05d69ba9ffd971a52d9 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Thu, 21 Mar 2019 17:52:31 +0100 Subject: [PATCH 24/25] fix readme --- README.md | 104 +++++++++++++++--------------------------------------- 1 file changed, 28 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 5d19da3..6d5bb48 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,53 @@ -
-

Instant-Lyrics

-

Instantly fetches the lyrics of the currently playing song and displays it on a window.

-A linux application with a very convenient GUI. Built with Python 3 Gtk+3 (gi).

+

-# Screenshot -![Screenshot](https://cloud.githubusercontent.com/assets/6123105/23824316/3fe58044-069a-11e7-804e-180ea4041002.jpeg) - -# Working example -![Working](https://cloud.githubusercontent.com/assets/6123105/23824730/e0e0829e-06a1-11e7-8d57-3235c4266f2c.gif) - -# Compatibility -- Python 3 -- Linux -# Installation +## Install -### system requirements (use `apt`, `pacman`, `dnf` ... your package manager): +### system requirements (use `apt`, `pacman`, `dnf`...): - python-gi (PyGObject) - python-dbus - AppIndicator3 ### python requirements (use `pip2` or `pip3`): -- requests -- beautifulsoup4 -- lxml - - -First, install the requirements: - -### For Ubuntu/Debian based systems: - -``` bash -sudo apt install python-gi python-dbus gir1.2-appindicator3-0.1 python-requests python-bs4 python-lxml +```bash +pip install . ``` -### For Arch users - +## Usage ``` bash -sudo pacman -S python3-dbus python3-requests python3-lxml python3-beautifulsoup4 python3-gobject libappindicator-gtk3 -``` - -### Fedora - -``` bash -sudo dnf install dbus-python python-gobject libappindicator-gtk3 python3-requests python-beautifulsoup4 python3-lxml -``` - -Then, enter the commands: - -``` bash -git clone https://github.com/bhrigu123/Instant-Lyrics.git -cd Instant-Lyrics/ -pip3 install .# this installs requests, lxml and bs4 instantlyrics # `instantlyrics.py &` to keep it running in background ``` The icon will appear in the system tray (indicator panel). You can start using the application from there. -
- -# Creating a launcher shortcut - -If you have installed from source, you can go to **Preferences** from the menu options, and click on the button `Create Desktop Entry`. -You should be able to see the `Instant Lyrics` application shortcut in your launcher menu. -You can also find several manual ways of doing so from the web. - -![Launcher](https://cloud.githubusercontent.com/assets/6123105/23824317/4735e83e-069a-11e7-8b1e-2814632bb3aa.jpeg) - +## Got questions? +If you have questions or general suggestions, don't hesitate to submit +a new [Github issue](https://github.com/sirfoga/Instant-Lyrics/issues/new). -# Contribution -Create an issue to discuss the changes/modifications before sending a PR. +## Contributing +[Fork](https://github.com/sirfoga/Instant-Lyrics/fork) | Patch | Push | [Pull request](https://github.com/sirfoga/Instant-Lyrics/pulls) -# Icon Credits -Icon made by [Freepik](http://www.freepik.com/) from www.flaticon.com +## Feedback +Suggestions and improvements are [welcome](https://github.com/sirfoga/Instant-Lyrics/issues)! -# License -## The MIT License -> Copyright (c) 2017 Bhrigu Srivastava http://bhrigu.me +## Authors -> Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +| [![sirfoga](https://avatars0.githubusercontent.com/u/14162628?s=128&v=4)](https://github.com/sirfoga "Follow @sirfoga on Github") | +|---| +| [Stefano Fogarollo](https://sirfoga.github.io) | -> The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +## Thanks +Thanks to [Bhrigu Srivastava](https://github.com/bhrigu123), the owner of the [original repository](https://github.com/bhrigu123/Instant-Lyrics). -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +## License +All of the codebases are **[MIT licensed](https://opensource.org/licenses/MIT)** unless otherwise specified. +**[Back to top](#topOfReadme)** From 46f983ce848fb77e949a6866b136354a28d9c957 Mon Sep 17 00:00:00 2001 From: sirfoga Date: Thu, 21 Mar 2019 17:55:37 +0100 Subject: [PATCH 25/25] remove version --- VERSION | 1 - setup.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 VERSION diff --git a/VERSION b/VERSION deleted file mode 100644 index da05b92..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -Version: 0.7.0 (315666d826caffdfee31968cd6463488987db8f5 at 11:46:49 18-09-26 +0200) \ No newline at end of file diff --git a/setup.py b/setup.py index 279bc06..b03ebe4 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ setup( name="Instant-Lyrics", - version="2.0", + version="2.0.1", author="sirfoga", author_email='captain.bhrigu@gmail.com,nicolas.guichard@ensimag.fr,' 'sirfoga@protonmail.com',