Compare commits
30 Commits
Author | SHA1 | Date |
---|---|---|
markjfine | eaeed80311 | |
markjfine | dab697f082 | |
Mark J. Fine | 49cf532fdc | |
Mark J. Fine | 76b7348943 | |
Mark J. Fine | a27f2969d4 | |
Mark J. Fine | 47d2c85abd | |
markjfine | aa7eca98ee | |
Andy Lianghua Xu | a3a6ac2a41 | |
Andy Lianghua Xu | 5f2e736d4b | |
Mark J. Fine | fd5ba2796b | |
Mark J. Fine | 31b4d262ed | |
Mark J. Fine | 3f77d35962 | |
Mark J. Fine | 69057f967a | |
Mark J. Fine | 1679eda1c5 | |
Mark J. Fine | d83c934b95 | |
Mark J. Fine | ec61b85afc | |
Mark J. Fine | b945e3611a | |
Mark J. Fine | 6069f6c503 | |
Mark J. Fine | a3068acddc | |
Mark J. Fine | ded176ae46 | |
Mark J. Fine | b65a8828be | |
Mark J. Fine | 8f2e8d4c8a | |
Mark J. Fine | 41295117e8 | |
Mark J. Fine | edf0b93b97 | |
Mark J. Fine | d6917ec5c5 | |
Mark J. Fine | 4c7bbe4358 | |
Mark J. Fine | 6ae3c93754 | |
markjfine | b8ae248bcf | |
markjfine | cd0b378276 | |
markjfine | bf78481456 |
|
@ -0,0 +1,8 @@
|
|||
**/._*
|
||||
**/.DS_Store
|
||||
*.txt
|
||||
*.log
|
||||
aas/**
|
||||
cfg/*.txt
|
||||
cfg/*.json
|
||||
map/**
|
34
README.md
34
README.md
|
@ -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)
|
||||
|
|
BIN
bin/libnrsc5.dll
BIN
bin/libnrsc5.dll
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
bin/nrsc5.exe
BIN
bin/nrsc5.exe
Binary file not shown.
322
nrsc5-dui.py
322
nrsc5-dui.py
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue