diff --git a/README.md b/README.md index fd5e2c3..35bf4da 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The folowing programs are required to run NRSC5-DUI * [Python Dateutil](https://pypi.org/project/python-dateutil) * [urllib3](https://pypi.org/project/urllib3) * [pyOpenSSL](https://pypi.org/project/pyOpenSSL) +* [musicbrainzngs](https://pypi.org/project/musicbrainzngs) * [nrsc5](https://github.com/theori-io/nrsc5) * [sox](https://github.com/chirlu/sox) @@ -39,17 +40,17 @@ to include the path to nrsc5 when using scripts (like Apple Script) that seeming You may first change some optional parameters of how nrsc5 works from the Settings tab in nrsc5-dui: Set the gain to Auto, or optionally enter an RF gain in dB that has known to work well for some stations. Enter a PPM correction value if your RTL-SDR dongle has an offset. -Enter the IP address that rtl_tcp is listening to and check the Enabled box if you are using a remote RTL-SDR. +Enter the IP address that rtl_tcp is listening to and check the Enabled box if you are using a remote RTL-SDR. Enter the number of the desired device if you have more than one RTL-SDR dongle. -Check `Log to file` to enable writing debug information from nrsc5 to nrsc5.log. -`Temporarily Disabled:` Check `DL Album Art` to enable automated downloading of album art. +Check `Log to file` to enable writing debug information from nrsc5 to nrsc5.log. +Check `DL Album Art` to enable automated downloading of album art from MusicBrainz. ## Playing Enter the frequency in MHz of the station you want to play and either click the triangular Play button on the toolbar, or just hit return. When the receiver attains synchronization, the pilot in the lower left corner of the status bar will turn green. It will return to gray if synchronization is lost. If the device itself becomes 'lost', the pilot will turn red to indicate an error has occurred (this is the theory, though I've yet to see this status message happen in practice). The synchronization process may take about 10 seconds, and the station will begin to play. This depends upon signal strength and whether it's relatively free from adjacent interference. After a short while, the station name will appear to the right of the frequency, and the available streams will show on a row of buttons just beneath the frequency entry. Clicking one of these buttons will change to that particular stream. Note: No settings other than stream may be changed while the device is playing. ## Album Art & Track Info Some stations will send album art and station logos. These will fill the Album Art tab, as they are made available by the station. Most stations will send the song title, artist, album, and genre. These are displayed in the Track Info pane, also if available. -`Temporarily Disabled:` The user can override what the stations send by enabling the DL Album Art setting. This will use the Title and Artist information to retrieve album art. If no album art is found, the station logo will be used, if available. +The user can override what the stations send by enabling the DL Album Art setting. This will use the Title and Artist information to retrieve album art from MusicBrainz. If no album art is found, the station logo will be used, if available. ## Bookmarks When a station is playing, you can click the Bookmark Station button to add it to the bookmarks list. You can click on the Name in the bookmarks list to edit it. Double click the Station to tune to that particular station and stream. Click the Delete Bookmark button to delete it. Note that some stations use the default MPS/SPS or HDn naming for their streams. In this case, the bookmark will be used to name the stream button. diff --git a/nrsc5-dui.py b/nrsc5-dui.py index 243055e..f92afa9 100644 --- a/nrsc5-dui.py +++ b/nrsc5-dui.py @@ -33,6 +33,7 @@ from gi.repository import Gtk, GObject, Gdk, GdkPixbuf, GLib import urllib3 from OpenSSL import SSL +import musicbrainzngs # print debug messages to stdout (if debugger is attached) debugMessages = (sys.gettrace() != None) @@ -58,7 +59,6 @@ class NRSC5_DUI(object): self.getControls() # get controls and windows self.initStreamInfo() # initilize stream info and clear status widgets self.http = urllib3.PoolManager() - # self.headers = urllib3.util.make_headers(keep_alive=True, accept_encoding=True, user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:87.0) Gecko/20100101 Firefox/87.0") self.debugLog("Local path determined as " + runtimeDir) @@ -77,6 +77,12 @@ class NRSC5_DUI(object): self.debugLog("OS Determination: Windows = {}".format(self.windowsOS)) + self.app_name = "NRSC5-DUI" + self.version = "2.1.0" + self.web_addr = "https://github.com/markjfine/nrsc5-dui" + self.copyright = "Copyright © 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021 Mark J. Fine" + musicbrainzngs.set_useragent(self.app_name,self.version,self.web_addr) + self.mapFile = os.path.join(resDir, "map.png") self.defaultSize = [490,250] # default width,height of main app self.nrsc5 = None # nrsc5 process @@ -327,30 +333,6 @@ class NRSC5_DUI(object): newArtist = self.streamInfo["Artist"].strip() return ((newArtist != oldArtist) and (newTitle != oldTitle)) - def get_cover_data(self, response): - check = -1 - resultUrl = "" - resultArtist = "" - m = re.search(r"card card_large float_fix",response) - if (m.start() > -1): - response = response[m.start():] - m = re.search(r" -1): - response = response[m.start()+15:] - m = re.search(r"\"",response) - if (m.start() > -1): - resultUrl = response[:m.start()] - response = response[m.start()+1:] - m = re.search(r" -1): - response = response[m.start()+13:] - m = re.search(r"\"",response) - if (m.start() > -1): - resultArtist = response[:m.start()] - response = response[m.start()+1:] - check = 0 - return check, response, resultUrl, resultArtist - def fix_artist(self): newArtist = self.streamInfo["Artist"] if ("/" in newArtist): @@ -368,36 +350,47 @@ class NRSC5_DUI(object): # only care about the first artist listed if separated by slashes newArtist = self.fix_artist() - baseStr = str(newArtist +" - "+self.streamInfo["Title"]).replace("/","_").replace(":","_") - saveStr = os.path.join(aasDir, baseStr.replace(" ","_")+".jpg") - searchStr = baseStr.replace(" ","+") + baseStr = str(newArtist+" - "+self.streamInfo["Title"]) + saveStr = os.path.join(aasDir, baseStr.replace(" ","_").replace("/","_").replace(":","_")+".jpg") + #searchStr = baseStr.replace(" ","+") + #print("Searching for:"+searchStr) # does it already exist? if (os.path.isfile(saveStr)): self.coverImage = saveStr - # if not, get it from Discogs + # if not, get it from MusicBrainz else: try: - searchStr = "https://www.discogs.com/search/?q="+searchStr+"&type=all" - #r = self.http.request('GET', searchStr, self.headers) - r = self.http.request('GET', searchStr) - if (r.status == 200): - response = r.data.decode('utf-8') + result = musicbrainzngs.search_releases(artist=self.streamInfo["Artist"], release=self.streamInfo["Title"]) - # loop through the page until you either get an artist match or you run out of page (check) - while (not got_cover): - resultUrl = "" - resultArtist = "" - check, response, resultUrl, resultArtist = self.get_cover_data(response) - got_cover = (newArtist.lower() in resultArtist.lower()) and (check == 0) - - # if you got a match, save it - if (resultUrl != ""): - with self.http.request('GET', resultUrl, preload_content=False) as r, open(saveStr, 'wb') as out_file: - if (r.status == 200): - shutil.copyfileobj(r, out_file) - self.coverImage = saveStr + if result['release-list']: + + # loop through the list until you get a match + #print() + resultID = "" + for (idx, release) in enumerate(result['release-list']): + #print(release["artist-credit-phrase"]+" - "+release['title']+" "+release['id']) + if (newArtist.lower() in release["artist-credit-phrase"].lower()) and (self.streamInfo["Title"].lower() in release['title'].lower()): + resultID = release['id'] + + # got a match, now get the cover art + #print("Found {} - {}, {}".format(release["artist-credit-phrase"], release['title'], resultID)) + imageList = musicbrainzngs.get_image_list(resultID) + for image in imageList["images"]: + if "Front" in image["types"] and image["approved"]: + resultURL = image["thumbnails"]["large"] + #print("{} is an approved front image".format(resultURL)) + + # now save it + # TODO - Use built-in MusicBrainz routine to eliminate need for urllib3 and OpenSSL for just this remaining call + #with musicbrainzngs.get_image_front(resultID, size="500") as r, open(saveStr, 'wb') as out_file: + with self.http.request('GET', resultURL, preload_content=False) as r, open(saveStr, 'wb') as out_file: + if (r.status == 200): + shutil.copyfileobj(r, out_file) + self.coverImage = saveStr + break + break # If no match use the station logo if there is one else: @@ -673,11 +666,11 @@ class NRSC5_DUI(object): about_dialog = Gtk.AboutDialog() about_dialog.set_transient_for(self.mainWindow) about_dialog.set_destroy_with_parent(True) - about_dialog.set_name("NRSC5 DUI") - about_dialog.set_program_name("NRSC5 DUI") - about_dialog.set_version("2.1.0") - about_dialog.set_copyright("Copyright © 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021 Mark J. Fine") - about_dialog.set_website("https://github.com/markjfine/nrsc5-dui") + about_dialog.set_name(self.app_name) + about_dialog.set_program_name(self.app_name) + about_dialog.set_version(self.version) + about_dialog.set_copyright(self.copyright) + about_dialog.set_website(self.web_addr) about_dialog.set_comments("A second-generation graphical interface for nrsc5.") about_dialog.set_authors(authors) about_dialog.set_license(license) @@ -980,8 +973,8 @@ class NRSC5_DUI(object): self.debugLog("Image Changed") # Disable downloaded cover images until fixed with MusicBrainz - #if (self.cbCovers.get_active() and self.id3Changed): - #self.get_cover_image_online() + if (self.cbCovers.get_active() and self.id3Changed): + self.get_cover_image_online() finally: #Gdk.threads_leave()