From c7ed5e997e6c1aa3e425c79816d8c3f06f3bc238 Mon Sep 17 00:00:00 2001 From: Timothy D Witham Date: Wed, 24 Jan 2024 23:55:46 -0600 Subject: [PATCH] metadata/Music: adjust our lyrics *.py to use CU LRC unmodified by moving our CLI from *.py to common/culrcwrap.py which uses tiny Kodistubs to let the few xbmc calls work. See README for simple upgrade procedure for any future CU LRC upgrades. --- .../metadata/Music/lyrics/Kodistubs/README | 8 + .../metadata/Music/lyrics/Kodistubs/xbmc.py | 10 + .../Music/lyrics/Kodistubs/xbmcaddon.py | 29 ++ .../Music/lyrics/Kodistubs/xbmcgui.py | 38 +++ .../Music/lyrics/Kodistubs/xbmcvfs.py | 56 ++++ .../scripts/metadata/Music/lyrics/README | 93 ++++-- .../scripts/metadata/Music/lyrics/azlyrics.py | 173 +--------- .../metadata/Music/lyrics/common/__init__.py | 5 +- .../metadata/Music/lyrics/common/audiofile.py | 127 ------- .../metadata/Music/lyrics/common/broken.py | 12 + .../metadata/Music/lyrics/common/culrcwrap.py | 177 ++++++++++ .../Music/lyrics/common/filelyrics.py | 43 +++ .../metadata/Music/lyrics/common/testall.py | 15 + .../metadata/Music/lyrics/common/utilities.py | 44 --- .../metadata/Music/lyrics/darklyrics.py | 253 +------------- .../scripts/metadata/Music/lyrics/embedlrc.py | 311 +++--------------- .../metadata/Music/lyrics/filelyrics.py | 180 ++-------- .../scripts/metadata/Music/lyrics/genius.py | 197 +---------- .../scripts/metadata/Music/lyrics/lrclib.py | 195 +---------- .../metadata/Music/lyrics/lyricscom.py | 202 +----------- .../metadata/Music/lyrics/lyricsify.py | 206 +----------- .../metadata/Music/lyrics/lyricsmode.py | 188 +---------- .../metadata/Music/lyrics/megalobiz.py | 195 +---------- .../scripts/metadata/Music/lyrics/music163.py | 204 ++---------- .../metadata/Music/lyrics/musixmatch.py | 215 +----------- .../metadata/Music/lyrics/musixmatchlrc.py | 246 ++------------ .../metadata/Music/lyrics/supermusic.py | 195 +---------- .../metadata/Music/lyrics/testlyrics.pl | 78 +++++ 28 files changed, 807 insertions(+), 2888 deletions(-) create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/README create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmc.py create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcaddon.py create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcgui.py create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcvfs.py delete mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/common/audiofile.py create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/common/broken.py create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/common/culrcwrap.py create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/common/filelyrics.py create mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/common/testall.py delete mode 100644 mythtv/programs/scripts/metadata/Music/lyrics/common/utilities.py create mode 100755 mythtv/programs/scripts/metadata/Music/lyrics/testlyrics.pl diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/README b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/README new file mode 100644 index 00000000000..0bb2fc70e80 --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/README @@ -0,0 +1,8 @@ +This is a tiny portion of Kodistubs to translate CU LRC to MythTV. + +If CU LRC ever refers to other functions, simply copy them in here +from original Kodistubs and update as needed. + + -twitham@sbcglobal.net, 2024/01 for v34 + +source: https://github.com/romanvm/Kodistubs diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmc.py b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmc.py new file mode 100644 index 00000000000..c4ea87c4b29 --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmc.py @@ -0,0 +1,10 @@ +# Tiny portion of Kodistubs to translate CU LRC to MythTV. If CU LRC +# ever refers to other functions, simply copy them in here from +# original Kodistubs and update where needed like below. + +import sys + +LOGDEBUG = 0 + +def log(msg: str, level: int = LOGDEBUG) -> None: + print(msg, file=sys.stderr) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcaddon.py b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcaddon.py new file mode 100644 index 00000000000..a0d2149e5f6 --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcaddon.py @@ -0,0 +1,29 @@ +# Tiny portion of Kodistubs to translate CU LRC to MythTV. If CU LRC +# ever refers to other functions, simply copy them in here from +# original Kodistubs and update where needed like below. + +from typing import Optional + +class Addon: + + def __init__(self, id: Optional[str] = None) -> None: + pass + + def getLocalizedString(self, id: int) -> str: + # only testall.py / scrapertest.py needs only 1 message from + # resources/language/resource.language.en_us/strings.po + if (id == 32163): + return "Testing: %s" + return "(%s)" % id + + def getSettingBool(self, id: str) -> bool: + return True + + def getSettingInt(self, id: str) -> int: + return 0 + + def getSettingString(self, id: str) -> str: + return "" + + def getAddonInfo(self, id: str) -> str: + return "" diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcgui.py b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcgui.py new file mode 100644 index 00000000000..dee8e481166 --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcgui.py @@ -0,0 +1,38 @@ +# Tiny portion of Kodistubs to translate CU LRC to MythTV. If CU LRC +# ever refers to other functions, simply copy them in here from +# original Kodistubs and update where needed like below. + +# show "dialog" on stderr for testall.py / scrapertest.py +import sys + +class Dialog: + + def __init__(self) -> None: + pass + + def ok(self, heading: str, message: str) -> bool: + return True + +class DialogProgress: + + def __init__(self) -> None: + pass + + def create(self, heading: str, message: str = "") -> None: + print("\tDIALOG created: ", heading, " : ", message, file=sys.stderr) + pass + + def update(self, percent: int, message: str = "") -> None: + print("\tDIALOG updated %s: " % percent, message, file=sys.stderr) + pass + + def close(self) -> None: + pass + + def iscanceled(self) -> bool: + # not cancelled is needed to continue the testall.py / scrapertest.py + return False + +class Window: + def __init__(self, existingWindowId: int = -1) -> None: + pass diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcvfs.py b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcvfs.py new file mode 100644 index 00000000000..e31ae388732 --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/Kodistubs/xbmcvfs.py @@ -0,0 +1,56 @@ +# Tiny portion of Kodistubs to translate CU LRC to MythTV. If CU LRC +# ever refers to other functions, simply copy them in here from +# original Kodistubs and update where needed like below. + +from typing import Union, Optional +import os.path + +# embedlrc.py and musixmatchlrc.py need some File access + +class File: + + def __init__(self, filepath: str, mode: Optional[str] = None) -> None: + self.filename = filepath + if mode: + self.fh = open(filepath, mode) + else: + self.fh = open(filepath, "rb") + + def __enter__(self) -> 'File': # Required for context manager + return self + + def __exit__(self, exc_type, exc_val, exc_tb): # Required for context manager + pass + + def read(self, numBytes: int = 0) -> str: + if numBytes: + return self.fh.read(numBytes) + return self.fh.read() + + def readBytes(self, numBytes: int = 0) -> bytearray: + if numBytes: + return bytearray(self.fh.read(numBytes)) + return bytearray(self.fh.read()) + + def write(self, buffer: Union[str, bytes, bytearray]) -> bool: + return self.fh.write(buffer) + + def size(self) -> int: + return 0 + + def seek(self, seekBytes: int, iWhence: int = 0) -> int: + return self.fh.seek(seekBytes, iWhence); + + def tell(self) -> int: + return self.fh.tell() + + def close(self) -> None: + self.fh.close() + pass + +def exists(path: str) -> bool: + # for musixmatchlrc.py the test must work or return False + return os.path.isfile(path) + +def translatePath(path: str) -> str: + return "" diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/README b/mythtv/programs/scripts/metadata/Music/lyrics/README index e2aebc19351..169fdea34e5 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/README +++ b/mythtv/programs/scripts/metadata/Music/lyrics/README @@ -1,10 +1,80 @@ -Lyrics Grabbers -=============== +Lyrics Grabbers for MythMusic of MythTV +======================================= You can add your own grabbers to this directory and MythMusic will automatically use them so long as they comply with the other grabbers. -The grabber should support these command line options :- +The included grabbers are sourced from the CU LRC Kodi addon: + + https://gitlab.com/ronie/script.cu.lrclyrics + + +Copyright / LICENSE +=================== + +CU LRC and therefore this directory are licensed under the terms of +the GNU GPL version 2, see ./LICENSE.txt for details. + + +Grabbers as/of 2024/01, Their Priority And Whether Synchronized +=============================================================== + +EmbeddedLyrics 50 Yes/No +FileLyrics 90 Yes/No +musixmatchlrc 100 Yes NEW in v34 +lrclib 110 Yes NEW in v34 +lyricsify 130 Yes NEW in v34 +genius 200 No +musixmatch 210 No NEW in v34 +lyricsmode 220 No +azlyrics 230 No NEW in v34 +lyricscom 240 No +supermusic 250 No NEW in v34 +darklyrics 260 No +megalobiz 400 Yes (too slow to be earlier, was 140) +music163 500 Yes (sometimes returns author only, was 120) + + +To Upgrade to latest CU LRC +=========================== + +Simply overwrite lib/ with the latest from the source: + + https://gitlab.com/ronie/script.cu.lrclyrics + +* remove any ./*.py that has been removed from lib/culrcscrapers + +* add a new ./*.py for any new scraper in lib/culrcscrapers + +* adjust the fields of info{} as needed + +* overwrite ./changelog.txt from the source, compare its head to line + 2 of addon.xml in the source and update the latest version number + into the header of ./common/culrcwrap.py + +* ./testlyrics -k -t + +That's all! + +As of v34 in 2024/01 this is how we use CU LRC unmodified: + +Only a small boilerplate bootstrap file of a few lines of information +is needed in this directory. These files import only the +LyricsFetcher from ./lib/culrcscrapers or from ./common for any +additional grabbers like filelyrics.py. Finally they run main from +./common/culrcwrap.py which implements the below CLI translation to +MythTV via a very tiny and simple ./Kodistubs. + +Custom grabbers can similarly use ./common/culrcwrap.py for the CLI +and simply put their LyricsFetcher in ./common, like filelyrics did. + + +Required command line options for all grabbers +============================================== + +(automatically done by ./common/culrcwrap.py) + +All grabbers should support these command line options: Options: -h, --help show this help message and exit @@ -87,20 +157,3 @@ Options: -Current Grabbers, Their Priority And Whether Synchronized -========================================================= - -EmbeddedLyrics 50 Yes/No -FileLyrics 90 Yes/No -musixmatchlrc 100 Yes NEW in v34 -lrclib 110 Yes NEW in v34 -lyricsify 130 Yes NEW in v34 -genius 200 No -musixmatch 210 No NEW in v34 -lyricsmode 220 No -azlyrics 230 No NEW in v34 -lyricscom 240 No -supermusic 250 No NEW in v34 -darklyrics 260 No -megalobiz 400 Yes (too slow to be earlier, was 140) -music163 500 Yes (returns incomplete results, was 120) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/azlyrics.py b/mythtv/programs/scripts/metadata/Music/lyrics/azlyrics.py index 7ddaa8b40a1..24a9b59603f 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/azlyrics.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/azlyrics.py @@ -1,159 +1,18 @@ -#-*- coding: UTF-8 -*- -""" -Scraper for http://www.azlyrics.com/ -ronie -""" - -import sys -import re -import requests -import html -from optparse import OptionParser -from common import utilities - -__author__ = "ronie" -__title__ = "Azlyrics" -__description__ = "Search http://www.azlyrics.com/ for lyrics" -__version__ = "0.1" -__priority__ = "230" -__syncronized__ = False - -debug = False - -class LyricsFetcher: - def __init__( self ): - self.url = 'https://www.azlyrics.com/lyrics/%s/%s.html' - - def get_lyrics(self, lyrics): - utilities.log(debug, '%s: searching lyrics for %s - %s' % (__title__, lyrics.artist, lyrics.title)) - artist = re.sub("[^a-zA-Z0-9]+", "", lyrics.artist).lower().lstrip('the ') - title = re.sub("[^a-zA-Z0-9]+", "", lyrics.title).lower() - try: - req = requests.get(self.url % (artist, title), timeout=10) - response = req.text - - except: - return False - req.close() - try: - lyricscode = response.split('t. -->')[1].split('', '\n') - lyr = re.sub('<[^<]+?>', '', lyricstext) - lyrics.lyrics = lyr - return True - except: - return False - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'azlyrics.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Perform self-test for dependencies.") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - if (len(args) > 0): - utilities.log('ERROR: invalid arguments found') - sys.exit(1) - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.azlyrics.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': 'AZLyrics', + 'description': 'Search https://azlyrics.com for lyrics', + 'author': 'ronie', + 'priority': '230', + 'syncronized': False, + 'artist': 'La Dispute', + 'title': 'Such Small Hands', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/common/__init__.py b/mythtv/programs/scripts/metadata/Music/lyrics/common/__init__.py index 391fdc6bbe8..8c0d5d5bb20 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/common/__init__.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/common/__init__.py @@ -1,4 +1 @@ -__version__ = "1.0.0" - -from . import utilities, audiofile - +__version__ = "2.0.0" diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/common/audiofile.py b/mythtv/programs/scripts/metadata/Music/lyrics/common/audiofile.py deleted file mode 100644 index 43d12c06ecc..00000000000 --- a/mythtv/programs/scripts/metadata/Music/lyrics/common/audiofile.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- -""" -read audio stream from audio file -""" - -import os -import struct - -class UnknownFormat(Exception):pass -class FormatError(Exception):pass - -class AudioFile(object): - f = None - audioStart = 0 - - def AudioFile(self): - self.f = None - self.audioStart = 0 - - def Open(self,filename): - self.audioStart = 0 - self.f = file(filename) - ext = os.path.splitext(filename)[1].lower() - if ext == '.mp3': self.AnalyzeMp3() - elif ext == '.ogg': self.AnalyzeOgg() - elif ext == '.wma': self.AnalyzeWma() - #elif ext == '.flac': self.AnalyzeFlac() - elif ext == '.flac': pass - elif ext == '.ape': pass - elif ext == '.wav': pass - else: # not supported format - self.f.close() - self.f = None - raise UnknownFormat - - def Close(self): - self.f.close() - self.f = None - - def ReadAudioStream(self, len, offset=0): - self.f.seek(self.audioStart+offset, 0) - return self.f.read(len) - - def AnalyzeMp3(self): - # Searching ID3v2 tag - while True: - buf = self.f.read(3) - if len(buf) < 3 or self.f.tell() > 50000: - # ID tag is not found - self.f.seek(0,0) - self.audioStart = 0 - return - if buf == 'ID3': - self.f.seek(3,1) # skip version/flag - # ID length (synchsafe integer) - tl = struct.unpack('4b', self.f.read(4)) - taglen = (tl[0]<<21)|(tl[1]<<14)|(tl[2]<<7)|tl[3] - self.f.seek(taglen,1) - break - self.f.seek(-2,1) - # Searching MPEG SOF - while True: - buf = self.f.read(1) - if len(buf) < 1 or self.f.seek(0,1) > 1000000: - raise FormatError - if buf == '\xff': - rbit = struct.unpack('B',self.f.read(1))[0] >> 5 - if rbit == 7: # 11 1's in total - self.f.seek(-2,1) - self.audioStart = self.f.tell() - return - - def AnalyzeOgg(self): - # Parse page (OggS) - while True: - buf = self.f.read(27) # header - if len(buf) < 27 or self.f.tell() > 50000: - # parse error - raise FormatError - if buf[0:4] != 'OggS': - # not supported page format - raise UnknownFormat - numseg = struct.unpack('B', buf[26])[0] - #print "#seg: %d" % numseg - - segtbl = struct.unpack('%dB'%numseg, self.f.read(numseg)) # segment table - for seglen in segtbl: - buf = self.f.read(7) # segment header - #print "segLen(%s): %d" % (buf[1:7],seglen) - if buf == "\x05vorbis": - self.f.seek(-7,1) # rollback - self.audioStart = self.f.tell() - return - self.f.seek(seglen-7,1) # skip to next segment - - def AnalyzeWma(self): - # Searching GUID - while True: - buf = self.f.read(16) - if len(buf) < 16 or self.f.tell() > 50000: - raise FormatError - guid = buf.encode("hex"); - if guid == "3626b2758e66cf11a6d900aa0062ce6c": - # ASF_Data_Object - self.f.seek(-16,1) # rollback - self.audioStart = self.f.tell() - return - else: - objlen = struct.unpack(' 50000: - # not found - raise FormatError - metalen = buf[1] | (buf[2]<<8) | (buf[3]<<16); - self.f.seek(metalen,1) # skip this metadata block - if buf[0] & 0x80: - # it was the last metadata block - self.audioStart = self.f.tell() - return - diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/common/broken.py b/mythtv/programs/scripts/metadata/Music/lyrics/common/broken.py new file mode 100644 index 00000000000..54a56c794a2 --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/common/broken.py @@ -0,0 +1,12 @@ +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- +""" +Scraper for broken scrapers - do nothing +""" + +class LyricsFetcher: + def __init__(self, *args, **kwargs): + self.DEBUG = kwargs['debug'] + self.settings = kwargs['settings'] + + def get_lyrics(self, song): + return False diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/common/culrcwrap.py b/mythtv/programs/scripts/metadata/Music/lyrics/common/culrcwrap.py new file mode 100644 index 00000000000..da62d2f29c0 --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/common/culrcwrap.py @@ -0,0 +1,177 @@ +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- +""" +Wrapper for using CU LRC scrapers with MythMusic of MythTV + +Paul Harrison, ronie, Timothy Witham +""" + +# UPDATE THIS from https://gitlab.com/ronie/script.cu.lrclyrics +# second line of its addon.xml: +__version__ = '6.6.2' + +# simulate kodi/xbmc via very simplified Kodistubs. +import os +import sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + '/../Kodistubs') +from lib.utils import * +from optparse import OptionParser + +# album is never searched, but it is given in the xml to mythtv +class Song(Song): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) # from ../lib/utils.py + self.album = '' + +debug = False + +lyricssettings = {} +lyricssettings['debug'] = ADDON.getSettingBool('log_enabled') +lyricssettings['save_filename_format'] = ADDON.getSettingInt('save_filename_format') +lyricssettings['save_lyrics_path'] = ADDON.getSettingString('save_lyrics_path') +lyricssettings['save_subfolder'] = ADDON.getSettingBool('save_subfolder') +lyricssettings['save_subfolder_path'] = ADDON.getSettingString('save_subfolder_path') + +def getCacheDir(): + confdir = os.environ.get('MYTHCONFDIR', '') + + if (not confdir) or (confdir == '/'): + confdir = os.environ.get('HOME', '') + + if (not confdir) or (confdir == '/'): + log("Unable to find MythTV directory for metadata cache.", debug=True) + return '/tmp' + + confdir = os.path.join(confdir, '.mythtv') + cachedir = os.path.join(confdir, 'cache') + + if not os.path.exists(cachedir): + os.makedirs(cachedir) + + return cachedir + +def performSelfTest(): + try: + from bs4 import BeautifulSoup + except: + log("Failed to import BeautifulSoup. This grabber requires python-bs4", debug=True) + sys.exit(1) + + found = False + song = Song(opt=lyricssettings) + song.artist = about.get('artist') + song.title = about.get('title') + song.album = about.get('album') + song.filepath = about.get('filename') + + fetcher = LyricsFetcher(settings=lyricssettings, debug=True) + lyrics = fetcher.get_lyrics(song) + + if lyrics: + if debug: + print(lyrics.lyrics) + try: + buildLyrics(song, lyrics) + except: + log("Failed to build lyrics xml file. " + "Maybe you don't have lxml installed?", debug=True) + sys.exit(1) + + log("Everything appears in order.", debug=True) + sys.exit(0) + + log("Failed to find the lyrics for the test search!", debug=True) + sys.exit(1) + +def buildLyrics(song, lyrics): + from lxml import etree + xml = etree.XML(u'') + etree.SubElement(xml, "artist").text = song.artist + etree.SubElement(xml, "album").text = song.album + etree.SubElement(xml, "title").text = song.title + etree.SubElement(xml, "syncronized").text = 'True' if about['syncronized'] else 'False' + etree.SubElement(xml, "grabber").text = about['name'] + + lines = lyrics.lyrics.splitlines() + for line in lines: + etree.SubElement(xml, "lyric").text = line + + print(etree.tostring(xml, encoding='UTF-8', + pretty_print=True, xml_declaration=True).decode()) + +def buildVersion(): + from lxml import etree + xml = etree.XML(u'') + etree.SubElement(xml, "name").text = about['name'] + etree.SubElement(xml, "author").text = about['author'] + etree.SubElement(xml, "command").text = about['command'] + etree.SubElement(xml, "type").text = 'lyrics' + etree.SubElement(xml, "description").text = about['description'] + etree.SubElement(xml, "version").text = about['version'] + etree.SubElement(xml, "priority").text = about['priority'] + etree.SubElement(xml, "syncronized").text = 'True' if about['syncronized'] else 'False' + + print(etree.tostring(xml, encoding='UTF-8', + pretty_print=True, xml_declaration=True).decode()) + sys.exit(0) + +def main(filename, info, fetcher): + global debug + global about + about = info + about['command'] = os.path.basename(filename) + if not about.get('version'): + about['version'] = __version__ + if not about.get('album'): + about['album'] = '' + global LyricsFetcher + LyricsFetcher = fetcher + + parser = OptionParser() + + parser.add_option('-v', "--version", action="store_true", default=False, + dest="version", help="Display version and author") + parser.add_option('-t', "--test", action="store_true", default=False, + dest="test", help="Test grabber with a known good search") + parser.add_option('-s', "--search", action="store_true", default=False, + dest="search", help="Search for lyrics.") + parser.add_option('-a', "--artist", metavar="ARTIST", default=None, + dest="artist", help="Artist of track.") + parser.add_option('-b', "--album", metavar="ALBUM", default=None, + dest="album", help="Album of track.") + parser.add_option('-n', "--title", metavar="TITLE", default=None, + dest="title", help="Title of track.") + parser.add_option('-f', "--filename", metavar="FILENAME", default=None, + dest="filename", help="Filename of track.") + parser.add_option('-d', '--debug', action="store_true", default=False, + dest="debug", help=("Show debug messages")) + + opts, args = parser.parse_args() + + song = Song(opt=lyricssettings) + + if opts.debug: + debug = True + + if opts.version: + buildVersion() + + if opts.test: + performSelfTest() + + if opts.artist: + song.artist = opts.artist + if opts.album: + song.album = opts.album + if opts.title: + song.title = opts.title + if opts.filename: + song.filepath = opts.filename + + fetcher = LyricsFetcher(settings=lyricssettings, debug=debug) + lyrics = fetcher.get_lyrics(song) + if lyrics: + buildLyrics(song, lyrics) + sys.exit(0) + else: + log("No lyrics found for this track", debug=True) + sys.exit(1) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/common/filelyrics.py b/mythtv/programs/scripts/metadata/Music/lyrics/common/filelyrics.py new file mode 100644 index 00000000000..23c0d4e547a --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/common/filelyrics.py @@ -0,0 +1,43 @@ +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- +""" +Scraper for file lyrics +""" + +from lib.utils import * + +__title__ = "FileLyrics" +__description__ = "Search the same directory as the track for lyrics" +__author__ = "Paul Harrison" +__version__ = "2.0" +__priority__ = "90" +__lrc__ = True + +class LyricsFetcher: + def __init__(self, *args, **kwargs): + self.DEBUG = kwargs['debug'] + self.settings = kwargs['settings'] + + def get_lyrics(self, song): + log("%s: searching lyrics for %s - %s - %s" % (__title__, song.artist, song.album, song.title), debug=self.DEBUG) + lyrics = Lyrics(settings=self.settings) + lyrics.song = song + lyrics.source = __title__ + lyrics.lrc = __lrc__ + + filename = song.filepath + filename = os.path.splitext(filename)[0] + + # look for a file ending in .lrc with the same filename as the track minus the extension + lyricFile = filename + '.lrc' + log("%s: searching for lyrics file: %s " % (__title__, lyricFile), debug=self.DEBUG) + if os.path.exists(lyricFile) and os.path.isfile(lyricFile): + #load the text file + with open (lyricFile, "r") as f: + lines = f.readlines() + + for line in lines: + lyrics.lyrics += line + + return lyrics + + return False diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/common/testall.py b/mythtv/programs/scripts/metadata/Music/lyrics/common/testall.py new file mode 100644 index 00000000000..833d2fc0035 --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/common/testall.py @@ -0,0 +1,15 @@ +#-*- coding: utf-8 -*- + +# simulate kodi/xbmc via very simplified Kodistubs. +import os +import sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + '/../Kodistubs') +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + '/..') + +from lib.scrapertest import * + +def main(): + test_scrapers(); + +if __name__ == '__main__': + main() diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/common/utilities.py b/mythtv/programs/scripts/metadata/Music/lyrics/common/utilities.py deleted file mode 100644 index bec61393340..00000000000 --- a/mythtv/programs/scripts/metadata/Music/lyrics/common/utilities.py +++ /dev/null @@ -1,44 +0,0 @@ -import sys -import os -import unicodedata - -def log(debug, txt): - if debug: - print(txt) - -class Lyrics: - def __init__(self): - self.artist = "" - self.album = "" - self.title = "" - self.filename = "" - self.lyrics = "" - self.source = "" - self.list = None - self.syncronized = False - -def deAccent(str): - return unicodedata.normalize('NFKD', str).replace('"', '') - -def convert_etree(etostr): - """lxml.etree.tostring is a bytes object in python3, and a str in python2. - """ - return(etostr.decode()) - -def getCacheDir(): - confdir = os.environ.get('MYTHCONFDIR', '') - - if (not confdir) or (confdir == '/'): - confdir = os.environ.get('HOME', '') - - if (not confdir) or (confdir == '/'): - print ("Unable to find MythTV directory for metadata cache.") - return '/tmp' - - confdir = os.path.join(confdir, '.mythtv') - cachedir = os.path.join(confdir, 'cache') - - if not os.path.exists(cachedir): - os.makedirs(cachedir) - - return cachedir diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/darklyrics.py b/mythtv/programs/scripts/metadata/Music/lyrics/darklyrics.py index 460cd557f0f..96567f765ee 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/darklyrics.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/darklyrics.py @@ -1,239 +1,18 @@ -#-*- coding: UTF-8 -*- -""" -Scraper for http://www.darklyrics.com/ - the largest metal lyrics archive on the Web. - -scraper by smory -""" - -import hashlib -import math -import requests -import urllib.parse -import re -import time -import chardet - -try: - from ctypes import c_int32 # ctypes not supported on xbox -except: - pass - -import sys -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and smory" -__title__ = "DarkLyrics" -__description__ = "Search http://www.darklyrics.com/ - the largest metal lyrics archive on the Web" -__priority__ = "260" -__version__ = "0.3" -__syncronized__ = False - -debug = False - -class LyricsFetcher: - - def __init__( self ): - self.base_url = "http://www.darklyrics.com/" - self.searchUrl = "http://www.darklyrics.com/search?q=%s" - self.cookie = self.getCookie() - def getCookie(self): - # http://www.darklyrics.com/tban.js - lastvisitts = 'Nergal' + str(math.ceil(time.time() * 1000 / (60 * 60 * 6 * 1000))) - lastvisittscookie = 0 - i = 0 - while i < len(lastvisitts): - try: - lastvisittscookie = c_int32((c_int32(lastvisittscookie<<5).value - c_int32(lastvisittscookie).value) + ord(lastvisitts[i])).value - except: - return - i += 1 - lastvisittscookie = lastvisittscookie & lastvisittscookie - return str(lastvisittscookie) - - def search(self, artist, title): - term = urllib.parse.quote((artist if artist else '') + '+' + (title if title else '')) - try: - headers = {'user-agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0'} - req = requests.get(self.searchUrl % term, headers=headers, cookies={'lastvisitts': self.cookie}, timeout=10) - searchResponse = req.text - - except: - return None - searchResult = re.findall('

(.*?)

', searchResponse) - if len(searchResult) == 0: - return None - links = [] - i = 0 - for result in searchResult: - a = [] - a.append(result[2] + (' ' + self.getAlbumName(self.base_url + result[0]) if i < 6 else '')) # title from server + album name - a.append(self.base_url + result[0]) # url with lyrics - a.append(artist) - a.append(title) - a.append(result[1]) # id of the side part containing this song lyrics - links.append(a) - i += 1 - return links - - def findLyrics(self, url, index): - try: - headers = {'user-agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0'} - req = requests.get(url, headers=headers, cookies={'lastvisitts': self.cookie}, timeout=10) - res = req.text - except: - return None - pattern = '(.*?)(?:

|', '') - s = s.replace('', '') - s = s.replace('', '') - s = s.replace('', '') - s = s.replace('

', '') - return s - else: - return None - - def getAlbumName(self, url): - try: - headers = {'user-agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0'} - req = requests.get(url, headers=headers, cookies={'lastvisitts': self.cookie}, timeout=10) - res = req.text - except: - return '' - match = re.search('

(?:album|single|ep|live):?\s?(.*?)

', res, re.IGNORECASE) - if match: - return ('(' + match.group(1) + ')').replace('\'', '') - else: - return '' - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - links = self.search(lyrics.artist, lyrics.title) - if(links == None or len(links) == 0): - return False - elif len(links) > 1: - lyrics.list = links - lyr = self.get_lyrics_from_list(links[0]) - if not lyr: - return False - lyrics.lyrics = lyr - return True - - - def get_lyrics_from_list(self, link): - title, url, artist, song, index = link - return self.findLyrics(url, index) - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Neurosis' - lyrics.album = '' - lyrics.title = 'Lost' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - line2 = re.sub(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\u10000-\u10FFFF]+', '', line) - etree.SubElement(xml, "lyric").text = line2 - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'darklyrics.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.darklyrics.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': 'DarkLyrics', + 'description': 'Search http://www.darklyrics.com/ - the largest metal lyrics archive on the Web', + 'author': 'Paul Harrison and smory', + 'priority': '260', + 'syncronized': False, + 'artist': 'Neurosis', + 'title': 'Lost', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/embedlrc.py b/mythtv/programs/scripts/metadata/Music/lyrics/embedlrc.py index ad163328fa9..fb631730157 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/embedlrc.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/embedlrc.py @@ -1,271 +1,46 @@ -#!/usr/bin/env python3 -# -*- coding: UTF-8 -*- -# ---------------------- -""" -Scraper for embedded lyrics -""" - -import sys, os, re, chardet -import xml.dom.minidom as xml -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and ronin" -__title__ = "EmbeddedLyrics" -__description__ = "Search tracks tag for embedded lyrics" -__version__ = "0.2" -__priority__ = "50" -__syncronized__ = True - -debug = False - -def getLyrics3(filename): - #Get lyrics embed with Lyrics3/Lyrics3V2 format - #See: http://id3.org/Lyrics3 - #http://id3.org/Lyrics3v2 - - utilities.log(debug, "%s: trying %s" % (__title__, "lyrics embed with Lyrics3/Lyrics3V2 format")) - - f = File(filename) - f.seek(-128-9, os.SEEK_END) - buf = f.read(9) - if (buf != "LYRICS200" and buf != "LYRICSEND"): - f.seek(-9, os.SEEK_END) - buf = f.read(9) - if (buf == "LYRICSEND"): - """ Find Lyrics3v1 """ - f.seek(-5100-9-11, os.SEEK_CUR) - buf = f.read(5100+11) - f.close(); - start = buf.find("LYRICSBEGIN") - elif (buf == "LYRICS200"): - """ Find Lyrics3v2 """ - f.seek(-9-6, os.SEEK_CUR) - size = int(f.read(6)) - f.seek(-size-6, os.SEEK_CUR) - buf = f.read(11) - if(buf == "LYRICSBEGIN"): - buf = f.read(size-11) - tags=[] - while buf!= '': - tag = buf[:3] - length = int(buf[3:8]) - content = buf[8:8+length] - if (tag == 'LYR'): - return content - buf = buf[8+length:] - f.close(); - return None - -def endOfString(string, utf16=False): - if (utf16): - pos = 0 - while True: - pos += string[pos:].find('\x00\x00') + 1 - if (pos % 2 == 1): - return pos - 1 - else: - return string.find('\x00') - -def ms2timestamp(ms): - mins = "0%s" % int(ms/1000/60) - sec = "0%s" % int((ms/1000)%60) - msec = "0%s" % int((ms%1000)/10) - timestamp = "[%s:%s.%s]" % (mins[-2:],sec[-2:],msec[-2:]) - return timestamp - -# Uses the high level interface in taglib to find the lyrics -# should work with all the tag formats supported by taglib that can have lyrics -def getLyricsGeneric(filename): - try: - import taglib - except: - utilities.log(True, "Failed to import taglib. This grabber requires " - "pytaglib TagLib bindings for Python. " - "https://github.com/supermihi/pytaglib") - return None - - try: - utilities.log(debug, "%s: trying to open %s" % (__title__, filename)) - f = taglib.File(filename) - - # see if we can find a lyrics tag - for tag in f.tags: - if tag.startswith('LYRICS'): - return f.tags[tag][0] - - return None - except: - return None - -# Get USLT/SYLT/TXXX lyrics embed with ID3v2 format -# See: http://id3.org/id3v2.3.0 -def getID3Lyrics(filename): - utilities.log(debug, "%s: trying %s" % (__title__, "lyrics embed with ID3v2 format")) - - # just use the generic taglib method for now - return getLyricsGeneric(filename) - -def getFlacLyrics(filename): - utilities.log(debug, "%s: trying %s" % (__title__, "lyrics embed with Flac format")) - - # just use the generic taglib method for now - return getLyricsGeneric(filename) - -def getMP4Lyrics(filename): - utilities.log(debug, "%s: trying %s" % (__title__, "lyrics embed with MP4 format")) - - # just use the generic taglib method for now - return getLyricsGeneric(filename) - +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +import os +from common import culrcwrap +from lib.embedlrc import * +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': '*EmbeddedLyrics', + 'description': 'Search track tags for embedded lyrics', + 'author': 'Paul Harrison and ronie', + 'priority': '50', # first, before filelyrics + 'syncronized': True, + 'artist': 'Robb Benson', + 'title': 'Lone Rock', + 'album': 'Demo Tracks', + 'filename': os.path.dirname(os.path.abspath(__file__)) + '/examples/taglyrics.mp3', +} + +# lib/embedlrc.py has no LyricsFetcher, so we create it here: class LyricsFetcher: - - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - filename = lyrics.filename - - ext = os.path.splitext(filename)[1].lower() - lry = None - - try: - if ext == '.mp3': - lry = getLyrics3(filename) - except: - pass - - if lry: - enc = chardet.detect(lry) - lyrics.lyrics = lry.decode(enc['encoding']) - else: - if ext == '.mp3': - lry = getID3Lyrics(filename) - elif ext == '.flac': - lry = getFlacLyrics(filename) - elif ext == '.m4a': - lry = getMP4Lyrics(filename) - if not lry: - return False - lyrics.lyrics = lry - - return True - - -def performSelfTest(): - try: - import taglib - except: - utilities.log(True, "Failed to import taglib. This grabber requires " - "pytaglib ? TagLib bindings for Python. " - "https://github.com/supermihi/pytaglib") - sys.exit(1) - - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Robb Benson' - lyrics.album = 'Demo Tracks' - lyrics.title = 'Lone Rock' - lyrics.filename = os.path.dirname(os.path.abspath(__file__)) + '/examples/taglyrics.mp3' - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'embedlrc.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Perform self-test for dependencies.") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) + def __init__(self, *args, **kwargs): + self.DEBUG = kwargs['debug'] + self.settings = kwargs['settings'] + + def get_lyrics(self, song): + log("%s: searching lyrics for %s - %s" + % (info['name'], song.artist, song.title), debug=self.DEBUG) + log("%s: searching file %s" + % (info['name'], song.filepath), debug=self.DEBUG) + log("%s: searching for SYNCHRONIZED lyrics" + % info['name'], debug=self.DEBUG) + lrc = getEmbedLyrics(song, True, culrcwrap.lyricssettings) + if lrc: + return lrc + log("%s: searching for NON-synchronized lyrics" + % info['name'], debug=self.DEBUG) + lrc = getEmbedLyrics(song, False, culrcwrap.lyricssettings) + if lrc: + return lrc + return None if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) + +# most of the code moved to lib/embedlrc.py diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/filelyrics.py b/mythtv/programs/scripts/metadata/Music/lyrics/filelyrics.py index 04c35cc22b4..98525097cfd 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/filelyrics.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/filelyrics.py @@ -1,160 +1,24 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# ---------------------- -""" -Scraper for file lyrics -""" - -import sys, os, re, chardet -import xml.dom.minidom as xml -from optparse import OptionParser -from common import * - -__author__ = "Paul Harrison" -__title__ = "FileLyrics" -__description__ = "Search the same directory as the track for lyrics" -__version__ = "0.1" -__priority__ = "90" -__syncronized__ = True - -debug = False - - -class LyricsFetcher: - - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - filename = lyrics.filename - filename = os.path.splitext(filename)[0] - - # look for a file ending in .lrc with the same filename as the track minus the extension - lyricFile = filename + '.lrc' - utilities.log(debug, "%s: searching for lyrics file: %s " % (__title__, lyricFile)) - if os.path.exists(lyricFile) and os.path.isfile(lyricFile): - #load the text file - with open (lyricFile, "r") as f: - lines = f.readlines() - - for line in lines: - lyrics.lyrics += line - - return True - - return False; - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Robb Benson' - lyrics.album = 'Demo Tracks' - lyrics.title = 'Lone Rock' - lyrics.filename = os.path.dirname(os.path.abspath(__file__)) + '/examples/filelyrics.mp3' - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - try: - buildLyrics(lyrics) - except: - utilities.log(True, "Failed to build lyrics xml file. " - "Maybe you don't have lxml installed?") - sys.exit(1) - - utilities.log(True, "Everything appears in order.") - sys.exit(0) - - utilities.log(True, "Failed to find the lyrics for the test search!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'filelyrics.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Perform self-test for dependencies.") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +import os +from common import culrcwrap +from common.filelyrics import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': '*FileLyrics', + 'description': 'Search the same directory as the track for lyrics', + 'author': 'Paul Harrison', + 'priority': '90', # before all remote web scrapers 100+ + 'version': '2.0', + 'syncronized': True, + 'artist': 'Robb Benson', + 'title': 'Lone Rock', + 'album': 'Demo Tracks', + 'filename': os.path.dirname(os.path.abspath(__file__)) + '/examples/filelyrics.mp3', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) + +# most of the code moved to common/filelyrics.py and common/main.py diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/genius.py b/mythtv/programs/scripts/metadata/Music/lyrics/genius.py index 68e5daf7d89..4a43a8a63f1 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/genius.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/genius.py @@ -1,183 +1,18 @@ -#-*- coding: UTF-8 -*- -""" -Scraper for http://www.genius.com - -taxigps -""" -import sys -import re -import urllib.parse -import requests -import html -import difflib -import json - -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and ronie" -__title__ = "Genius" -__description__ = "Search http://www.genius.com for lyrics" -__priority__ = "200" -__version__ = "0.1" -__syncronized__ = False - - -debug = False - -class LyricsFetcher: - def __init__( self ): - self.url = 'http://api.genius.com/search?q=%s%%20%s&access_token=Rq_cyNZ6fUOQr4vhyES6vu1iw3e94RX85ju7S8-0jhM-gftzEvQPG7LJrrnTji11' - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - try: - headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; rv:77.0) Gecko/20100101 Firefox/77.0'} - url = self.url % (urllib.parse.quote(lyrics.artist), urllib.parse.quote(lyrics.title)) - req = requests.get(url, headers=headers, timeout=10) - response = req.text - except: - return False - data = json.loads(response) - try: - name = data['response']['hits'][0]['result']['primary_artist']['name'] - track = data['response']['hits'][0]['result']['title'] - if (difflib.SequenceMatcher(None, lyrics.artist.lower(), name.lower()).ratio() > 0.8) and (difflib.SequenceMatcher(None, lyrics.title.lower(), track.lower()).ratio() > 0.8): - self.page = data['response']['hits'][0]['result']['url'] - else: - return False - except: - return False - utilities.log(debug, '%s: search url: %s' % (__title__, self.page)) - try: - headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; rv:77.0) Gecko/20100101 Firefox/77.0'} - req = requests.get(self.page, headers=headers, timeout=10) - response = req.text - except: - return False - response = html.unescape(response) - matchcode = re.findall('class="Lyrics__Container.*?">(.*?)', '\n', lyricscode) - lyr2 = re.sub('<[^<]+?>', '', lyr1) - lyr3 = lyr2.replace('\\n','\n').strip() - if not lyr3 or lyr3 == '[Instrumental]' or lyr3.startswith('Lyrics for this song have yet to be released'): - return False - lyrics.lyrics = lyr3 - return True - except: - return False - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'genius.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.genius.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': 'Genius', + 'description': 'Search http://www.genius.com for lyrics', + 'author': 'Paul Harrison and ronie', + 'priority': '200', + 'syncronized': False, + 'artist': 'Maren Morris', + 'title': 'My Church', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/lrclib.py b/mythtv/programs/scripts/metadata/Music/lyrics/lrclib.py index b2f2db2f5d7..6a5e3b69a27 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/lrclib.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/lrclib.py @@ -1,181 +1,18 @@ -#-*- coding: UTF-8 -*- -''' -Scraper for https://lrclib.net/ - -lrclib - -https://github.com/rtcq/syncedlyrics -''' - -import requests -import difflib - -import sys -from optparse import OptionParser -from common import * - -__author__ = "Paul Harrison and ronie" -__title__ = "LrcLib" -__description__ = "Search https://lrclib.net for lyrics" -__priority__ = "110" -__version__ = "0.1" -__syncronized__ = True - - -debug = False - -class LyricsFetcher: - def __init__( self ): - self.SEARCH_URL = 'https://lrclib.net/api/search?q=%s-%s' - self.LYRIC_URL = 'https://lrclib.net/api/get/%i' - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - try: - url = self.SEARCH_URL % (lyrics.artist, lyrics.title) - response = requests.get(url, timeout=10) - result = response.json() - except: - return False - links = [] - for item in result: - artistname = item['artistName'] - songtitle = item['name'] - songid = item['id'] - if (difflib.SequenceMatcher(None, lyrics.artist.lower(), artistname.lower()).ratio() > 0.8) and (difflib.SequenceMatcher(None, lyrics.title.lower(), songtitle.lower()).ratio() > 0.8): - links.append((artistname + ' - ' + songtitle, self.LYRIC_URL % songid, artistname, songtitle)) - if len(links) == 0: - return False - elif len(links) > 1: - lyrics.list = links - for link in links: - lyr = self.get_lyrics_from_list(link) - if lyr: - lyrics.lyrics = lyr - return True - return False - - def get_lyrics_from_list(self, link): - title,url,artist,song = link - try: - utilities.log(debug, '%s: search url: %s' % (__title__, url)) - response = requests.get(url, timeout=10) - result = response.json() - except: - return None - if 'syncedLyrics' in result: - lyrics = result['syncedLyrics'] - return lyrics - - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'lrclib.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) - +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.lrclib.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': '*LrcLib', + 'description': 'Search https://lrclib.net for synchronized lyrics', + 'author': 'ronie', + 'priority': '110', + 'syncronized': True, + 'artist': 'CHVRCHES', + 'title': 'Clearest Blue', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/lyricscom.py b/mythtv/programs/scripts/metadata/Music/lyrics/lyricscom.py index 2ed375968cb..a2a583b7101 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/lyricscom.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/lyricscom.py @@ -1,188 +1,18 @@ -# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- -""" -Scraper for http://www.lyrics.com/ - -ronie -""" - -import re -import requests -import urllib.parse -import difflib -from bs4 import BeautifulSoup - -import sys -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and ronie" -__title__ = "Lyrics.Com" -__description__ = "Search http://www.lyrics.com for lyrics" -__priority__ = "240" -__version__ = "0.1" -__syncronized__ = False - -debug = False - -class LyricsFetcher: - def __init__( self ): - self.url = 'http://www.lyrics.com/serp.php?st=%s&qtype=2' - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - sess = requests.Session() - - try: - request = sess.get(self.url % urllib.parse.quote_plus(lyrics.artist), timeout=10) - response = request.text - except: - return False - soup = BeautifulSoup(response, 'html.parser') - url = '' - for link in soup.find_all('a'): - if link.string and link.get('href').startswith('artist/'): - url = 'https://www.lyrics.com/' + link.get('href') - break - if url: - try: - req = sess.get(url, timeout=10) - resp = req.text - except: - return False - soup = BeautifulSoup(resp, 'html.parser') - url = '' - for link in soup.find_all('a'): - if link.string and (difflib.SequenceMatcher(None, link.string.lower(), lyrics.title.lower()).ratio() > 0.8): - url = 'https://www.lyrics.com' + link.get('href') - break - if url: - try: - req2 = sess.get(url, timeout=10) - resp2 = req2.text - except: - return False - matchcode = re.search('(.*?)', resp2, flags=re.DOTALL) - if matchcode: - lyricscode = (matchcode.group(1)) - lyr = re.sub('<[^<]+?>', '', lyricscode) - lyrics.lyrics = lyr.replace('\\n','\n') - return True - - return False - -def performSelfTest(): - try: - from bs4 import BeautifulSoup - except: - utilities.log(True, "Failed to import BeautifulSoup. This grabber requires python-bs4") - sys.exit(1) - - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'lyricscom.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.lyricscom.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': 'Lyrics.Com', + 'description': 'Search https://lyrics.com for lyrics', + 'author': 'Paul Harrison and ronie', + 'priority': '240', + 'syncronized': False, + 'artist': 'Blur', + 'title': "You're so Great", +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/lyricsify.py b/mythtv/programs/scripts/metadata/Music/lyrics/lyricsify.py index 89314e0e9c1..6b96885e910 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/lyricsify.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/lyricsify.py @@ -1,192 +1,18 @@ -# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- -""" -Scraper for https://www.lyricsify.com/ - -ronie -""" - -import requests -import re -import difflib -from bs4 import BeautifulSoup - -import sys -from optparse import OptionParser -from common import utilities - - -__author__ = "Paul Harrison and ronie" -__title__ = "Lyricsify" -__description__ = "Search https://www.lyricsify.com for lyrics" -__priority__ = "130" -__version__ = "0.1" -__syncronized__ = True - -debug = False - -UserAgent = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"} - -class LyricsFetcher: - def __init__( self ): - self.SEARCH_URL = 'https://www.lyricsify.com/lyrics/%s/%s' - self.LYRIC_URL = 'https://www.lyricsify.com%s' - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - artist = lyrics.artist.replace(' ', '-') - title = lyrics.title.replace(' ', '-') - try: - url = self.SEARCH_URL % (artist, title) - search = requests.get(url, headers=UserAgent, timeout=10) - response = search.text - except: - return False - links = [] - soup = BeautifulSoup(response, 'html.parser') - for link in soup.find_all('a'): - if link.string and link.get('href').startswith('/lrc/'): - foundartist = link.string.split(' - ', 1)[0] - # some links don't have a proper 'artist - title' format - try: - foundsong = link.string.split(' - ', 1)[1].rstrip('.lrc') - except: - continue - if (difflib.SequenceMatcher(None, artist.lower(), foundartist.lower()).ratio() > 0.8) and (difflib.SequenceMatcher(None, title.lower(), foundsong.lower()).ratio() > 0.8): - links.append((foundartist + ' - ' + foundsong, self.LYRIC_URL % link.get('href'), foundartist, foundsong)) - if len(links) == 0: - return False - elif len(links) > 1: - lyrics.list = links - for link in links: - lyr = self.get_lyrics_from_list(link) - if lyr: - lyrics.lyrics = lyr - return True - return False - - def get_lyrics_from_list(self, link): - title,url,artist,song = link - try: - utilities.log(debug, '%s: search url: %s' % (__title__, url)) - search = requests.get(url, headers=UserAgent, timeout=10) - response = search.text - except: - return None - matchcode = re.search('/h3>(.*?)', '', lyricscode) - return cleanlyrics - - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'lyricsify.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) - +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.lyricsify.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': '*Lyricsify', + 'description': 'Search https://lyricsify.com for synchronized lyrics', + 'author': 'ronie', + 'priority': '130', + 'syncronized': True, + 'artist': 'Madonna', + 'title': 'Crazy For You', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/lyricsmode.py b/mythtv/programs/scripts/metadata/Music/lyrics/lyricsmode.py index d0719dfa88b..472f50e8994 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/lyricsmode.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/lyricsmode.py @@ -1,174 +1,18 @@ -#-*- coding: UTF-8 -*- -import requests -import urllib.parse -import re - -import sys -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and ronie" -__title__ = "LyricsMode" -__description__ = "Search http://www.lyricsmode.com for lyrics" -__priority__ = "240" -__version__ = "0.1" -__syncronized__ = False - -debug = False - -class LyricsFetcher: - def __init__( self ): - return - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - artist = utilities.deAccent(lyrics.artist) - title = utilities.deAccent(lyrics.title) - url = 'http://www.lyricsmode.com/lyrics/%s/%s/%s.html' % (artist.lower()[:1], artist.lower().replace('&','and').replace(' ','_'), title.lower().replace('&','and').replace(' ','_')) - result = self.direct_url(url) - if not result: - result = self.search_url(artist, title) - if result: - lyr = result.split('style="position: relative;">')[1].split('', '') - return True - - def direct_url(self, url): - try: - utilities.log(debug, '%s: direct url: %s' % (__title__, url)) - song_search = requests.get(url, timeout=10) - response = song_search.text - if response.find('lyrics_text') >= 0: - return response - except: - utilities.log(debug, 'error in direct url') - - def search_url(self, artist, title): - try: - url = 'http://www.lyricsmode.com/search.php?search=' + urllib.parse.quote_plus(artist.lower() + ' ' + title.lower()) - utilities.log(debug, '%s: search url: %s' % (__title__, url)) - song_search = requests.get(url, timeout=10) - response = song_search.text - matchcode = re.search('lm-list__cell-title">.*?') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'lyricsmode.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) - +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.lyricsmode.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': 'LyricsMode', + 'description': 'Search http://www.lyricsmode.com for lyrics', + 'author': 'Paul Harrison and ronie', + 'priority': '220', + 'syncronized': False, + 'artist': 'Maren Morris', + 'title': 'My Church', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/megalobiz.py b/mythtv/programs/scripts/metadata/Music/lyrics/megalobiz.py index 46215ab5f85..be6d3558339 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/megalobiz.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/megalobiz.py @@ -1,184 +1,21 @@ -#-*- coding: UTF-8 -*- -""" -Scraper for https://www.megalobiz.com/ +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- -megalobiz -""" +from common import culrcwrap +from lib.culrcscrapers.megalobiz.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename -import requests -import re -from bs4 import BeautifulSoup +info = { + 'name': '*Megalobiz', + 'description': 'Search https://megalobiz.com for synchronized lyrics', + 'author': 'ronie', + 'priority': '140', + 'syncronized': True, + 'artist': 'Michael Jackson', + 'title': 'Beat It', +} -import sys -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and 'ronie'" -__title__ = "Megalobiz" -__description__ = "Search https://www.megalobiz.com/ for lyrics" -__version__ = "0.1" -__priority__ = "400" -__syncronized__ = True - -debug = False - -class LyricsFetcher: - def __init__( self ): - self.SEARCH_URL = 'https://www.megalobiz.com/search/all?qry=%s-%s&searchButton.x=0&searchButton.y=0' - self.LYRIC_URL = 'https://www.megalobiz.com/%s' - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - try: - url = self.SEARCH_URL % (lyrics.artist, lyrics.title) - response = requests.get(url, timeout=10) - result = response.text - except: - return None - links = [] - soup = BeautifulSoup(result, 'html.parser') - for link in soup.find_all('a'): - if link.get('href') and link.get('href').startswith('/lrc/maker/'): - linktext = link.text.replace('_', ' ').strip() - if lyrics.artist.lower() in linktext.lower() and lyrics.title.lower() in linktext.lower(): - links.append((linktext, self.LYRIC_URL % link.get('href'), lyrics.artist, lyrics.title)) - if len(links) == 0: - return None - elif len(links) > 1: - lyrics.list = links - for link in links: - lyr = self.get_lyrics_from_list(link) - if lyr: - lyrics.lyrics = lyr - return True - return False - - def get_lyrics_from_list(self, link): - title,url,artist,song = link - try: - utilities.log(debug, '%s: search url: %s' % (__title__, url)) - response = requests.get(url, timeout=10) - result = response.text - except: - return None - matchcode = re.search('span id="lrc_[0-9]+_lyrics">(.*?)', '', lyricscode) - return cleanlyrics - - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'megalobiz.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Perform self-test for dependencies.") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - if (len(args) > 0): - utilities.log('ERROR: invalid arguments found') - sys.exit(1) - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# it takes 8 seconds, have to move to the end -twitham, 2024/01 +info['priority'] = '400' if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/music163.py b/mythtv/programs/scripts/metadata/Music/lyrics/music163.py index 721474edcde..cbf56e9736b 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/music163.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/music163.py @@ -1,184 +1,24 @@ -# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- -""" -Scraper for http://music.163.com/ - -osdlyrics -""" - -import requests -import re -import random -import difflib - -import sys -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and ronie" -__title__ = "Music163" -__description__ = "Lyrics scraper for http://music.163.com/" -__priority__ = "500" -__version__ = "0.1" -__syncronized__ = True - -debug = False - -headers = {} -headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0' - -class LyricsFetcher: - def __init__( self ): - self.SEARCH_URL = 'http://music.163.com/api/search/get' - self.LYRIC_URL = 'http://music.163.com/api/song/lyric' - - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - artist = lyrics.artist.replace(' ', '+') - title = lyrics.title.replace(' ', '+') - search = '?s=%s+%s&type=1' % (artist, title) - try: - url = self.SEARCH_URL + search - response = requests.get(url, headers=headers, timeout=10) - result = response.json() - except: - return False - links = [] - if 'result' in result and 'songs' in result['result']: - for item in result['result']['songs']: - artists = "+&+".join([a["name"] for a in item["artists"]]) - if (difflib.SequenceMatcher(None, artist.lower(), artists.lower()).ratio() > 0.6) and (difflib.SequenceMatcher(None, title.lower(), item['name'].lower()).ratio() > 0.8): - links.append((artists + ' - ' + item['name'], self.LYRIC_URL + '?id=' + str(item['id']) + '&lv=-1&kv=-1&tv=-1', artists, item['name'])) - if len(links) == 0: - return False - elif len(links) > 1: - lyrics.list = links - for link in links: - lyr = self.get_lyrics_from_list(link) - if lyr and lyr.startswith('['): - lyrics.lyrics = lyr - return True - return None - - def get_lyrics_from_list(self, link): - title,url,artist,song = link - try: - utilities.log(debug, '%s: search url: %s' % (__title__, url)) - response = requests.get(url, headers=headers, timeout=10) - result = response.json() - except: - return None - if 'lrc' in result: - return result['lrc']['lyric'] - - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'music163.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.music163.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': '*Music163', + 'description': 'Search http://music.163.com for synchronized lyrics', + 'author': 'ronie', + 'priority': '120', + 'syncronized': True, + 'artist': 'Madonna', + 'title': 'Vogue', +} + +# -a Rainmakers -n Doomsville, for example, reports author only which +# is incomplete and stops the search so I need to move it last. +# Simply comment this if you prefer the above 120 from CU LRC +# -twitham, 2024/01 +info['priority'] = '500' if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/musixmatch.py b/mythtv/programs/scripts/metadata/Music/lyrics/musixmatch.py index 896163b5c07..2713683fac9 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/musixmatch.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/musixmatch.py @@ -1,201 +1,18 @@ -# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- -""" -Scraper for https://www.musixmatch.com - -taxigps -""" - -import os -import requests -import re -import random -import difflib -from bs4 import BeautifulSoup - -import sys -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and 'ronie'" -__title__ = "Musixmatch" -__description__ = "Search https://www.musixmatch.com for lyrics" -__priority__ = "210" -__version__ = "0.1" -__syncronized__ = False - -debug = False - -headers = {} -headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0' - - -class LyricsFetcher: - def __init__( self ): - self.SEARCH_URL = 'https://www.musixmatch.com/search/' - self.LYRIC_URL = 'https://www.musixmatch.com' - - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - artist = lyrics.artist.replace(' ', '+') - title = lyrics.title.replace(' ', '+') - search = '%s+%s' % (artist, title) - try: - url = self.SEARCH_URL + search - response = requests.get(url, headers=headers, timeout=10) - result = response.text - except: - return False - links = [] - soup = BeautifulSoup(result, 'html.parser') - for item in soup.find_all('li', {'class': 'showArtist'}): - artistname = item.find('a', {'class': 'artist'}).get_text() - songtitle = item.find('a', {'class': 'title'}).get_text() - url = item.find('a', {'class': 'title'}).get('href') - if (difflib.SequenceMatcher(None, artist.lower(), artistname.lower()).ratio() > 0.8) and (difflib.SequenceMatcher(None, title.lower(), songtitle.lower()).ratio() > 0.8): - links.append((artistname + ' - ' + songtitle, self.LYRIC_URL + url, artistname, songtitle)) - if len(links) == 0: - return False - elif len(links) > 1: - lyrics.list = links - for link in links: - lyr = self.get_lyrics_from_list(link) - if lyr: - lyrics.lyrics = lyr - return True - return False - - def get_lyrics_from_list(self, link): - title,url,artist,song = link - try: - utilities.log(debug, '%s: search url: %s' % (__title__, url)) - response = requests.get(url, headers=headers, timeout=10) - result = response.text - except: - return None - soup = BeautifulSoup(result, 'html.parser') - lyr = soup.find_all('span', {'class': 'lyrics__content__ok'}) - if lyr: - lyrics = '' - for part in lyr: - lyrics = lyrics + part.get_text() + '\n' - return lyrics - else: - lyr = soup.find_all('span', {'class': 'lyrics__content__error'}) - if lyr: - lyrics = '' - for part in lyr: - lyrics = lyrics + part.get_text() + '\n' - return lyrics - - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'musixmatch.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.musixmatch.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': 'Musixmatch', + 'description': 'Search https://musixmatch.com for lyrics', + 'author': 'ronie', + 'priority': '210', + 'syncronized': False, + 'artist': 'Kate Bush', + 'title': 'Wuthering Heights', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/musixmatchlrc.py b/mythtv/programs/scripts/metadata/Music/lyrics/musixmatchlrc.py index 14d8abbc6f6..cd9109d7298 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/musixmatchlrc.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/musixmatchlrc.py @@ -1,228 +1,22 @@ -# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- -""" -Scraper for https://www.musixmatch.com/ - -ronie -https://github.com/rtcq/syncedlyrics -""" - -import requests -import json -import time -import difflib - -import os -import sys -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and ronie" -__title__ = "MusixMatchLRC" -__description__ = "Search http://musixmatch.com for lyrics" -__priority__ = "100" -__version__ = "0.1" -__syncronized__ = True - -debug = False - -class LyricsFetcher: - def __init__( self ): - self.SEARCH_URL = 'https://apic-desktop.musixmatch.com/ws/1.1/%s' - self.session = requests.Session() - self.session.headers.update( - { - "authority": "apic-desktop.musixmatch.com", - "cookie": "AWSELBCORS=0; AWSELB=0", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0", - } - ) - self.current_time = int(time.time()) - - def get_token(self): - self.token = '' - tokenpath = os.path.join(utilities.getCacheDir(), 'musixmatch_token') - if os.path.exists(tokenpath): - tokenfile = open(tokenpath, 'r') - tokendata = json.load(tokenfile) - tokenfile.close() - cached_token = tokendata.get("token") - expiration_time = tokendata.get("expiration_time") - if cached_token and expiration_time and self.current_time < expiration_time: - self.token = cached_token - if not self.token: - try: - url = self.SEARCH_URL % 'token.get' - query = [('user_language', 'en'), ('app_id', 'web-desktop-app-v1.0'), ('t', self.current_time)] - response = self.session.get(url, params=query, timeout=10) - result = response.json() - except: - return None - if 'message' in result and 'body' in result["message"] and 'user_token' in result["message"]["body"]: - self.token = result["message"]["body"]["user_token"] - expiration_time = self.current_time + 600 - tokendata = {} - tokendata['token'] = self.token - tokendata['expiration_time'] = expiration_time - tokenfile = open(tokenpath, 'w') - json.dump(tokendata, tokenfile) - tokenfile.close() - return self.token - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - self.token = self.get_token() - if not self.token: - return False - artist = lyrics.artist.replace(' ', '+') - title = lyrics.title.replace(' ', '+') - search = '%s - %s' % (artist, title) - try: - url = self.SEARCH_URL % 'track.search' - query = [('q', search), ('page_size', '5'), ('page', '1'), ('s_track_rating', 'desc'), ('quorum_factor', '1.0'), ('app_id', 'web-desktop-app-v1.0'), ('usertoken', self.token), ('t', self.current_time)] - response = requests.get(url, params=query, timeout=10) - result = response.json() - except: - return False - links = [] - if 'message' in result and 'body' in result["message"] and 'track_list' in result["message"]["body"] and result["message"]["body"]["track_list"]: - for item in result["message"]["body"]["track_list"]: - artistname = item['track']['artist_name'] - songtitle = item['track']['track_name'] - trackid = item['track']['track_id'] - if (difflib.SequenceMatcher(None, artist.lower(), artistname.lower()).ratio() > 0.8) and (difflib.SequenceMatcher(None, title.lower(), songtitle.lower()).ratio() > 0.8): - links.append((artistname + ' - ' + songtitle, trackid, artistname, songtitle)) - if len(links) == 0: - return False - elif len(links) > 1: - lyrics.list = links - for link in links: - lyr = self.get_lyrics_from_list(link) - if lyr: - lyrics.lyrics = lyr - return True - return False - - def get_lyrics_from_list(self, link): - title,trackid,artist,song = link - try: - utilities.log(debug, '%s: search track id: %s' % (__title__, trackid)) - url = self.SEARCH_URL % 'track.subtitle.get' - query = [('track_id', trackid), ('subtitle_format', 'lrc'), ('app_id', 'web-desktop-app-v1.0'), ('usertoken', self.token), ('t', self.current_time)] - response = requests.get(url, params=query, timeout=10) - result = response.json() - except: - return None - if 'message' in result and 'body' in result["message"] and 'subtitle' in result["message"]["body"] and 'subtitle_body' in result["message"]["body"]["subtitle"]: - lyrics = result["message"]["body"]["subtitle"]["subtitle_body"] - return lyrics - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Dire Straits' - lyrics.album = 'Brothers In Arms' - lyrics.title = 'Money For Nothing' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'musixmatchlrc.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap + +# In this grabber we need to point its PROFILE to mythtv's cache +# directory, like this: +import lib.culrcscrapers.musixmatchlrc.lyricsScraper +lib.culrcscrapers.musixmatchlrc.lyricsScraper.PROFILE = culrcwrap.getCacheDir() +# make sure this--^^^^^^^^^^^^^ matches this file's basename + +info = { + 'name': '*Musixmatchlrc', + 'description': 'Search https://musixmatch.com for synchronized lyrics', + 'author': 'ronie', + 'priority': '100', + 'syncronized': True, + 'artist': 'Kate Bush', + 'title': 'Wuthering Heights', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, lib.culrcscrapers.musixmatchlrc.lyricsScraper.LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/supermusic.py b/mythtv/programs/scripts/metadata/Music/lyrics/supermusic.py index 8391969f2e8..549adabaf75 100644 --- a/mythtv/programs/scripts/metadata/Music/lyrics/supermusic.py +++ b/mythtv/programs/scripts/metadata/Music/lyrics/supermusic.py @@ -1,181 +1,18 @@ -# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*- -""" -Scraper for https://supermusic.cz - -Jose Riha -""" - -import re -import requests -import html - -import os -import sys -from optparse import OptionParser -from common import utilities - -__author__ = "Paul Harrison and Jose Riha" -__title__ = "SuperMusic" -__description__ = "Search https://supermusic.cz for lyrics" -__priority__ = "250" -__version__ = "0.1" -__syncronized__ = False - -debug = False - -class LyricsFetcher: - def __init__( self ): - return - - def get_lyrics(self, lyrics): - utilities.log(debug, "%s: searching lyrics for %s - %s - %s" % (__title__, lyrics.artist, lyrics.album, lyrics.title)) - - artist = lyrics.artist.lower() - title = lyrics.title.lower() - - try: - req = requests.post('https://supermusic.cz/najdi.php', data={'hladane': title, 'typhladania': 'piesen', 'fraza': 'off'}) - response = req.text - except: - return False - req.close() - url = None - try: - items = re.search(r'Počet nájdených piesní.+

(.*)
', response, re.S).group(1) - for match in re.finditer(r'
"[^"]+?") target="_parent">(?P.*?) - (?P.+?) \((.*?)', response, re.S).group(1) - lyr = re.sub(r'.*?', '', lyr) - lyr = re.sub(r'\s*', '\n', lyr) - lyr = re.sub(r'', '', lyr, flags=re.DOTALL) - lyr = re.sub(r'<[^>]*?>', '', lyr, flags=re.DOTALL) - lyr = lyr.strip('\r\n') - lyr = html.unescape(lyr) - lyrics.lyrics = lyr - return True - except: - return False - -def performSelfTest(): - found = False - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - lyrics.artist = 'Karel Gott' - lyrics.album = '' - lyrics.title = 'Trezor' - - fetcher = LyricsFetcher() - found = fetcher.get_lyrics(lyrics) - - if found: - utilities.log(True, "Everything appears in order.") - buildLyrics(lyrics) - sys.exit(0) - - utilities.log(True, "The lyrics for the test search failed!") - sys.exit(1) - -def buildLyrics(lyrics): - from lxml import etree - xml = etree.XML(u'') - etree.SubElement(xml, "artist").text = lyrics.artist - etree.SubElement(xml, "album").text = lyrics.album - etree.SubElement(xml, "title").text = lyrics.title - etree.SubElement(xml, "syncronized").text = 'True' if __syncronized__ else 'False' - etree.SubElement(xml, "grabber").text = lyrics.source - - lines = lyrics.lyrics.splitlines() - for line in lines: - etree.SubElement(xml, "lyric").text = line - - utilities.log(True, utilities.convert_etree(etree.tostring(xml, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def buildVersion(): - from lxml import etree - version = etree.XML(u'') - etree.SubElement(version, "name").text = __title__ - etree.SubElement(version, "author").text = __author__ - etree.SubElement(version, "command").text = 'supermusic.py' - etree.SubElement(version, "type").text = 'lyrics' - etree.SubElement(version, "description").text = __description__ - etree.SubElement(version, "version").text = __version__ - etree.SubElement(version, "priority").text = __priority__ - etree.SubElement(version, "syncronized").text = 'True' if __syncronized__ else 'False' - - utilities.log(True, utilities.convert_etree(etree.tostring(version, encoding='UTF-8', - pretty_print=True, xml_declaration=True))) - sys.exit(0) - -def main(): - global debug - - parser = OptionParser() - - parser.add_option('-v', "--version", action="store_true", default=False, - dest="version", help="Display version and author") - parser.add_option('-t', "--test", action="store_true", default=False, - dest="test", help="Test grabber with a know good search") - parser.add_option('-s', "--search", action="store_true", default=False, - dest="search", help="Search for lyrics.") - parser.add_option('-a', "--artist", metavar="ARTIST", default=None, - dest="artist", help="Artist of track.") - parser.add_option('-b', "--album", metavar="ALBUM", default=None, - dest="album", help="Album of track.") - parser.add_option('-n', "--title", metavar="TITLE", default=None, - dest="title", help="Title of track.") - parser.add_option('-f', "--filename", metavar="FILENAME", default=None, - dest="filename", help="Filename of track.") - parser.add_option('-d', '--debug', action="store_true", default=False, - dest="debug", help=("Show debug messages")) - - opts, args = parser.parse_args() - - lyrics = utilities.Lyrics() - lyrics.source = __title__ - lyrics.syncronized = __syncronized__ - - if opts.debug: - debug = True - - if opts.version: - buildVersion() - - if opts.test: - performSelfTest() - - if opts.artist: - lyrics.artist = opts.artist - if opts.album: - lyrics.album = opts.album - if opts.title: - lyrics.title = opts.title - if opts.filename: - lyrics.filename = opts.filename - - fetcher = LyricsFetcher() - if fetcher.get_lyrics(lyrics): - buildLyrics(lyrics) - sys.exit(0) - else: - utilities.log(True, "No lyrics found for this track") - sys.exit(1) +# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil; -*- + +from common import culrcwrap +from lib.culrcscrapers.supermusic.lyricsScraper import LyricsFetcher +# make sure this-------^^^^^^^^^ matches this file's basename + +info = { + 'name': 'Supermusic', + 'description': 'Search https://supermusic.cz for lyrics', + 'author': 'Jose Riha', + 'priority': '250', + 'syncronized': False, + 'artist': 'Karel Gott', + 'title': 'Trezor', +} if __name__ == '__main__': - main() + culrcwrap.main(__file__, info, LyricsFetcher) diff --git a/mythtv/programs/scripts/metadata/Music/lyrics/testlyrics.pl b/mythtv/programs/scripts/metadata/Music/lyrics/testlyrics.pl new file mode 100755 index 00000000000..9b3c27f948f --- /dev/null +++ b/mythtv/programs/scripts/metadata/Music/lyrics/testlyrics.pl @@ -0,0 +1,78 @@ +#!/usr/bin/perl + +# test all the grabbers, by twitham@sbcglobal.net, 2024/01 + +# Similar to CU LRC's lib/scrapertest.py but for MythTV's *.py +# wrappers. This tester adds several additional benefits: + +# -t test grabbers in priority order +# lookup any -a artist and -n title +# stop at first match (default) or -k keep going +# high resolution timing summary +# report lyric lines found in the summary + +use warnings; +use strict; +use Getopt::Long; +use File::Basename; +use Time::HiRes qw(gettimeofday tv_interval); + +my $begin = [gettimeofday]; +my($name, $path, $suffix) = fileparse($0); + +my @opt = qw(k|keepgoing v|version t|test s|search d|debug + a|artist=s b|album=s n|title=s f|filename=s); +my %opt; +my $usage = "usage: $0 [-d] [-k] [-v | -t | -a artist -n title] +-d | --debug extra debugging lines to stderr +-k | --keepgoing don't stop at the first match +-v | --version show scraper version headers +-t | --test lookup known good artist/title +-a | --artist lookup given artist +-n | --title lookup given name/title +"; +GetOptions(\%opt, @opt) or die $usage; +$opt{v} or $opt{t} or $opt{a} and $opt{n} or die $usage; + +chdir $path or die $!; +opendir DIR, '.' or die $!; +my(%pri, %sync); + +# code reading hack works with current info format: +for my $py (grep /\.py$/, readdir DIR) { + if (open FILE, $py) { + while () { + m/'priority':\s*'(\d+)'/ + and $pri{$py} = $1; + m/info.'priority'.\s*=\s*'(\d+)'/ + and $pri{$py} = $1; + m/'syncronized':\s*(\S+)/ + and $sync{$py} = $1; + } + } +} +closedir DIR; + +my(%found, %time, %lines); +@opt = map { $opt{$_} ? ("-$_", $opt{$_}) : () } qw/a b n f v d t/; +for my $py (sort { $pri{$a} <=> $pri{$b} } keys %pri) { + my $begin = [gettimeofday]; + my @cmd = ('python3', $py, @opt); + warn "$pri{$py}\t$sync{$py}\t@cmd\n"; + my $out = ''; + open PIPE, '-|', @cmd; + while () { + $out .= $_; + } + close PIPE; + $lines{$py} = $out =~ s///g; + $lines{$py} ||= 0; + $time{$py} = tv_interval($begin, [gettimeofday]); + $out and print $out; + $out and !$opt{k} and last; +} +warn join("\t", qw(pri sync lyrics seconds command)), "\n"; +for my $py (sort { $pri{$a} <=> $pri{$b} } keys %lines) { + warn "$pri{$py}\t$sync{$py}\t$lines{$py}\t$time{$py}\t$py\n"; +} +warn "TOTAL seconds elapsed\t", tv_interval($begin, [gettimeofday]), "\n";