Compare commits

...

30 Commits

Author SHA1 Message Date
markjfine eaeed80311
Added debug to MusicBrainz search error 2023-07-22 09:39:09 -04:00
markjfine dab697f082
Updated several package versions 2023-07-20 13:56:15 -04:00
Mark J. Fine 49cf532fdc renamed code 511 per spec, added official mime types, modified LOT regex to acount for expiry, added regex for Navteq/HERE stream/packet info, added place holder for Navteq/HERE images. 2023-07-10 20:26:19 -04:00
Mark J. Fine 76b7348943 fixed typo in python-dateutil 2023-02-24 17:02:32 -05:00
Mark J. Fine a27f2969d4 Added requirements.txt instructions 2023-01-12 07:57:20 -05:00
Mark J. Fine 47d2c85abd Added pygobject 2023-01-11 13:19:31 -05:00
markjfine aa7eca98ee
Merge pull request #33 from andylhxu/patch-1
Add requirements.txt
2023-01-11 11:21:14 -05:00
Andy Lianghua Xu a3a6ac2a41
Update requirements.txt 2023-01-11 01:27:49 -05:00
Andy Lianghua Xu 5f2e736d4b
Add requirements.txt
To make it easier to pip install the required packages, rather than relying on exceptions to figure out the missing ones.
2023-01-11 01:25:41 -05:00
Mark J. Fine fd5ba2796b Fill some of the dead space in wide mode with station messages/alerts 2022-12-22 16:08:42 -05:00
Mark J. Fine 31b4d262ed Fixed typo in set_sensitive() 2022-12-22 14:25:13 -05:00
Mark J. Fine 3f77d35962 Added warning before restarting 2022-12-22 08:33:43 -05:00
Mark J. Fine 69057f967a Added way to change application display from narrow to wide layout 2022-12-21 22:28:29 -05:00
Mark J. Fine 1679eda1c5 Changed version number to 2.2.2. 2022-12-19 05:27:39 -05:00
Mark J. Fine d83c934b95 Corrected the way gain was being set for SDRPlay. 2022-12-18 13:50:20 -05:00
Mark J. Fine ec61b85afc Removed quotes from SDRPlay Antenna switch and added Auto option 2022-12-17 16:35:45 -05:00
Mark J. Fine b945e3611a Increased RTL_TCP address size to allow for port 2022-12-14 21:12:07 -05:00
Mark J. Fine 6069f6c503 Use Resmapling.LANCZOS only on PIL > v8, include RTL -H and -d switches only when using RTL. 2022-12-11 20:27:38 -05:00
Mark J. Fine a3068acddc Updated to operate with up to 8 nrsc5 streams, per the nrsc5 spec. 2022-12-09 15:30:42 -05:00
Mark J. Fine ded176ae46 Added notes for SDRPlay compilation and operation. 2022-12-08 11:34:57 -05:00
Mark J. Fine b65a8828be Made SDR Radio selectable in a combobox making irrelevant entries invisible 2022-12-08 11:18:19 -05:00
Mark J. Fine 8f2e8d4c8a More interface cleanup regarding radio selection 2022-12-08 10:05:12 -05:00
Mark J. Fine 41295117e8 Changed SDRPlay antenna to combobox 2022-12-07 12:47:24 -05:00
Mark J. Fine edf0b93b97 interface cleanup, fixed int bug in gain setting, updated LANCZOS to Resampling.LANCZOS 2022-12-07 11:07:37 -05:00
Mark J. Fine d6917ec5c5 Another change to the ignore file 2022-12-06 19:59:53 -05:00
Mark J. Fine 4c7bbe4358 ignore file 2022-12-06 19:47:41 -05:00
Mark J. Fine 6ae3c93754 Added preliminary support for SDRPlay version of nrsc5. 2022-12-06 17:10:47 -05:00
markjfine b8ae248bcf
Corrected int vs. str in debug log output for fileSize and actualFileSize. 2022-07-03 21:12:26 -04:00
markjfine cd0b378276
Update README.md 2021-10-31 19:48:11 -04:00
markjfine bf78481456
cross-compiled versions
rebuilt nrsc5.exe, libnrsc5.dll, libusb-1.0.dll, and librtlsdr.dll cross-compiled from macos.
2021-05-31 10:49:19 -04:00
11 changed files with 5967 additions and 174 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
**/._*
**/.DS_Store
*.txt
*.log
aas/**
cfg/*.txt
cfg/*.json
map/**

View File

@ -1,4 +1,4 @@
NRSC5-DUI is a graphical interface for [nrsc5](https://github.com/theori-io/nrsc5). It makes it easy to play your favorite FM HD radio stations using an RTL-SDR dongle. It will also display weather radar and traffic maps found on most iHeart radio stations.
NRSC5-DUI is a graphical interface for [nrsc5](https://github.com/theori-io/nrsc5). It makes it easy to play your favorite FM HD radio stations using an RTL-SDR or SDRPlay dongle. It will also display weather radar and traffic maps found on most iHeart radio stations.
This version is really a fork of a fork of the original nrsc5-gui: The first was developed by [cmnybo](https://github.com/cmnybo/nrsc5-gui) and subsequently modified by [zefie](https://github.com/zefie/nrsc5-gui). It merges the features of the former to the architecture of the latter, while adding several additional control and display features.
@ -16,11 +16,13 @@ The following programs are required to run NRSC5-DUI
* [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)
* [nrsc5 for RTL_SDR only](https://github.com/theori-io/nrsc5) or [nrsc5 for SDRPlay](https://github.com/fventuri/nrsc5)
It is also assumed you have a fully operational Gtk3 environment installed from [Homebrew](https://brew.sh/), if running on macOS.
# Setup
1. Install the latest version of Python 3.9, PyGObject, Pillow, and other python dependencies.
2. Compile and install nrsc5.
1. Install the latest version of Python 3.9, PyGObject, Pillow, and other python dependencies. Once Python is installed, you may install the dependencies by giving the command `pip install -r <path_to requirements.txt>`
2. Compile and install nrsc5. If using an SDRPlay, you must compile and install the version provided by [fventuri](https://github.com/fventuri/nrsc5).
3. Install nrsc5-dui files in a directory where you have write permissions.
The configuration and resource directories will be created in a new `cfg` and `res` directory under where nrsc5-dui.py resides. Similarly, an `aas` directory will be created for downloaded files and a `map` directory will be created to store weather & traffic maps.
@ -37,25 +39,34 @@ One of the goals of this project was to provide a stand-alone, cross-platform ap
The bottom line is that some have had success installing and running the application and it's dependencies under specific MinGW environments such as WSL2, but may still require the dongle to operate under RTL_TCP and not directly via NRSC5.EXE. Some legacy Windows executables and libraries have been provided in the `bin` directory for those that wish to experiment further. Feel free to use them at your own risk.
# Usage
Please ensure your RTL-SDR dongle is first connected to an available USB port. Then, from the terminal, start nrsc5-dui by entering:
Please ensure your RTL-SDR dongle or SDRPlay is first connected to an available USB port. Then, from the terminal, start nrsc5-dui by entering:
`python3 nrsc5-dui.py`
or something like:
`python3 nrsc5-dui.py /usr/local/bin/`
to include the path to nrsc5 when using scripts (like Apple Script) that seemingly ignore the environment.
The latter includes the path to nrsc5 when using scripts (like Apple Script) that seemingly ignore the environment.
## Settings
You may first change some optional parameters of how nrsc5 works from the Settings tab in nrsc5-dui:
You may first change some optional parameters of how nrsc5 works from the Settings tab in nrsc5-dui:
Set the radio you are using to either RTL_SDR or SDRPlay.
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.
If using an RTL_SDR:
Enter the number of the desired device if you have more than one RTL-SDR dongle.
Enter the IP address that rtl_tcp is listening to and check the Enabled box if you are using a remote RTL-SDR.
If using an SDRPlay:
Enter the serial number of the SDRPlay.
Enter the antenna port used by the SDRPlay.
Other settings:
Check `Log to file` to enable writing debug information from nrsc5 to nrsc5.log.
Check `Download Album Art` to enable automated downloading of album art from MusicBrainz.
Check `Include Station Art` to display album art that is generated by the station in addition to downloading from MusicBrainz.
Check `Extended Queries` to apply several MusicBrainz queries to find album art. Turning this option on may be slower than non-extended queries.
## 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.
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 the two rows 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.
@ -63,10 +74,10 @@ The user can override what the stations send by enabling the DL Album Art settin
The user can change the logo of the playing station by right-clicking in the Album Art area. This will display a popup prompting you for the URL of an image found on the web. Pasting the URL in the box and clicking 'Ok' will download the image and set the logo of the playing station with it.
## 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.
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 respective bookmark will be used to name the stream button.
## Station Info
The station name, slogan, message, and optional alert message will display if the station as pre-programmed them. The current audio bit rate will be displayed here as well as on the status bar. The stations available streams and data services, with a description of each will display, as the station has pre-programmed them. This is a useful feature for noting which stations have [Total Traffic & Weather Network](https://www.ttwnetwork.com/) traffic and weather images.
The station name, slogan, message, and optional alert message will display if the station as pre-programmed them. The current audio bit rate will be displayed here as well as on the status bar. The station's available streams and data services, with a description of each will display, as the station has pre-programmed them. This is a useful feature for noting which stations have [Total Traffic & Weather Network](https://www.ttwnetwork.com/) traffic and weather images.
### Signal Strength
The Modulation Error Ratio for the lower and upper sidebands are displayed as they are determined. Important: High MER values for both sidebands indicates a strong signal. The current, average, minimum and maximum Bit Error Rates will also be displayed as they are determined. High BER values will cause the audio to glitch or drop out. The current BER is also shown on the status bar and may be used as a tuning tool.
@ -91,3 +102,4 @@ The default map used for the weather radar comes from [OpenStreetMap](https://ww
1.2.0 zefie update to modern nrsc5 build
2.0.0 Updated to use the nrsc5 API
2.1.0 Updated and enhanced operation and use
2.2.0 Updated for use with SDRPlay and operates with up to 8 possible audio channels (per nrsc5 spec)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# NRSC5 DUI - A graphical interface for nrsc5
# Copyright (C) 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021 Mark J. Fine
# Copyright (C) 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021-2022 Mark J. Fine
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -18,13 +18,20 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Updated by zefie for modern nrsc5 ~ 2019
# Updated and enhanced by markjfine ~ 2021
# Updated and enhanced by markjfine ~ 2021/2022
import os, pty, select, sys, shutil, re, json, datetime, numpy, glob, time, platform, io
from subprocess import Popen, PIPE
from threading import Timer, Thread
from dateutil import tz
from PIL import Image, ImageFont, ImageDraw
from PIL import Image, ImageFont, ImageDraw, __version__
print('Using Pillow v'+__version__)
if (int(__version__[0]) < 9):
imgLANCZOS = Image.LANCZOS
else:
imgLANCZOS = Image.Resampling.LANCZOS
import gi
gi.require_version("Gtk", "3.0")
@ -51,7 +58,8 @@ cfgDir = os.path.join(runtimeDir, "cfg") # config file directory
class NRSC5_DUI(object):
def __init__(self):
global runtimeDir, resDir
global runtimeDir, resDir, imgLANCZOS
self.windowsOS = False # save our determination as a var in case we change how we determine.
self.getControls() # get controls and windows
@ -76,9 +84,9 @@ class NRSC5_DUI(object):
self.debugLog("OS Determination: Windows = {}".format(self.windowsOS))
self.app_name = "NRSC5-DUI"
self.version = "2.1.1"
self.version = "2.2.2"
self.web_addr = "https://github.com/markjfine/nrsc5-dui"
self.copyright = "Copyright © 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021 Mark J. Fine"
self.copyright = "Copyright © 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021-2022 Mark J. Fine"
musicbrainzngs.set_useragent(self.app_name,self.version,self.web_addr)
self.width = 0 # window width
@ -106,7 +114,7 @@ class NRSC5_DUI(object):
self.update_btns = True # whether to update the stream buttons
self.set_program_btns() # whether to set the stream buttons
self.bookmarks = [] # station bookmarks
self.booknames = ["","","",""] # station bookmark names
self.booknames = ["","","","","","","",""] # station bookmark names
self.stationLogos = {} # station logos
self.coverMetas = {} # cover metadata
self.bookmarked = False # is current station bookmarked
@ -162,7 +170,7 @@ class NRSC5_DUI(object):
263 : "Service Maintenance",
264 : "HD Radio System Services",
265 : "Audio-Related Objects",
511 : "Test_Str_E"
511 : "Reserved for Special Tests"
}
self.ProgramType = {
@ -200,6 +208,25 @@ class NRSC5_DUI(object):
76 : "Special Reading Services"
}
self.MIMETypes = {
0x1E653E9C : "JPEG",
0x2D42AC3E : "NavTeq",
0x4F328CA0 : "PNG",
0x4DC66C5A : "HDC",
0x4EB03469 : "TTN TPEG 2",
0x52103469 : "TTN TPEG 3",
0x82F03DFC : "HERE TPEG",
0xB39EBEB2 : "TTN TPEG 1",
0xB7F03DFC : "HERE Image",
0xB81FFAA8 : "Unknown Test",
0xBB492AAC : "Text",
0xBE4B7536 : "Primary Image",
0xD9C72536 : "Station Logo",
0xEECB55B6 : "HD TMC",
0xEF042E96 : "TTN STM Weather",
0xFF8422D7 : "TTN STM Traffic"
}
self.pointer_cursor = Gdk.Cursor(Gdk.CursorType.LEFT_PTR)
self.hand_cursor = Gdk.Cursor(Gdk.CursorType.HAND2)
@ -208,16 +235,28 @@ class NRSC5_DUI(object):
self.set_tuning_actions(self.btnAudioPrgs1, "btn_prg1", False, False)
self.set_tuning_actions(self.btnAudioPrgs2, "btn_prg2", False, False)
self.set_tuning_actions(self.btnAudioPrgs3, "btn_prg3", False, False)
self.set_tuning_actions(self.btnAudioPrgs4, "btn_prg4", False, False)
self.set_tuning_actions(self.btnAudioPrgs5, "btn_prg5", False, False)
self.set_tuning_actions(self.btnAudioPrgs6, "btn_prg6", False, False)
self.set_tuning_actions(self.btnAudioPrgs7, "btn_prg7", False, False)
self.set_tuning_actions(self.lblAudioPrgs0, "prg0", True, True)
self.set_tuning_actions(self.lblAudioPrgs1, "prg1", True, True)
self.set_tuning_actions(self.lblAudioPrgs2, "prg2", True, True)
self.set_tuning_actions(self.lblAudioPrgs3, "prg3", True, True)
self.set_tuning_actions(self.lblAudioPrgs4, "prg4", True, True)
self.set_tuning_actions(self.lblAudioPrgs5, "prg5", True, True)
self.set_tuning_actions(self.lblAudioPrgs6, "prg6", True, True)
self.set_tuning_actions(self.lblAudioPrgs7, "prg7", True, True)
self.set_tuning_actions(self.lblAudioSvcs0, "svc0", True, True)
self.set_tuning_actions(self.lblAudioSvcs1, "svc1", True, True)
self.set_tuning_actions(self.lblAudioSvcs2, "svc2", True, True)
self.set_tuning_actions(self.lblAudioSvcs3, "svc3", True, True)
self.set_tuning_actions(self.lblAudioSvcs4, "svc4", True, True)
self.set_tuning_actions(self.lblAudioSvcs5, "svc5", True, True)
self.set_tuning_actions(self.lblAudioSvcs6, "svc6", True, True)
self.set_tuning_actions(self.lblAudioSvcs7, "svc7", True, True)
# setup bookmarks listview
nameRenderer = Gtk.CellRendererText()
@ -244,7 +283,7 @@ class NRSC5_DUI(object):
re.compile("^[0-9\:]{8,8} Title: (.*)$"), # 4 match title
re.compile("^[0-9\:]{8,8} Artist: (.*)$"), # 5 match artist
re.compile("^[0-9\:]{8,8} Album: (.*)$"), # 6 match album
re.compile("^[0-9\:]{8,8} LOT file: port=([\d]+) lot=([\d]+) name=(.*\.(?:jpg|jpeg|png|txt)) size=([\d]+) mime=([\w]+)$"), # 7 match file (album art, maps, weather info)
re.compile("^[0-9\:]{8,8} LOT file: port=([\d]+) lot=([\d]+) name=(.*\.(?:jpg|jpeg|png|txt)) size=([\d]+) mime=([\w]+) .*$"), # 7 match file (album art, maps, weather info)
re.compile("^[0-9\:]{8,8} MER: (-?[\d]+\.[\d]+) dB \(lower\), (-?[\d]+\.[\d]+) dB \(upper\)$"), # 8 match MER
re.compile("^[0-9\:]{8,8} BER: (0\.[\d]+), avg: (0\.[\d]+), min: (0\.[\d]+), max: (0\.[\d]+)$"), # 9 match BER
re.compile("^[0-9\:]{8,8} Best gain: (.*) dB,.*$"), # 10 match gain
@ -259,7 +298,9 @@ class NRSC5_DUI(object):
re.compile("^[0-9\:]{8,8} Synchronized$"), # 19 synchronized
re.compile("^[0-9\:]{8,8} Lost synchronization$"), # 20 lost synch
re.compile("^[0-9\:]{8,8} Lost device$"), # 21 lost device
re.compile("^[0-9\:]{8,8} Open device failed.$") # 22 No device
re.compile("^[0-9\:]{8,8} Open device failed.$"), # 22 No device
re.compile("^[0-9\:]{8,8} Stream data: port=([\d]+).* mime=([\w]+) size=([\d]+)$"), # 23 Navteq/HERE stream info
re.compile("^[0-9\:]{8,8} Packet data: port=([\d]+).* mime=([\w]+) size=([\d]+)$") # 24 Navteq/HERE packet info
]
self.loadSettings()
@ -290,6 +331,50 @@ class NRSC5_DUI(object):
self.lblExtend.set_sensitive(dlCoversSet)
self.cbExtend.set_sensitive(dlCoversSet)
def restart_program(self):
python = sys.executable
os.execl(python, python, *sys.argv)
def confirm_dialog(self, title, message):
dialog = Gtk.MessageDialog(parent=self.mainWindow, flags=0, message_type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.YES_NO, text=title)
dialog.format_secondary_text(message)
dialog.set_default_response(Gtk.ResponseType.YES)
response = dialog.run()
dialog.destroy()
return (response == Gtk.ResponseType.YES)
def on_cbxAspect_changed(self, btn):
screenAspect = self.cbxAspect.get_active_text()
if (screenAspect == "narrow") or (screenAspect == "wide"):
mainFile = os.path.join(resDir, "mainForm.glade")
gladeFile = os.path.join(resDir, "mainForm-"+screenAspect+".glade")
if (os.path.isfile(gladeFile)):
shutil.copy(gladeFile,mainFile)
title = "Aspect Changed"
message = "You have change the display layout to "+screenAspect+". This change will not happen until the application is restarted. Would you like to restart it now?"
if (self.confirm_dialog(title,message)):
self.restart_program()
def on_cbxSDRRadio_changed(self, btn):
useSDRPlay = (self.cbxSDRRadio.get_active_text() == "SDRPlay")
self.lblSdrPlaySer.set_visible(useSDRPlay)
self.txtSDRPlaySer.set_visible(useSDRPlay)
self.txtSDRPlaySer.set_can_focus(useSDRPlay)
self.label14d.set_visible(useSDRPlay)
self.lblSDRPlayAnt.set_visible(useSDRPlay)
self.cbxSDRPlayAnt.set_visible(useSDRPlay)
self.cbxSDRPlayAnt.set_can_focus(useSDRPlay)
self.label14a.set_visible(useSDRPlay)
self.lblRTL.set_visible(not(useSDRPlay))
self.spinRTL.set_visible(not(useSDRPlay))
self.spinRTL.set_can_focus(not(useSDRPlay))
self.label14b.set_visible(useSDRPlay)
self.lblDevIP.set_visible(not(useSDRPlay))
self.txtDevIP.set_visible(not(useSDRPlay))
self.txtDevIP.set_can_focus(not(useSDRPlay))
self.cbDevIP.set_visible(not(useSDRPlay))
self.cbDevIP.set_can_focus(not(useSDRPlay))
def img_to_pixbuf(self,img):
"""convert PIL.Image to GdkPixbuf.Pixbuf"""
data = GLib.Bytes.new(img.tobytes())
@ -305,7 +390,7 @@ class NRSC5_DUI(object):
return result
def on_cover_resize(self, container):
global mapDir
global mapDir, imgLANCZOS
if (self.did_resize()):
self.showArtwork(self.coverImage)
@ -313,13 +398,13 @@ class NRSC5_DUI(object):
if (self.mapData["mapMode"] == 0):
map_file = os.path.join(mapDir, "TrafficMap.png")
if os.path.isfile(map_file):
map_img = Image.open(map_file).resize((img_size, img_size), Image.LANCZOS)
map_img = Image.open(map_file).resize((img_size, img_size), imgLANCZOS)
self.imgMap.set_from_pixbuf(self.img_to_pixbuf(map_img))
else:
self.imgMap.set_from_icon_name("MISSING_IMAGE", Gtk.IconSize.DIALOG)
elif (self.mapData["mapMode"] == 1):
if os.path.isfile(self.mapData["weatherNow"]):
map_img = Image.open(self.mapData["weatherNow"]).resize((img_size, img_size), Image.LANCZOS)
map_img = Image.open(self.mapData["weatherNow"]).resize((img_size, img_size), imgLANCZOS)
self.imgMap.set_from_pixbuf(self.img_to_pixbuf(map_img))
else:
self.imgMap.set_from_icon_name("MISSING_IMAGE", Gtk.IconSize.DIALOG)
@ -435,6 +520,13 @@ class NRSC5_DUI(object):
result = musicbrainzngs.search_recordings(strict=setStrict, artist=searchArtist, recording=newTitle, type=setType, status=setStatus)
except:
print("MusicBrainz recording search error")
print("iteration =",i,".")
print("imgSaved =",imgSaved,".")
print("strict =",setStrict,".")
print("artist =",searchArtist,".")
print("recording =",newTitle,".")
print("type =",setType,".")
print("status =",setStatus,".")
if (result is not None) and ('recording-list' in result) and (len(result['recording-list']) != 0):
# loop through the list until you get a match
@ -522,7 +614,7 @@ class NRSC5_DUI(object):
self.showArtwork(logo)
else:
# add entry in database for the station if it doesn't exist
self.stationLogos[self.stationStr] = ["", "", "", ""]
self.stationLogos[self.stationStr] = ["", "", "", "", "", "", "", ""]
def service_data_type_name(self, type):
for key, value in self.ServiceDataType.items():
@ -556,21 +648,23 @@ class NRSC5_DUI(object):
self.spinPPM.update()
self.spinRTL.update()
useSDRPlay = (self.cbxSDRRadio.get_active_text() == "SDRPlay")
# enable aas output if temp dir was created
if (aasDir is not None):
self.nrsc5Args.append("--dump-aas-files")
self.nrsc5Args.append(aasDir)
# set IP address if rtl_tcp is used
if (self.cbDevIP.get_active()):
if (not(useSDRPlay)) and (self.cbDevIP.get_active()):
self.nrsc5Args.append("-H")
self.nrsc5Args.append(self.txtDevIP.get_text())
# set gain if auto gain is not selected
if (not self.cbAutoGain.get_active()):
self.streamInfo["Gain"] = self.spinGain.get_value()
self.streamInfo["Gain"] = round(self.spinGain.get_value(),2)
self.nrsc5Args.append("-g")
self.nrsc5Args.append(str(int(self.streamInfo["Gain"])))
self.nrsc5Args.append(str(self.streamInfo["Gain"]))
# set ppm error if not zero
if (self.spinPPM.get_value() != 0):
@ -578,23 +672,49 @@ class NRSC5_DUI(object):
self.nrsc5Args.append(str(int(self.spinPPM.get_value())))
# set rtl device number if not zero
if (self.spinRTL.get_value() != 0):
if (not(useSDRPlay)) and (self.spinRTL.get_value() != 0):
self.nrsc5Args.append("-d")
self.nrsc5Args.append(str(int(self.spinRTL.get_value())))
# set log level to 2 if SDRPLay enabled
#if (self.cbSDRPlay.get_active()):
if (useSDRPlay):
self.nrsc5Args.append("-l2")
# set SDRPlay serial number if not blank
#if (self.cbSDRPlay.get_active()) and (self.txtSDRPlaySer.get_text() != ""):
if (useSDRPlay) and (self.txtSDRPlaySer.get_text() != ""):
self.nrsc5Args.append("-d")
self.nrsc5Args.append(self.txtSDRPlaySer.get_text())
# set SDRPlay antenna if not blank
#if (self.cbSDRPlay.get_active()) and (self.cbxSDRPlayAnt.get_active_text() != ""):
if (useSDRPlay) and (self.cbxSDRPlayAnt.get_active_text() != ""):
if self.cbxSDRPlayAnt.get_active_text() != "Auto":
self.nrsc5Args.append("-A")
self.nrsc5Args.append("Antenna "+self.cbxSDRPlayAnt.get_active_text())
# set frequency and stream
self.nrsc5Args.append(str(self.spinFreq.get_value()))
self.nrsc5Args.append(str(int(self.streamNum)))
print(self.nrsc5Args)
# start the timer
self.statusTimer = Timer(1, self.checkStatus)
self.statusTimer.start()
# disable the controls
self.spinFreq.set_sensitive(False)
self.cbxAspect.set_sensitive(False)
self.cbxSDRRadio.set_sensitive(False)
self.spinGain.set_sensitive(False)
self.spinPPM.set_sensitive(False)
self.spinRTL.set_sensitive(False)
self.txtDevIP.set_sensitive(False)
self.cbDevIP.set_sensitive(False)
self.txtSDRPlaySer.set_sensitive(False)
self.cbxSDRPlayAnt.set_sensitive(False)
self.btnPlay.set_sensitive(False)
self.btnStop.set_sensitive(True)
self.cbAutoGain.set_sensitive(False)
@ -624,7 +744,7 @@ class NRSC5_DUI(object):
self.btnDelete.set_sensitive(self.bookmarked)
def get_bookmark_names(self):
self.booknames = ["","","",""]
self.booknames = ["","","","","","","",""]
freq = str(int((self.spinFreq.get_value()+0.005)*10))
for b in self.bookmarks:
test = str(b[2])
@ -651,8 +771,14 @@ class NRSC5_DUI(object):
if (not self.cbAutoGain.get_active()):
self.spinGain.set_sensitive(True)
self.spinFreq.set_sensitive(True)
self.cbxAspect.set_sensitive(True)
self.cbxSDRRadio.set_sensitive(True)
self.spinPPM.set_sensitive(True)
self.spinRTL.set_sensitive(True)
self.txtDevIP.set_sensitive(True)
self.cbDevIP.set_sensitive(True)
self.txtSDRPlaySer.set_sensitive(True)
self.cbxSDRPlayAnt.set_sensitive(True)
self.btnPlay.set_sensitive(True)
self.btnStop.set_sensitive(False)
self.btnBookmark.set_sensitive(False)
@ -723,13 +849,13 @@ class NRSC5_DUI(object):
authors = [
"Cody Nybo <cmnybo@gmail.com>",
"Clayton Smith <argilo@gmail.com>",
"nefie <zefie@zefie.net>",
"zefie <zefie@zefie.net>",
"Mark J. Fine <mark.fine@fineware-swl.com>"
]
license = """
NRSC5 DUI - A second-generation graphical interface for nrsc5
Copyright (C) 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021 Mark J. Fine
Copyright (C) 2017-2019 Cody Nybo & Clayton Smith, 2019 zefie, 2021/2022 Mark J. Fine
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
@ -789,6 +915,10 @@ class NRSC5_DUI(object):
self.btnAudioPrgs1.set_active(self.update_btns and self.streamNum == 1)
self.btnAudioPrgs2.set_active(self.update_btns and self.streamNum == 2)
self.btnAudioPrgs3.set_active(self.update_btns and self.streamNum == 3)
self.btnAudioPrgs4.set_active(self.update_btns and self.streamNum == 4)
self.btnAudioPrgs5.set_active(self.update_btns and self.streamNum == 5)
self.btnAudioPrgs6.set_active(self.update_btns and self.streamNum == 6)
self.btnAudioPrgs7.set_active(self.update_btns and self.streamNum == 7)
self.update_btns = True
def on_program_select(self, _label, evt):
@ -849,7 +979,7 @@ class NRSC5_DUI(object):
self.btnDelete.set_sensitive(iter is not None)
def on_radMap_toggled(self, btn):
global mapDir
global mapDir, imgLANCZOS
if (btn.get_active()):
img_size = min(self.alignmentMap.get_allocated_height(), self.alignmentMap.get_allocated_width()) - 12
if (img_size < 200):
@ -858,7 +988,7 @@ class NRSC5_DUI(object):
self.mapData["mapMode"] = 0
mapFile = os.path.join(mapDir, "TrafficMap.png")
if (os.path.isfile(mapFile)): # check if map exists
mapImg = Image.open(mapFile).resize((img_size, img_size), Image.LANCZOS) # scale map to fit window
mapImg = Image.open(mapFile).resize((img_size, img_size), imgLANCZOS) # scale map to fit window
self.imgMap.set_from_pixbuf(imgToPixbuf(mapImg)) # convert image to pixbuf and display
else:
self.imgMap.set_from_icon_name("MISSING_IMAGE", Gtk.IconSize.DIALOG) # display missing image if file is not found
@ -866,7 +996,7 @@ class NRSC5_DUI(object):
elif (btn == self.radMapWeather):
self.mapData["mapMode"] = 1
if (os.path.isfile(self.mapData["weatherNow"])):
mapImg = Image.open(self.mapData["weatherNow"]).resize((img_size, img_size), Image.LANCZOS) # scale map to fit window
mapImg = Image.open(self.mapData["weatherNow"]).resize((img_size, img_size), imgLANCZOS) # scale map to fit window
self.imgMap.set_from_pixbuf(imgToPixbuf(mapImg)) # convert image to pixbuf and display
else:
self.imgMap.set_from_icon_name("MISSING_IMAGE", Gtk.IconSize.DIALOG) # display missing image if file is not found
@ -1000,20 +1130,38 @@ class NRSC5_DUI(object):
self.lblSlogan.set_tooltip_text(self.streamInfo["Slogan"])
self.lblMessage.set_label(self.streamInfo["Message"])
self.lblMessage.set_tooltip_text(self.streamInfo["Message"])
if (self.txtMessage2):
self.txtMessage2.set_label(self.streamInfo["Message"])
self.txtMessage2.set_tooltip_text(self.streamInfo["Message"])
self.lblAlert.set_label(self.streamInfo["Alert"])
self.lblAlert.set_tooltip_text(self.streamInfo["Alert"])
if (self.txtAlert2):
self.txtAlert2.set_label(self.streamInfo["Alert"])
self.txtAlert2.set_tooltip_text(self.streamInfo["Alert"])
self.set_button_name(self.btnAudioPrgs0,self.btnAudioLbl0,0)
self.set_button_name(self.btnAudioPrgs1,self.btnAudioLbl1,1)
self.set_button_name(self.btnAudioPrgs2,self.btnAudioLbl2,2)
self.set_button_name(self.btnAudioPrgs3,self.btnAudioLbl3,3)
self.set_button_name(self.btnAudioPrgs4,self.btnAudioLbl4,4)
self.set_button_name(self.btnAudioPrgs5,self.btnAudioLbl5,5)
self.set_button_name(self.btnAudioPrgs6,self.btnAudioLbl6,6)
self.set_button_name(self.btnAudioPrgs7,self.btnAudioLbl7,7)
self.set_label_name(self.lblAudioPrgs0, self.streamInfo["Streams"][0], True)
self.set_label_name(self.lblAudioPrgs1, self.streamInfo["Streams"][1], True)
self.set_label_name(self.lblAudioPrgs2, self.streamInfo["Streams"][2], True)
self.set_label_name(self.lblAudioPrgs3, self.streamInfo["Streams"][3], True)
self.set_label_name(self.lblAudioPrgs4, self.streamInfo["Streams"][4], True)
self.set_label_name(self.lblAudioPrgs5, self.streamInfo["Streams"][5], True)
self.set_label_name(self.lblAudioPrgs6, self.streamInfo["Streams"][6], True)
self.set_label_name(self.lblAudioPrgs7, self.streamInfo["Streams"][7], True)
self.set_label_name(self.lblAudioSvcs0, self.streamInfo["Programs"][0], True)
self.set_label_name(self.lblAudioSvcs1, self.streamInfo["Programs"][1], True)
self.set_label_name(self.lblAudioSvcs2, self.streamInfo["Programs"][2], True)
self.set_label_name(self.lblAudioSvcs3, self.streamInfo["Programs"][3], True)
self.set_label_name(self.lblAudioSvcs4, self.streamInfo["Programs"][4], True)
self.set_label_name(self.lblAudioSvcs5, self.streamInfo["Programs"][5], True)
self.set_label_name(self.lblAudioSvcs6, self.streamInfo["Programs"][6], True)
self.set_label_name(self.lblAudioSvcs7, self.streamInfo["Programs"][7], True)
self.set_label_name(self.lblDataSvcs0, self.streamInfo["Services"][0], False)
self.set_label_name(self.lblDataSvcs1, self.streamInfo["Services"][1], False)
self.set_label_name(self.lblDataSvcs2, self.streamInfo["Services"][2], False)
@ -1031,7 +1179,7 @@ class NRSC5_DUI(object):
if (self.cbAutoGain.get_active()):
self.spinGain.set_value(self.streamInfo["Gain"])
self.lblGain.set_label("{:2.1f}dB".format(self.streamInfo["Gain"]))
self.lblGain.set_label("{:2.2f}dB".format(self.streamInfo["Gain"]))
# second param is lot id, if -1, show cover, otherwise show cover
# technically we should show the file with the matching lot id
@ -1069,7 +1217,7 @@ class NRSC5_DUI(object):
self.statusTimer.start()
def processTrafficMap(self, fileName):
global aasDir, mapDir
global aasDir, mapDir, imgLANCZOS
r = re.compile("^[\d]+_TMT_.*_([1-3])_([1-3])_([\d]{4})([\d]{2})([\d]{2})_([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})_([0-9A-Fa-f]{4})\..*$") # match file name
m = r.match(fileName)
@ -1122,7 +1270,7 @@ class NRSC5_DUI(object):
imgBig = (981,981) # size of a weather map
posTS = (imgBig[0]-235, imgBig[1]-29) # calculate position to put timestamp (bottom right)
imgTS = self.mkTimestamp(t, imgBig, posTS) # create timestamp for a weather map
imgTS = imgTS.resize((imgMap.size[0], imgMap.size[1]), Image.LANCZOS) # resize it so it's proportional to the size of a traffic map (981 -> 600)
imgTS = imgTS.resize((imgMap.size[0], imgMap.size[1]), imgLANCZOS) # resize it so it's proportional to the size of a traffic map (981 -> 600)
imgMap = Image.alpha_composite(imgMap, imgTS) # overlay timestamp on traffic map
imgMap.save(os.path.join(mapDir, "TrafficMap.png")) # save traffic map
@ -1130,13 +1278,13 @@ class NRSC5_DUI(object):
# display on map page
if (self.radMapTraffic.get_active()):
img_size = min(self.alignmentMap.get_allocated_height(), self.alignmentMap.get_allocated_width()) - 12
imgMap = imgMap.resize((img_size, img_size), Image.LANCZOS) # scale map to fit window
imgMap = imgMap.resize((img_size, img_size), imgLANCZOS) # scale map to fit window
self.imgMap.set_from_pixbuf(imgToPixbuf(imgMap)) # convert image to pixbuf and display
if (self.mapViewer is not None): self.mapViewer.updated(0) # notify map viwerer if it's open
def processWeatherOverlay(self, fileName):
global aasDir, mapDir
global aasDir, mapDir, imgLANCZOS
r = re.compile("^[\d]+_DWRO_(.*)_.*_([\d]{4})([\d]{2})([\d]{2})_([\d]{2})([\d]{2})_([0-9A-Fa-f]+)\..*$") # match file name
m = r.match(fileName)
@ -1185,7 +1333,7 @@ class NRSC5_DUI(object):
posTS = (imgMap.size[0]-235, imgMap.size[1]-29) # calculate position to put timestamp (bottom right)
imgTS = self.mkTimestamp(t, imgMap.size, posTS) # create timestamp
imgRadar = Image.open(wxOlPath).convert("RGBA") # open radar overlay
imgRadar = imgRadar.resize(imgMap.size, Image.LANCZOS) # resize radar overlay to fit the map
imgRadar = imgRadar.resize(imgMap.size, imgLANCZOS) # resize radar overlay to fit the map
imgMap = Image.alpha_composite(imgMap, imgRadar) # overlay radar image on map
imgMap = Image.alpha_composite(imgMap, imgTS) # overlay timestamp
imgMap.save(wxMapPath) # save weather map
@ -1195,7 +1343,7 @@ class NRSC5_DUI(object):
# display on map page
if (self.radMapWeather.get_active()):
img_size = min(self.alignmentMap.get_allocated_height(), self.alignmentMap.get_allocated_width()) - 12
imgMap = imgMap.resize((img_size, img_size), Image.LANCZOS) # scale map to fit window
imgMap = imgMap.resize((img_size, img_size), imgLANCZOS) # scale map to fit window
self.imgMap.set_from_pixbuf(imgToPixbuf(imgMap)) # convert image to pixbuf and display
self.proccessWeatherMaps() # get rid of old maps and add new ones to the list
@ -1321,7 +1469,7 @@ class NRSC5_DUI(object):
def checkPorts(self, port, type):
result = -1
for i in range(0,3):
for i in range(0,7):
if (len(self.streams[i]) > type):
if (port == self.streams[i][type]):
result = i
@ -1369,6 +1517,16 @@ class NRSC5_DUI(object):
self.lastLOT = lot
self.xhdrChanged = True
self.debugLog("XHDR Changed: {:s} (lot {:s})".format(xhdr,lot))
elif (self.regex[23].match(line)):
# match HERE Images
m = self.regex[23].match(line)
if (m):
p = int(m.group(1),16)
mime = m.group(2)
fileSize = int(m.group(3))
fileName = "HERE_Image.jpg"
# if (mime == "B7F03DFC"):
# print (line)
elif (self.regex[7].match(line)):
# match album art
m = self.regex[7].match(line)
@ -1387,7 +1545,7 @@ class NRSC5_DUI(object):
else:
actualFileSize = os.path.getsize(os.path.join(aasDir,fileName))
if (fileSize != actualFileSize):
self.debugLog("Corrupt file: " + fileName + " (expected: "+fileSize+" bytes, got "+actualFileSize+" bytes)")
self.debugLog("Corrupt file: " + fileName + " (expected: "+str(fileSize)+" bytes, got "+str(actualFileSize)+" bytes)")
if (coverStream > -1):
if coverStream == self.streamNum:
@ -1437,7 +1595,7 @@ class NRSC5_DUI(object):
self.debugLog("Found Stream: Type {:s}, Number {:02X}". format(t, s))
self.lastType = t
if (t == "audio" and s >= 1 and s <= 4):
if (t == "audio" and s >= 1 and s <= 8):
self.numStreams = s
self.streamInfo["Streams"][s-1] = n
if (t == "data"):
@ -1477,7 +1635,7 @@ class NRSC5_DUI(object):
# match Open device failed
self.on_btnStop_clicked(None)
self.set_synchronization(-1)
def getControls(self):
global resDir
# setup gui
@ -1501,12 +1659,23 @@ class NRSC5_DUI(object):
self.alignmentMap = builder.get_object("alignment_map")
self.imgMap = builder.get_object("imgMap")
self.spinFreq = builder.get_object("spinFreq")
self.cbxAspect = builder.get_object("cbxAspect")
self.cbxSDRRadio = builder.get_object("cbxSDRRadio")
self.spinGain = builder.get_object("spinGain")
self.spinPPM = builder.get_object("spinPPM")
self.spinRTL = builder.get_object("spinRTL")
self.txtDevIP = builder.get_object("txtDevIP")
self.cbAutoGain = builder.get_object("cbAutoGain")
self.spinPPM = builder.get_object("spinPPM")
self.lblRTL = builder.get_object("lblRTL")
self.spinRTL = builder.get_object("spinRTL")
self.label14b = builder.get_object("label14b")
self.lblDevIP = builder.get_object("lblDevIP")
self.txtDevIP = builder.get_object("txtDevIP")
self.cbDevIP = builder.get_object("cbDevIP")
self.lblSdrPlaySer = builder.get_object("lblSdrPlaySer")
self.txtSDRPlaySer = builder.get_object("txtSDRPlaySer")
self.label14d = builder.get_object("label14d")
self.lblSDRPlayAnt = builder.get_object("lblSDRPlayAnt")
self.cbxSDRPlayAnt = builder.get_object("cbxSDRPlayAnt")
self.label14a = builder.get_object("label14a")
self.cbLog = builder.get_object("cbLog")
self.cbCovers = builder.get_object("cbCovers")
self.lblCoverIncl = builder.get_object("lblCoverIncl")
@ -1528,22 +1697,40 @@ class NRSC5_DUI(object):
self.lblSlogan = builder.get_object("lblSlogan")
self.lblMessage = builder.get_object("lblMessage")
self.lblAlert = builder.get_object("lblAlert")
self.txtMessage2 = builder.get_object("txtMessage2")
self.txtAlert2 = builder.get_object("txtAlert2")
self.btnAudioPrgs0 = builder.get_object("btn_audio_prgs0")
self.btnAudioPrgs1 = builder.get_object("btn_audio_prgs1")
self.btnAudioPrgs2 = builder.get_object("btn_audio_prgs2")
self.btnAudioPrgs3 = builder.get_object("btn_audio_prgs3")
self.btnAudioPrgs4 = builder.get_object("btn_audio_prgs4")
self.btnAudioPrgs5 = builder.get_object("btn_audio_prgs5")
self.btnAudioPrgs6 = builder.get_object("btn_audio_prgs6")
self.btnAudioPrgs7 = builder.get_object("btn_audio_prgs7")
self.btnAudioLbl0 = builder.get_object("btn_audio_lbl0")
self.btnAudioLbl1 = builder.get_object("btn_audio_lbl1")
self.btnAudioLbl2 = builder.get_object("btn_audio_lbl2")
self.btnAudioLbl3 = builder.get_object("btn_audio_lbl3")
self.btnAudioLbl4 = builder.get_object("btn_audio_lbl4")
self.btnAudioLbl5 = builder.get_object("btn_audio_lbl5")
self.btnAudioLbl6 = builder.get_object("btn_audio_lbl6")
self.btnAudioLbl7 = builder.get_object("btn_audio_lbl7")
self.lblAudioPrgs0 = builder.get_object("lbl_audio_prgs0")
self.lblAudioPrgs1 = builder.get_object("lbl_audio_prgs1")
self.lblAudioPrgs2 = builder.get_object("lbl_audio_prgs2")
self.lblAudioPrgs3 = builder.get_object("lbl_audio_prgs3")
self.lblAudioPrgs4 = builder.get_object("lbl_audio_prgs4")
self.lblAudioPrgs5 = builder.get_object("lbl_audio_prgs5")
self.lblAudioPrgs6 = builder.get_object("lbl_audio_prgs6")
self.lblAudioPrgs7 = builder.get_object("lbl_audio_prgs7")
self.lblAudioSvcs0 = builder.get_object("lbl_audio_svcs0")
self.lblAudioSvcs1 = builder.get_object("lbl_audio_svcs1")
self.lblAudioSvcs2 = builder.get_object("lbl_audio_svcs2")
self.lblAudioSvcs3 = builder.get_object("lbl_audio_svcs3")
self.lblAudioSvcs4 = builder.get_object("lbl_audio_svcs4")
self.lblAudioSvcs5 = builder.get_object("lbl_audio_svcs5")
self.lblAudioSvcs6 = builder.get_object("lbl_audio_svcs6")
self.lblAudioSvcs7 = builder.get_object("lbl_audio_svcs7")
self.lblDataSvcs0 = builder.get_object("lbl_data_svcs0")
self.lblDataSvcs1 = builder.get_object("lbl_data_svcs1")
self.lblDataSvcs2 = builder.get_object("lbl_data_svcs2")
@ -1598,8 +1785,8 @@ class NRSC5_DUI(object):
"Artist": "", # track artist
"Cover": "", # filename of track cover
"Logo": "", # station logo
"Streams": ["","","",""], # audio stream names
"Programs": ["","","",""], # audio stream types
"Streams": ["","","","","","","",""], # audio stream names
"Programs": ["","","","","","","",""], # audio stream types
"Services": ["","","",""], # data service names
"SvcTypes": ["","","",""], # data service types
"Bitrate": 0, # current stream bit rate
@ -1608,7 +1795,7 @@ class NRSC5_DUI(object):
"Gain": 0 # automatic gain
}
self.streams = [[],[],[],[]]
self.streams = [[],[],[],[],[],[],[],[]]
self.numStreams = 0
self.numServices = 0
self.lastType = 0
@ -1619,6 +1806,10 @@ class NRSC5_DUI(object):
self.btnAudioLbl1.set_label("")
self.btnAudioLbl2.set_label("")
self.btnAudioLbl3.set_label("")
self.btnAudioLbl4.set_label("")
self.btnAudioLbl5.set_label("")
self.btnAudioLbl6.set_label("")
self.btnAudioLbl7.set_label("")
self.lblBitRate.set_label("")
self.lblBitRate2.set_label("")
self.lblError.set_label("")
@ -1634,10 +1825,22 @@ class NRSC5_DUI(object):
self.lblSlogan.set_tooltip_text("")
self.lblMessage.set_label("")
self.lblMessage.set_tooltip_text("")
if (self.txtMessage2):
self.txtMessage2.set_label("")
self.txtMessage2.set_tooltip_text("")
self.lblAlert.set_label("")
self.lblAlert.set_tooltip_text("")
if (self.txtAlert2):
self.txtAlert2.set_label("")
self.txtAlert2.set_tooltip_text("")
self.btnAudioPrgs0.set_sensitive(False)
self.btnAudioPrgs1.set_sensitive(False)
self.btnAudioPrgs2.set_sensitive(False)
self.btnAudioPrgs3.set_sensitive(False)
self.btnAudioPrgs4.set_sensitive(False)
self.btnAudioPrgs5.set_sensitive(False)
self.btnAudioPrgs6.set_sensitive(False)
self.btnAudioPrgs7.set_sensitive(False)
self.lblAudioPrgs0.set_label("")
self.lblAudioPrgs0.set_sensitive(False)
self.lblAudioPrgs1.set_label("")
@ -1646,6 +1849,14 @@ class NRSC5_DUI(object):
self.lblAudioPrgs2.set_sensitive(False)
self.lblAudioPrgs3.set_label("")
self.lblAudioPrgs3.set_sensitive(False)
self.lblAudioPrgs4.set_label("")
self.lblAudioPrgs4.set_sensitive(False)
self.lblAudioPrgs5.set_label("")
self.lblAudioPrgs5.set_sensitive(False)
self.lblAudioPrgs6.set_label("")
self.lblAudioPrgs6.set_sensitive(False)
self.lblAudioPrgs7.set_label("")
self.lblAudioPrgs7.set_sensitive(False)
self.lblAudioSvcs0.set_label("")
self.lblAudioSvcs0.set_sensitive(False)
self.lblAudioSvcs1.set_label("")
@ -1654,6 +1865,14 @@ class NRSC5_DUI(object):
self.lblAudioSvcs2.set_sensitive(False)
self.lblAudioSvcs3.set_label("")
self.lblAudioSvcs3.set_sensitive(False)
self.lblAudioSvcs4.set_label("")
self.lblAudioSvcs4.set_sensitive(False)
self.lblAudioSvcs5.set_label("")
self.lblAudioSvcs5.set_sensitive(False)
self.lblAudioSvcs6.set_label("")
self.lblAudioSvcs6.set_sensitive(False)
self.lblAudioSvcs7.set_label("")
self.lblAudioSvcs7.set_sensitive(False)
self.lblDataSvcs0.set_label("")
self.lblDataSvcs1.set_label("")
self.lblDataSvcs2.set_label("")
@ -1679,6 +1898,9 @@ class NRSC5_DUI(object):
if (os.path.isfile(stationLogos)):
with open(stationLogos, mode='r') as f:
self.stationLogos = json.load(f)
for station in self.stationLogos:
while (len(self.stationLogos[station]) < 8):
self.stationLogos[station].append("")
except:
self.debugLog("Error: Unable to load station logo database", True)
@ -1724,6 +1946,12 @@ class NRSC5_DUI(object):
self.cbAutoGain.set_active(config["AutoGain"])
self.spinPPM.set_value(config["PPMError"])
self.spinRTL.set_value(config["RTL"])
if ("SDRRadio" in config):
self.cbxSDRRadio.set_active_id("rcvr"+config["SDRRadio"])
if ("SDRPlaySer" in config):
self.txtSDRPlaySer.set_text(config["SDRPlaySer"])
if ("SDRPlayAnt" in config):
self.cbxSDRPlayAnt.set_active_id("ant"+config["SDRPlayAnt"])
self.cbLog.set_active(config["LogToFile"])
if ("DLoadArt" in config):
self.cbCovers.set_active(config["DLoadArt"])
@ -1812,6 +2040,9 @@ class NRSC5_DUI(object):
"PPMError" : int(self.spinPPM.get_value()),
"RTL" : int(self.spinRTL.get_value()),
"DevIP" : self.txtDevIP.get_text(),
"SDRRadio" : self.cbxSDRRadio.get_active_text(),
"SDRPlaySer" : self.txtSDRPlaySer.get_text(),
"SDRPlayAnt" : self.cbxSDRPlayAnt.get_active_text(),
"LogToFile" : self.cbLog.get_active(),
"DLoadArt" : self.cbCovers.get_active(),
"StationArt" : self.cbCoverIncl.get_active(),
@ -1981,12 +2212,13 @@ class NRSC5_Map(object):
self.callback() # run the callback
def animate(self):
global imgLANCZOS
fileName = self.weatherMaps[self.mapIndex] if len(self.weatherMaps) else ""
if (os.path.isfile(fileName)):
self.animateBusy = True # set busy to true
if (self.config["scale"]):
mapImg = imgToPixbuf(Image.open(fileName).resize((600,600), Image.LANCZOS)) # open weather map, resize to 600x600, and convert to pixbuf
mapImg = imgToPixbuf(Image.open(fileName).resize((600,600), imgLANCZOS)) # open weather map, resize to 600x600, and convert to pixbuf
else:
mapImg = imgToPixbuf(Image.open(fileName)) # open weather map and convert to pixbuf
@ -2009,9 +2241,11 @@ class NRSC5_Map(object):
self.mapIndex = 0
def showImage(self, fileName, scale):
global imgLANCZOS
if (os.path.isfile(fileName)):
if (scale):
mapImg = Image.open(fileName).resize((600,600), Image.LANCZOS) # open and scale map to fit window
mapImg = Image.open(fileName).resize((600,600), imgLANCZOS) # open and scale map to fit window
else:
mapImg = Image.open(fileName) # open map

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
pygobject==3.44.1
musicbrainzngs==0.7.1
numpy==1.25.1
Pillow==10.0.0
pyOpenSSL==23.0.0
python-dateutil==2.8.2
urllib3==1.26.14

2454
res/mainForm-narrow.glade Normal file

File diff suppressed because it is too large Load Diff

2507
res/mainForm-wide.glade Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff