From 255acc1d6131dabaa2abcc9e4a01f4f6aca60b5f Mon Sep 17 00:00:00 2001 From: Spiros Georgaras Date: Sun, 15 Aug 2021 17:18:01 +0300 Subject: [PATCH] - Version 0.8.9.6 (0.9-beta3) - RadioBrowser History Management finalized - Fields' placement fixed in RadioBrowser Search Window - RadioBrowser man page added - Docs updated --- Changelog | 7 + devel/build_install_pyradio | 8 + pyradio.1 | 231 ++++++++------- pyradio/__init__.py | 2 +- pyradio/browser.py | 468 +++++++++++++++++++++++-------- pyradio/config.py | 1 + pyradio/radio.py | 31 +- pyradio/simple_curses_widgets.py | 4 +- pyradio_rb.1 | 252 +++++++++++++++++ radio-browser.html | 31 +- radio-browser.md | 25 +- 11 files changed, 799 insertions(+), 261 deletions(-) create mode 100644 pyradio_rb.1 diff --git a/Changelog b/Changelog index b31b78d2..782893f2 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,10 @@ +2021-08-15 + * Version 0.8.9.6 (0.9-beta3) + * RadioBrowser History Management finalized + * Fields' placement fixed in RadioBrowser Search Window + * RadioBrowser man page added + * Docs updated + 2021-08-11 * Version 0.8.9.5 (0.9-beta2) * Fixed a crash that would occur when searching for name only in diff --git a/devel/build_install_pyradio b/devel/build_install_pyradio index e842960b..e90fca91 100755 --- a/devel/build_install_pyradio +++ b/devel/build_install_pyradio @@ -80,6 +80,7 @@ function uninstall(){ echo done echo -n '** Removing help files ... ' sudo rm -f /usr/share/man/man1/pyradio.1.gz 2>/dev/null || sudo rm -f /usr/local/share/man/man1/pyradio.1.gz 2>/dev/null + sudo rm -f /usr/share/man/man1/pyradio_rb.1.gz 2>/dev/null || sudo rm -f /usr/local/share/man/man1/pyradio_rb.1.gz 2>/dev/null echo done } || { echo -n '** Removing executable ... ' @@ -92,6 +93,7 @@ function uninstall(){ rm -rf ~/.local/local/share/doc/pyradio 2>/dev/null fi rm -f ~/.local/share/man/man1/pyradio.1.gz 2>/dev/null + rm -f ~/.local/share/man/man1/pyradio_rb.1.gz 2>/dev/null echo done } #set -x @@ -142,7 +144,9 @@ cp ../*html usr/local/share/doc cp ../LICENCE usr/local/share/doc/copyright mkdir -p usr/local/share/man/man1 cp ../pyradio.1 usr/local/share/man/man1/ +cp ../pyradio_rb.1 usr/local/share/man/man1/ gzip usr/local/share/man/man1/pyradio.1 +gzip usr/local/share/man/man1/pyradio_rb.1 sed -i "s/|version|/$cur_version/" DEBIAN/control cd .. @@ -271,6 +275,8 @@ python"${TO_PYTHON}" setup.py build && { sudo python"${TO_PYTHON}" setup.py install && { gzip -k pyradio.1 sudo mv -f pyradio.1.gz /usr/share/man/man1 2>/dev/null || sudo mv -f pyradio.1.gz /usr/local/share/man/man1 + gzip -k pyradio_rb.1 + sudo mv -f pyradio_rb.1.gz /usr/share/man/man1 2>/dev/null || sudo mv -f pyradio_rb.1.gz /usr/local/share/man/man1 DOC=/usr/share/doc/pyradio sudo mkdir "$DOC" 2>/dev/null if [ ! -d "$DOC" ];then @@ -294,8 +300,10 @@ python"${TO_PYTHON}" setup.py build && { echo "***** installing for user..." python"${TO_PYTHON}" setup.py install --user && { gzip -k pyradio.1 + gzip -k pyradio_rb.1 mkdir -p ~/.local/share/man/man1 2>/dev/null mv -f pyradio.1.gz ~/.local/share/man/man1 2>/dev/null || mv -f pyradio.1.gz ~/.local/local/share/man/man1 + mv -f pyradio_rb.1.gz ~/.local/share/man/man1 2>/dev/null || mv -f pyradio_rb.1.gz ~/.local/local/share/man/man1 DOC=~/.local/share/doc/pyradio mkdir -p "$DOC" 2>/dev/null if [ ! -d "$DOC" ];then diff --git a/pyradio.1 b/pyradio.1 index 5dde6a1c..4fe41c54 100644 --- a/pyradio.1 +++ b/pyradio.1 @@ -1,24 +1,24 @@ .\" Copyright (C) 2011 Ben Dowling .\" This manual is freely distributable under the terms of the GPL. .\" -.TH PYRADIO 1 "April 2021" +.TH pyradio1 "August 2021" PyRadio -.SH NAME +.SH Name .PP pyradio \- a curses Internet radio player. -.SH SYNOPSIS +.SH Synopsis .PP \fBpyradio\fR\ [\fIOPTIONS\fR] -.SH DESCRIPTION +.SH Description .PP -.B pyradio -is a command line Internet radio player based on curses, that uses external media players to perform the actual playback. +.B PyRadio +is a command line Internet Radio Player based on curses, that uses external media players to perform the actual playback. .PP It currently supports the following players: \fIMPV\fR, \fIMPlayer\fR and \fIVLC\fR. -.SH OPTIONS +.SH Options .IP \fB-s\fR,\fB\ \--stations\ \fR[\fISTATIONS\fR] Use specified station CSV file. @@ -45,11 +45,11 @@ Specify the extra player parameter set to be used with the default player. \fIAC .IP \fB-lp List extra players parameters. .IP \fB-U -Update \fBpyradio\fR. +Update \fBPyRadio\fR. .IP \fB--user Install only for current user (linux only). .IP \fB-R -Uninstall \fBpyradio\fR. +Uninstall \fBPyRadio\fR. .IP \fB--unlock Remove sessions' lock file. .IP \fB-d\fR,\fB\ \--debug @@ -59,7 +59,7 @@ Show usage information and exit. .RE .PP -The following options can also be set in \fBpyradio\fR’s configuration file: +The following options can also be set in \fBPyRadio\fR’s configuration file: .IP \fB-s\fR parameter \fIdefault_playlist\fR (default value: \fBstations\fR) @@ -68,7 +68,7 @@ parameter \fIdefault_station\fR (default value: \fB-1\fR) .IP \fB-u\fR parameter \fIplayer\fR (default value: \fBmpv, mplayer, vlc\fR) -.SH CONTROLS +.SH Controls .IP \fBUp\fR/\fBDown\fR/\fBPgUp\fR/\fBPgDown Change station selection @@ -130,10 +130,10 @@ Show keys help Quit .P -The same logic applies to all \fBpyradio\fR windows. +The same logic applies to all \fBPyRadio\fR windows. .IP \fBNote: -All windows - except the \fISearch window\fR - support changing the volume and muting / unmuting the player (provided that \fBpyradio\fR is actually connected to a station). +All windows - except the \fISearch window\fR - support changing the volume and muting / unmuting the player (provided that \fBPyRadio\fR is actually connected to a station). .IP \fBNote: When inserting numbers (either to jump to a station or to move a station), the number will be displayed at the right bottom corner of the window, suffixed by a "\fIG\fR", i.e. pressing \fI35\fR will display \fI[35G]\fR. @@ -141,9 +141,9 @@ When inserting numbers (either to jump to a station or to move a station), the n .IP \fBNote: When tagging a station position for a move action (by pressing "\fBJ\fR"), the position will be displayed at the right bottom corner of the window, suffixed by a "\fIJ\fR", i.e. pressing "\fIJ\fR" on position \fI35\fR will display \fI[35J]\fR. -.SH PYRADIO'S MODES +.SH Pyradio's Modes -\fBpyradio\fR has the following primary modes: +\fBPyRadio\fR has the following primary modes: .RS 5 .IP 1. 3 @@ -159,7 +159,7 @@ The \fBRegisters\fR mode. This is identical to the "\fIPlaylist\fR" mode, but in The \fBRegister Main\fR mode, which is identical to the "\fIMain\fR" mode, except it displays the content of a \fBnamed\fR register. .IP 5. 3 -The \fBListening\fR mode, which is intended to be used when you want \fBpyradio\fR to just play your favorite station and not take up too much space. It is ideal for tilling window manager use, as the whole TUI can be reduced all the way down to a single line (displaying the "\fIStatus Bar\fR"). In this mode, adjusting, muting and saving the volume are the only actions available. To get \fBpyradio\fR back to normal operation one would just resize its window to a reasonable size (7 lines vertically, or more). +The \fBListening\fR mode, which is intended to be used when you want \fBPyRadio\fR to just play your favorite station and not take up too much space. It is ideal for tilling window manager use, as the whole TUI can be reduced all the way down to a single line (displaying the "\fIStatus Bar\fR"). In this mode, adjusting, muting and saving the volume are the only actions available. To get \fBPyRadio\fR back to normal operation one would just resize its window to a reasonable size (7 lines vertically, or more). .RE @@ -182,27 +182,27 @@ The \fBPaste\fR mode, which is available in the \fIStation editor\fR window only The functions availabe through the \fIsecondary modes\fR are content dependant, so you can see what command is available by pressing "\fB?\fR" while within such a mode. Pressing any other key will exit the secondary mode. -.SH CONFIG FILE -\fBpyradio\fR upon its execution tries to read its configuration file (i.e. \fI~/.config/pyradio/config\fR). If this file is not found, it will be created. If an error occurs while parsing it, an error message will be displayed and \fBpyradio\fR will terminate. +.SH Config File +\fBPyRadio\fR upon its execution tries to read its configuration file (i.e. \fI~/.config/pyradio/config\fR). If this file is not found, it will be created. If an error occurs while parsing it, an error message will be displayed and \fBPyRadio\fR will terminate. -The file contains parameters such as the player to use, the playlist to load etc. It is heavily commented, so that manual editing is really easy. The best practice to manually edit this file is executing \fBpyradio\fR with the \fB-ocd\fR command line option, which will open the configuration directory in your file manager, and then edit it using your preferable text editor. +The file contains parameters such as the player to use, the playlist to load etc. It is heavily commented, so that manual editing is really easy. The best practice to manually edit this file is executing \fBPyRadio\fR with the \fB-ocd\fR command line option, which will open the configuration directory in your file manager, and then edit it using your preferable text editor. -The file can also be altered while \fBpyradio\fR is running, by pressing "\fIc\fR", which will open the "\fIConfiguration window\fR". This window presents all \fBpyradio\fR options and provide the way to change them and finally save them by pressing "\fIs\fR". +The file can also be altered while \fBPyRadio\fR is running, by pressing "\fIc\fR", which will open the "\fIConfiguration window\fR". This window presents all \fBPyRadio\fR options and provide the way to change them and finally save them by pressing "\fIs\fR". -In any case, \fBpyradio\fR will save the file before exiting (or in case Ctrl-C is pressed) if needed (e.g. if a config parameter has been changed during its execution). +In any case, \fBPyRadio\fR will save the file before exiting (or in case Ctrl-C is pressed) if needed (e.g. if a config parameter has been changed during its execution). -If saving the configuration file fails, \fBpyradio\fR will create a back up file and terminate. When restarted, \fBpyradio\fR will try to restore previously used settings from the said back up file. +If saving the configuration file fails, \fBPyRadio\fR will create a back up file and terminate. When restarted, \fBPyRadio\fR will try to restore previously used settings from the said back up file. -.SH ABOUT PLAYLIST FILES +.SH About Playlist Files .PP -\fBpyradio\fR reads the stations to use from a CSV file, where each line contains two columns, the first being the station name and the second being the stream URL. +\fBPyRadio\fR reads the stations to use from a CSV file, where each line contains two columns, the first being the station name and the second being the stream URL. .PP Optionally, a third column can be inserted, stating the encoding used by the station (more on this at \fBSPECIFYING STATIONS' ENCODING\fR). .PP -\fBpyradio\fR will by default load the user's stations file (e.g. \fI~/.config/pyradio/stations.csv\fR). If this file is not found, it will be created and populated with a default set of stations. +\fBPyRadio\fR will by default load the user's stations file (e.g. \fI~/.config/pyradio/stations.csv\fR). If this file is not found, it will be created and populated with a default set of stations. .IP \fBTip: -If you already have a custom \fIstations.csv\fR file, but want to update it with \fBpyradio\fR's default one, you just rename it, run \fBpyradio\fR (so that the default one get created) and then merge the two files. +If you already have a custom \fIstations.csv\fR file, but want to update it with \fBPyRadio\fR's default one, you just rename it, run \fBPyRadio\fR (so that the default one get created) and then merge the two files. .IP \fBNote: Older versions used to use \fI~/.pyradio\fR as default stations file. If this file is found, it will be copied to use's config directory (e.g. \fI~/.config/pyradio\fR) and renamed to \fIstations.csv\fR or if this file exists, to \fIpyradio.csv\fR. In this case, this file will be the default one. @@ -212,7 +212,7 @@ Older versions used to use \fI~/.pyradio\fR as default stations file. If this fi Specifying a playlist to load (command line) .PP -\fBpyradio\fR will normally load its default playlist file, as described above, upon its execution. A different file can be loaded when the \fI-s\fR command line option is used. +\fBPyRadio\fR will normally load its default playlist file, as described above, upon its execution. A different file can be loaded when the \fI-s\fR command line option is used. .PP The \fI-s\fR option will accept: @@ -235,7 +235,7 @@ To load a playlist called "\fIblues.csv\fR", one would use the command: \fBpyradio -s /path/to/\fIblues.csv\fR .RE -If this file was saved inside \fBpyradio\fR's configuration directory, one could use the following command: +If this file was saved inside \fBPyRadio\fR's configuration directory, one could use the following command: .RS 5 \fBpyradio -s \fIblues\fR @@ -260,14 +260,14 @@ Playlists found in "/home/user/.config/pyradio" .IP \fBNote\fR -The default playlist to load can also be set in \fBpyradio\fR’s configuration file, parameter \fIdefault_playlist\fR (default value is \fIstations\fR). +The default playlist to load can also be set in \fBPyRadio\fR’s configuration file, parameter \fIdefault_playlist\fR (default value is \fIstations\fR). .RE .PP .B -MANAGING PLAYLISTS (WITHIN PYRADIO) +Managing Playlists (within Pyradio) -Once \fBpyradio\fR has been loaded, one can perform a series of actions on the current playlist and set of playlists saved in its configuration directory. +Once \fBPyRadio\fR has been loaded, one can perform a series of actions on the current playlist and set of playlists saved in its configuration directory. Currently, the following actions are available: @@ -275,36 +275,36 @@ Pressing "\fIa\fR" or "\fIA\fR" will enable you to add a new station (either bel If you just want to change the encoding of the selected station, just press "\fIE\fR". If the station is currently playing, playback will be restarted so that the encoding's change takes effect (hopefully correctly displaying the station/song title). -Then, when this is done, you can either save the modified playlist, by pressing "\fIs\fR", or reload the playlist from disk, by pressing "\fIR\fR". A modified playlist will \fIautomatically\fR be saved when \fBpyradio\fR exits (or Ctrl-C is pressed). +Then, when this is done, you can either save the modified playlist, by pressing "\fIs\fR", or reload the playlist from disk, by pressing "\fIR\fR". A modified playlist will \fIautomatically\fR be saved when \fBPyRadio\fR exits (or Ctrl-C is pressed). One thing you may also want to do is remove a station from a playlist, e.g. when found that it not longer works. You can do that by pressing "\fIDEL\fR" or "\fIx\fR". -Finally, opening another playlist is also possible. Just press "\fIo\fR" and you will be presented with a list of saved playists to choose from. These playlists must be saved beforehand in \fBpyradio\fR's configuration directory. +Finally, opening another playlist is also possible. Just press "\fIo\fR" and you will be presented with a list of saved playists to choose from. These playlists must be saved beforehand in \fBPyRadio\fR's configuration directory. -While executing any of the previous actions, you may get confirmation messages (when opening a playlist while the current one is modified but not saved, for example) or error messages (when an action fails). Just follow the on screen information, keeping in mind that a capital letter as an answer will save this answer in \fBpyradio\fR's configuration file for future reference. +While executing any of the previous actions, you may get confirmation messages (when opening a playlist while the current one is modified but not saved, for example) or error messages (when an action fails). Just follow the on screen information, keeping in mind that a capital letter as an answer will save this answer in \fBPyRadio\fR's configuration file for future reference. .PP .B -MANAGING “FOREIGN” PLAYLISTS +Managing “Foreign” Playlists A playlist that does not reside within the program’s configuration directory is considered a "\fIforeign\fR" playlist. This playlist can only be opened by the \fB-s\fR command line option. -When this happens, \fBpyradio\fR will offer you the choise to copy the playlist in its configuration directory, thus making it available for manipulation within the program. +When this happens, \fBPyRadio\fR will offer you the choise to copy the playlist in its configuration directory, thus making it available for manipulation within the program. If a playlist of the same name already exists in the configuration directory, the "\fIforeign\fR" playlist will be time-stamped. For example, if a "\fIforeign\fR" playlist is named "\fIstations.csv\fR", it will be named "\fI2019-01-11_13-35-47_stations.csv\fR" (provided that the action was taked on January 11, 2019 at 13:35:47). .PP .B -PLAYLIST HISTORY +Playlist History -\fBpyradio\fR will keep a history of all the playlists opened (within a given session), so that navigating between them is made easy. +\fBPyRadio\fR will keep a history of all the playlists opened (within a given session), so that navigating between them is made easy. In order to go back to the previous playlist, the user just has to press "\fI\\\\\fR" (double backslash). To get to the first playlist "\fI\\]\fR" (backslash - closing square bracket) can be used. Going forward in history is not supported. -.SH SEARCH FUNCTION +.SH Search Function On any window presenting a list of items (stations, playlists, themes) a \fBsearch function\fR is available by pressing "\fI/\fR". @@ -314,9 +314,9 @@ One can always get help by pressing the "\fI?\fR" key. After a search term has been successfully found (search is case insensitive), next occurrence can be obtained using the "\fIn\fR" key and previous occurrence can be obtained using the "\fIN\fR" key. -.SH LINE EDITOR +.SH Line Editor -\fBpyradio\fR "\fISearch function\fR" and "\fIStation editor\fR" use a \fILine editor\fR to permit typing and editing stations' data. +\fBPyRadio\fR "\fISearch function\fR" and "\fIStation editor\fR" use a \fILine editor\fR to permit typing and editing stations' data. The \fILine editor\fR works both on \fBPython 2\fR and \fBPython 3\fR, but does not provide the same functionality for both versions: @@ -350,7 +350,7 @@ This mode is enabled by pressing "\fB\\p\fR" and gets automatically disabled whe This mode is designed to directly accept the "\fI?\fR" and "\fI\\\fR" characters (which are normally used as commands indicators). This makes it possible to easily paste a station's name and URL, especially when the "\fI?\fR" and "\fI\\\fR" characters exist in them; it is very common to have them in URLs. .PP -\fBCJK CHARACTERS SUPPORT\fR +\fBCJK Characters Support\fR The \fILine editor\fR supports the insertion of \fICJK Unified Ideographs [1]\fR, as described on \fICJK Unified Ideographs (Unicode block) [2]\fR, also known as URO, abbreviation of Unified Repertoire and Ordering. These characters, although encoded as a single code-poin (character), actually take up a 2-character space, when rendered on the terminal. @@ -363,7 +363,7 @@ A depiction of the editor's behavior can be seen at this image: [2] \fIhttps://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)\fR -.SH MOVING STATIONS AROUND +.SH Moving Stations Around Rearranging the order of the stations in the playlist is another feature PyRadio offers. @@ -381,25 +381,25 @@ Type a station number and press \fICtrl-U\fR or \fICtrl-D\fR to move the current Go to the position you want to move a station to, and press "\fIJ\fR". This will tag this position (making it the target of the move). Then go to the station you want to move and press \fICtrl-U\fR or \fICtrl-D\fR to move it there. -.SH SPECIFYING STATIONS' ENCODING +.SH Specifying Stations' Encoding -Normally, stations provide information about their status (including the title of the song playing, which \fBpyradio\fR displays) in Unicode (\fIutf-8\fR encoded). Therefore, \fBpyradio\fR will use \fIutf-8\fR to decode such data, by default. +Normally, stations provide information about their status (including the title of the song playing, which \fBPyRadio\fR displays) in Unicode (\fIutf-8\fR encoded). Therefore, \fBPyRadio\fR will use \fIutf-8\fR to decode such data, by default. -In an ideal world that would be the case for all stations and everything would be ok and as far as \fBpyradio\fR is concerned, songs' titles would be correctly displayed. Unfortunately, this is not the case. +In an ideal world that would be the case for all stations and everything would be ok and as far as \fBPyRadio\fR is concerned, songs' titles would be correctly displayed. Unfortunately, this is not the case. -A lot of stations encode and transmit data in a different encoding (typically the encoding used at the region the come from). The result in \fBpyradio\fR would be that a song title would be incorrectly displayed, not displayed at all, or trying to displaying it might even break \fBpyradio\fR's layout. +A lot of stations encode and transmit data in a different encoding (typically the encoding used at the region the come from). The result in \fBPyRadio\fR would be that a song title would be incorrectly displayed, not displayed at all, or trying to displaying it might even break \fBPyRadio\fR's layout. .IP \fBNote\fR \fIvlc\fR will not work in this case; it presumably tries to decode the said data beforehand, probably using \fIutf-8\fR by default, and when it fails, it provides a "\fI(null)\fR" string, instead of the actual data. So, you'd better not use \fIvlc\fR if such stations are in your playlists. .PP -\fBpyradio\fR addresses this issue by allowing the user to declare the encoding to use either in a station by station mode or globally. +\fBPyRadio\fR addresses this issue by allowing the user to declare the encoding to use either in a station by station mode or globally. .PP .B -STATION BY STATION ENCODING DECLARATION +Station By Station Encoding Declaration -As previously stated, a \fBpyradio\fR's playlist can optionally contain a third column (in addition to the station name and station URL columns), which declares the station's encoding. +As previously stated, a \fBPyRadio\fR's playlist can optionally contain a third column (in addition to the station name and station URL columns), which declares the station's encoding. So, when a \fInon-utf-8\fR encoded station is inserted in a playlist, its encoding can also be declared along with its other data. The drawback of this feature is that an encoding must be declared for \fBall stations\fR (so that the \fBCSV\fR file structure remains valid). To put it simple, since one station comprises the third column, all stations must do so as well. @@ -435,22 +435,22 @@ Finally, we insert the new station to the playlist: Station2\fB,\fIStation2_URL\fB,\fIiso-8859-7 .IP \fBNote\fR -Using the \fB-a\fR command line option will save you all this trouble, as it will automatically take care of creating a valid \fBCSV\fR file. Alternatively, you can change the selected station's encoding by pressing "\fIE\fR" while in \fBpyradio\fR. +Using the \fB-a\fR command line option will save you all this trouble, as it will automatically take care of creating a valid \fBCSV\fR file. Alternatively, you can change the selected station's encoding by pressing "\fIE\fR" while in \fBPyRadio\fR. .PP .B -GLOBAL ENCODING DECLARATION +Global Encoding Declaration -\fBpyradio\fR's configuration file contains the parameter \fBdefault_encoding\fR, which by default is set to \fIutf-8\fR. +\fBPyRadio\fR's configuration file contains the parameter \fBdefault_encoding\fR, which by default is set to \fIutf-8\fR. -Setting this parameter to a different encoding, will permit \fBpyradio\fR to successfully decode such stations. +Setting this parameter to a different encoding, will permit \fBPyRadio\fR to successfully decode such stations. This would be useful in the case where most of your stations do not use \fIutf-8\fR. Instead of editing the playlist and add the encoding to each and every affected station, you just set it globally. .PP .B -FINDING THE RIGHT ENCODING +Finding The Right Encoding A valid encoding list can be found at: @@ -458,9 +458,9 @@ A valid encoding list can be found at: \fRreplacing \fI2.7\fR with specific version: \fI3.0\fR up to current python version. -.SH PLAYER DETECTION / SELECTION +.SH Player Detection / Selection .PP -\fBpyradio\fR is basically built around the existence of a valid media player it can use. Thus, it will auto detect the existence of its supported players upon its execution. +\fBPyRadio\fR is basically built around the existence of a valid media player it can use. Thus, it will auto detect the existence of its supported players upon its execution. .PP Currently, it supports \fIMPV\fR, \fIMPlayer\fR and \fIVLC\fR, and it will look for them in that order. If none of them is found, the program will terminate with an error. .PP @@ -469,21 +469,21 @@ Users can alter this default behavior by using the \fB-u\fR command line option. Example: .IP \fBpyradio\ -u\ vlc -will instruct \fBpyradio\fR to use VLC; if it is not found, the program will terminate with an error. +will instruct \fBPyRadio\fR to use VLC; if it is not found, the program will terminate with an error. .IP \fBpyradio\ -u\ vlc,mplayer,mpv -will instruct \fBpyradio\fR to look for VLC, then MPlayer and finaly for MPV and use whichever it finds first; if none is found, the program will terminate with an error. +will instruct \fBPyRadio\fR to look for VLC, then MPlayer and finaly for MPV and use whichever it finds first; if none is found, the program will terminate with an error. .IP \fBNote\fR -The default player to use can also be set in \fBpyradio\fR’s configuration file, parameter \fIplayer\fR (default value is \fImpv, mplayer, vlc\fR). +The default player to use can also be set in \fBPyRadio\fR’s configuration file, parameter \fIplayer\fR (default value is \fImpv, mplayer, vlc\fR). .SH -\fBEXTRA PLAYER PARAMETERS\fR +\fBExtra Player Parameters\fR All three supported players can accept a significant number of "\fIcommand line parameters\fR", which are well documented and accessible through man pages (on linux and macOs) or the documentation (on Windows). -\fBpyradio\fR uses some of these parameters in order to execute and communicate with the players. In particular, the following parameters are in use \fBby default\fR: +\fBPyRadio\fR uses some of these parameters in order to execute and communicate with the players. In particular, the following parameters are in use \fBby default\fR: .RS 5 .IP \fBPlayer\fR 10 @@ -500,11 +500,11 @@ All three supported players can accept a significant number of "\fIcommand line The user should not use or change the above player parameters. Failing to do so, may render the player \fBunusable\fR. .P -\fBpyradio\fR provides a way for the user to add extra parameters to the player, either by a command line parameter, or the "\fIConfiguration Window\fR" (under "\fIPlayer:\fR"). +\fBPyRadio\fR provides a way for the user to add extra parameters to the player, either by a command line parameter, or the "\fIConfiguration Window\fR" (under "\fIPlayer:\fR"). This way, 10 sets of parameters can be inserted and made available for selection. -\fBUSING THE COMMAND LINE\fR +\fBUsing The Command Line\fR When the command line parameter (\fB-epp\fR or \fB-extra_player_parameters\fR) is used, the parameters specified must be of a specific format, and will be added to the list of parameters and made default for the player for the current session. @@ -528,7 +528,7 @@ Example: .IP \fBNote\fR -When a parameter is passed to \fImpv\fR or \fImplayer\fR, \fBpyradio\fR will use the default player profile (called \fBpyradio\fR). +When a parameter is passed to \fImpv\fR or \fImplayer\fR, \fBPyRadio\fR will use the default player profile (called \fBPyRadio\fR). .P For \fImpv\fR and \fImplayer\fR a profile can be specified (\fIvlc\fR does not support profiles). In this case the format of the \fBparameters\fR part of the command line is: \fI[\fBprofile\fR:\fBprofile_name\fI]\fR. @@ -550,9 +550,9 @@ Example: .P -\fBUSING THE CONFIGURATION WINDOW\fR +\fBUsing The Configuration Window\fR -When the user uses the configuration window (shown in the following image), he is presented with an interface which will permit him to select the player to use with \fBpyradio\fR and edit its extra parameters. +When the user uses the configuration window (shown in the following image), he is presented with an interface which will permit him to select the player to use with \fBPyRadio\fR and edit its extra parameters. [pyradio-player-selection.jpg](https://members.hellug.gr/sng/pyradio/pyradio-player-selection.jpg) @@ -560,19 +560,19 @@ Each of the supported players can have up to 11 sets of extra parameters (the fi The user can add ("\fBa\fR") a new parameter, edit ("\fBe\fR") an existing set and delete ("\fBx\fR" or "\fBDEL\fR") one. -\fBCHANGING PARAMETERS' SET\fR +\fBChanging Parameters' Set\fR .P When all desired parameter sets are already defined, using the \fB-ap\fR (\fB--active-player-param-id\fR) command line parameter can activate the set that corresponds to the number specified. The number to use for any given set can be retrieved using the \fB-lp\fR (\fB--list-player-parameters\fR) command line parameter. -While \fBpyradio\fR is running, the user can change the parameters' set used by the player using the "\fIPlayer Extra Parameters\fR" window, by pressing "\fBZ\fR". +While \fBPyRadio\fR is running, the user can change the parameters' set used by the player using the "\fIPlayer Extra Parameters\fR" window, by pressing "\fBZ\fR". If playback is on, changing the player's parameters will make the player restart the playback so that the new parameters is used. .IP \fBNote\fR -Any changes made this way will not be saved but will be in effect until \fBpyradio\fR terminates. +Any changes made this way will not be saved but will be in effect until \fBPyRadio\fR terminates. -.SH PLAYER CONNECTION PROTOCOL +.SH Player Connection Protocol Most radio stations use plain old http protocol to broadcast, but some of them use https. @@ -580,7 +580,7 @@ Experience has shown that playing a \fBhttps\fR radio station depends on the com If such a station fails to play, one might as well try to use \fBhttp\fR protocol to connect to it. -\fBpyradio\fR provides a way to instruct the player used to do so; the "\fIForce http connections\fR" configuration parameter. If it is \fIFalse\fR (the default), the player will use whatever protocol the station proposes (either \fBhttp\fR or \fBhttps\fR). When changed to \fBTrue\fR, all connections will use the \fBhttp\fR protocol. +\fBPyRadio\fR provides a way to instruct the player used to do so; the "\fIForce http connections\fR" configuration parameter. If it is \fIFalse\fR (the default), the player will use whatever protocol the station proposes (either \fBhttp\fR or \fBhttps\fR). When changed to \fBTrue\fR, all connections will use the \fBhttp\fR protocol. When the selected player is initialized (at program startup), it reads this configuration parameter and acts accordingly. @@ -589,11 +589,11 @@ If the parameter has to be changed mid-session (without restarting the program), .IP \fBNote\fR Changes made using the "\fIConnection Type\fR" window are not stored; next time the program is executed, it will use whatever value the configuration parameter holds. Furthermore, changing the configuration stored value, will not affect the "working" value of the parameter. -.SH PLAYER DEFAULT VOLUME LEVEL +.SH Player Default Volume Level .PP \fIMPV\fR and \fIMPlayer\fR, when started, use their saved (or default) volume level to play any multimedia content. Fortunately, this is not the case with \fIVLC\fR. .PP -This introduces a problem to \fBpyradio\fR: every time a user plays a station (i.e restarts playback), even though he may have already set the volume to a desired level, the playback starts at the player's default level. +This introduces a problem to \fBPyRadio\fR: every time a user plays a station (i.e restarts playback), even though he may have already set the volume to a desired level, the playback starts at the player's default level. .PP The way to come around it, is to save the desired volume level in a way that it will be used by the player whenever it is restarted. .PP @@ -603,7 +603,7 @@ This is done by typing "\fIv\fR" right after setting a desired volume level. .PP \fIMPV\fR uses profiles to customize its behavior. .PP -\fBpyradio\fR defines a profile called "\fI[pyradio]\fR" in MPV's configuration file (e.g. \fI~/.config/mpv/mpv.conf\fR). This profile will be used every time playback is started. +\fBPyRadio\fR defines a profile called "\fI[pyradio]\fR" in MPV's configuration file (e.g. \fI~/.config/mpv/mpv.conf\fR). This profile will be used every time playback is started. .PP Example: @@ -620,7 +620,7 @@ volume=50 .PP \fIMPlayer\fR uses profiles to customize its behavior as well. .PP -\fBpyradio\fR defines a profile called "\fI[pyradio]\fR" in MPV's configuration file (e.g. \fI~/.mplayer/config\fR). This profile will be used every time playback is started. +\fBPyRadio\fR defines a profile called "\fI[pyradio]\fR" in MPV's configuration file (e.g. \fI~/.mplayer/config\fR). This profile will be used every time playback is started. .PP Example: @@ -641,9 +641,9 @@ volstep=1 volume=50 .IP \fBNote: -Starting with \fBpyradioR v. 0.8.9\fR, \fImplayer\fR's default profile will use its internal mixer to adjust its volume; this is accompliced using the "\fIsoftvol=1\fR" and "\fIsoftvol-max=300\fR" lines above. The user may choose to remove these lines from the config (to activate system-wide volume adjustment) or add them to the config (in case the profile was created by an older \fBpyradio\fR version). +Starting with \fBpyradioR v. 0.8.9\fR, \fImplayer\fR's default profile will use its internal mixer to adjust its volume; this is accompliced using the "\fIsoftvol=1\fR" and "\fIsoftvol-max=300\fR" lines above. The user may choose to remove these lines from the config (to activate system-wide volume adjustment) or add them to the config (in case the profile was created by an older \fBPyRadio\fR version). -.SH DISPLAYING STATION INFO +.SH Displaying Station Info When a connection to a radio station has been established, the station starts sending audio data for the user to listen to. @@ -655,16 +655,16 @@ The station actually also sends identification data, audio format data, notifica Now, not all stations send the whole set of data; most send their name, website, genre and bitrate, for example, but some may ommit the website or the genre. -\fBpyradio\fR can receive, decode and display this data, and even help the user to identify an unknown station. This is the way to do it: +\fBPyRadio\fR can receive, decode and display this data, and even help the user to identify an unknown station. This is the way to do it: After a connection to a station has been established (after playback has started), just press "\fIi\fR" to display the station's info. The window that appears includes the "\fIPlaylist Name\fR" (the station name we have in the playlist) and the "\fIReported Name\fR" (the name the station transmitted to us) among other fields (an example can be seen here: \fIhttps://members.hellug.gr/sng/pyradio/pyradio-station-info.jpg\fR . If these two names are not identical, the user can press "\fIr\fR" to rename the station in the playlist using the "\fIReported Name\fR". This way an unknown station (when only the URL is known) can be correctly identified (after being inserted in a playlist with a dummy station name). -.SH COPYING AND PASTING - REGISTERS +.SH Copying And Pasting - Registers -\fBpyradio\fR takes the concept of \fBregisters\fR from i\fIvim\fR (\fIhttps://www.vim.org\fR), and adapts their function to its own needs. So this is how it all works. +\fBPyRadio\fR takes the concept of \fBregisters\fR from i\fIvim\fR (\fIhttps://www.vim.org\fR), and adapts their function to its own needs. So this is how it all works. There are 36 named registers (name is \fBa-z\fR, \fB0-9\fR) and one unnamed register. @@ -716,13 +716,13 @@ To \fBpaste\fR the \fIunnamed\fR register to a playlist or register, one would p .RE -.SH PYRADIO THEMES +.SH Pyradio Themes .PP -\fBpyradio\fR comes with 6 preconfigured (hard coded) themes: +\fBPyRadio\fR comes with 6 preconfigured (hard coded) themes: .IP \fBdark\fR\ (8\ color\ theme) -This is the appearance \fBpyradio\fR has always had. Enabled by default. +This is the appearance \fBPyRadio\fR has always had. Enabled by default. .IP \fBlight\fR\ (8\ color\ theme) A theme for light terminal background settings. .IP \fBdark_16_colors\fR\ (16\ color\ theme) @@ -743,7 +743,7 @@ The visual result of an applied theme greatly depends on the terminal settings ( Pressing "\fBt\fR" will bring up the \fITheme selection window\fR, which can be used to activate a theme and set the default one. .IP \fBNote\fR -Themes that use more colors than those supported by the terminal in use, will not be present in the \fITheme selection window\fR. Furthermore, if a such a theme is set as default (or requested using the "\fB-t\fR" command line option), \fBpyradio\fR will fall-back to the "\fBdark\fR" theme, (or the "\fBlight\fR" theme, if the terminal supports 8 colors and default theme is set to "\fIlight_16_colors\fR"), and will display a relevant messages at program startup. +Themes that use more colors than those supported by the terminal in use, will not be present in the \fITheme selection window\fR. Furthermore, if a such a theme is set as default (or requested using the "\fB-t\fR" command line option), \fBPyRadio\fR will fall-back to the "\fBdark\fR" theme, (or the "\fBlight\fR" theme, if the terminal supports 8 colors and default theme is set to "\fIlight_16_colors\fR"), and will display a relevant messages at program startup. .PP The \fITheme selection window\fR will remain open after activating a theme, so that the user can inspect the visual result and easily change it, if desired. Then, when he is satisfied with the activated theme, the window will have to be manually closed (by pressing "\fBq\fR" or any other relevant key - pressing "\fB?\fR" will bring up its help). @@ -751,25 +751,25 @@ The \fITheme selection window\fR will remain open after activating a theme, so t The use of custom themes and theme editing is not implemented yet; theses are features for future releases. .PP -\fBUSING TRANSPARENCY\fR +\fBUsing Transparency\fR -\fBpyradio\fR themes are able to be used with a transparent background. +\fBPyRadio\fR themes are able to be used with a transparent background. -Pressing "\fBT\fR" will toggle the transparency setting (it is \fIoff\fR by default) and save this state in \fBpyradio\fR's configuration file. +Pressing "\fBT\fR" will toggle the transparency setting (it is \fIoff\fR by default) and save this state in \fBPyRadio\fR's configuration file. -Setting transparency on, will actually force \fBpyradio\fR to not use its own background color, effectively making it to display whatever is on the terminal (color/picture/transparency). The visual result depends on terminal settings and whether a compositor is running. +Setting transparency on, will actually force \fBPyRadio\fR to not use its own background color, effectively making it to display whatever is on the terminal (color/picture/transparency). The visual result depends on terminal settings and whether a compositor is running. When the \fITheme selection window\fR is visible, a "\fI[T]\fR" string displayed at its bottom right corner will indicate that transparency is \fIon\fR. -.SH MOUSE SUPPORT +.SH Mouse Support -Being a console application, \fBpyradio\fR was never intended to work with a mouse. +Being a console application, \fBPyRadio\fR was never intended to work with a mouse. Furthermore, when using the mouse on a console application, the result is highly dependent on the terminal used and the way it implements mouse support. -Having said that, and since the question of using the mouse with \fBpyradio\fR has been risen, basic mouse support has been implemented; starting, stopping and muting the player, scrolling within the playlist and adjusting the player's volume is now possible using the mouse. +Having said that, and since the question of using the mouse with \fBPyRadio\fR has been risen, basic mouse support has been implemented; starting, stopping and muting the player, scrolling within the playlist and adjusting the player's volume is now possible using the mouse. -All one has to do is enable mouse support in the "\fIConfig Window\fR" (mouse support is disabled by default) and restart \fBpyradio\fR for the change to take effect. +All one has to do is enable mouse support in the "\fIConfig Window\fR" (mouse support is disabled by default) and restart \fBPyRadio\fR for the change to take effect. Then, the mouse can be used as follows: @@ -792,33 +792,47 @@ Adjust volume (does not work with all terminals) .RE -.SH SESSION LOCKING +.SH Online Radio Directory Services -\fBpyradio\fR uses session locking, which actually means that only the first instance executed within a given session will be able to write to the configuration file. +\fBPyRadio\fR supports the following \fIOnline Radio Directory services\fR: + +.IP \fBRadioBrowser\ -\ \fIhttps://www.radio-browser.info/\fR + +This is a community driven effort (like wikipedia) with the aim of collecting as many internet radio and TV stations as possible. + +For more information please refer to the relevant man page: \fIpyradio_rb(1)\fR. + +.PP +To access supported services, just press "\fIO\fR" at the program's main window. + + +.SH Session Locking + +\fBPyRadio\fR uses session locking, which actually means that only the first instance executed within a given session will be able to write to the configuration file. Subsequent instances will be "\fIlocked\fR. This means that the user can still play stations, load and edit playlists, load and test themes, but any changes will \fBnot\fR be recorded in the configuration file. \fBSession unlocking\fR -If for any reason \fBpyradio\fR always starts in \fIlocked mode\fR, one can \fBunclock\fR the session, using the "\fB--unlock\fR" command line paremater. +If for any reason \fBPyRadio\fR always starts in \fIlocked mode\fR, one can \fBunclock\fR the session, using the "\fB--unlock\fR" command line paremater. -.SH UPDATE NOTIFICATION +.SH Update Notification .PP -\fBpyradio\fR will periodically (once every 10 days) check whether a new version has been released. +\fBPyRadio\fR will periodically (once every 10 days) check whether a new version has been released. If so, a notification message will be displayed, informing the user about it and asking to proceed with updating the program (provided this is not a distribution package). -.SH CLEANING UP +.SH Cleaning Up -\fBpyradio\fR will uninstall all previously installed versions when updated (using the \fB-U\fR command line parameter), so no extra steps are needed any more to house keep your system. +\fBPyRadio\fR will uninstall all previously installed versions when updated (using the \fB-U\fR command line parameter), so no extra steps are needed any more to house keep your system. -.SH DEBUG MODE +.SH Debug Mode .PP -Adding the \fB-d\fR option to the command line will instruct \fBpyradio\fR to enter \fBDebug mode\fR, which means that it will print debug messages to a file. This file will always reside in the user's home directory and will be named \fIpyradio.log\fR. +Adding the \fB-d\fR option to the command line will instruct \fBPyRadio\fR to enter \fBDebug mode\fR, which means that it will print debug messages to a file. This file will always reside in the user's home directory and will be named \fIpyradio.log\fR. .PP In case of a bug or a glitch, please include this file to the issue you will open in github at \<\fIhttps://github.com/coderholic/pyradio/issues\fR\> -.SH REPORTING BUGS +.SH Reporting Bugs .PP When a bug is found, please do report it by opening an issue at github at \<\fIhttps://github.com/coderholic/pyradio/issues\fR\>, as already stated above. @@ -839,10 +853,10 @@ Then try to reproduce the bug and exit pyradio. Finally, include the file produced in your report. -.SH ACKNOWLEGEMENT +.SH Acknowlegement .PP -\fBpyradio\fR uses code from the following projects: +\fBPyRadio\fR uses code from the following projects: .RS 5 @@ -855,7 +869,7 @@ Finally, include the file produced in your report. .IP 3. 3 \fBVifm\fR (\fIhttps://vifm.info/\fR) - A file manager with curses interface, which provides a Vi[m]-like environment. -.SH FILES +.SH Files .PP .I /usr/share/doc/pyradio/README.md @@ -871,7 +885,7 @@ Finally, include the file produced in your report. On \fBMac OS\fR, these file may be installed in \fI/usr/local/share/doc/pyradio\fR, depending on whether or not \fBSIP\fR is enabled. -.SH AUTHORS +.SH Authors .PP \fBBen Dowling\fR\ \<\fIhttps://github.com/coderholic\fR\>,\ (Origianl\ author) .PP @@ -886,3 +900,6 @@ On \fBMac OS\fR, these file may be installed in \fI/usr/local/share/doc/pyradio\ You can see a complete list of contributors at https://github.com/coderholic/pyradio/graphs/contributors +.SH See also + + pyradio_rb(1) diff --git a/pyradio/__init__.py b/pyradio/__init__.py index 0a07f9bd..05cbfb9f 100644 --- a/pyradio/__init__.py +++ b/pyradio/__init__.py @@ -1,6 +1,6 @@ " pyradio -- Console radio player. " -version_info = (0, 8, 9, 5) +version_info = (0, 8, 9, 6) # Application state: # New stable version: '' diff --git a/pyradio/browser.py b/pyradio/browser.py index fd704a00..8d566dd3 100644 --- a/pyradio/browser.py +++ b/pyradio/browser.py @@ -7,6 +7,7 @@ from copy import deepcopy import random import json +from os import path import collections from operator import itemgetter try: @@ -262,11 +263,13 @@ def vote(self, a_station): pass -class RadioBrowserInfo(PyRadioStationsBrowser): +class RadioBrowser(PyRadioStationsBrowser): BASE_URL = 'api.radio-browser.info' TITLE = 'RadioBrowser ' + browser_config = None + _headers = {'User-Agent': 'PyRadio/dev', 'Content-Type': 'application/json'} @@ -303,14 +306,15 @@ class RadioBrowserInfo(PyRadioStationsBrowser): search_by = _old_search_by = None _default_max_number_of_results = 100 + _default_server = '' keyboard_handler = None ''' _search_history_index - current item in this browser - corresponds to search window _history_id - _search_default_history_index - autoload item in this browser - corresponds to search window _default_history_id + _default_search_history_index - autoload item in this browser - corresponds to search window _default_history_id ''' _search_history_index = 1 - _search_default_history_index = 1 + _default_search_history_index = 1 def __init__(self, config, @@ -331,6 +335,8 @@ def __init__(self, ''' self.first_search = True self._cnf = config + self.browser_config = RadioBrowserConfig(self._cnf.stations_dir) + if session: self._session = session else: @@ -344,48 +350,9 @@ def __init__(self, def initialize(self): - self._dns_info = RadioBrowserInfoDns() - self._server = self._dns_info.give_me_a_server_url() - if logger.isEnabledFor(logging.INFO): - if self._server: - logger.info('random server is ' + self._server) - else: - logger.info('No server URL found!!!') - if self._server: - self._get_title() - - self._search_history.append({ - 'type': '', - 'term': str(self._default_max_number_of_results), - 'post_data': {} - }) - - self._search_history.append({ - 'type': 'topvote', - 'term': '100', - 'post_data': {'reverse': 'true'} - }) - - # self._search_history.append({ - # 'type': 'bytag', - # 'term': 'big band', - # 'post_data': {'order': 'votes', 'reverse': 'true'}, - # }) - - # self._search_history.append({ - # 'type': 'search', - # 'term': '', - # 'post_data': {'name': 'Jazz', 'codec': 'mp3', 'order': 'clickcount', 'reverse': 'true', 'limit': '40'}, - # }) - - # self._search_history.append({ - # 'type': 'search', - # 'term': '', - # 'post_data': {'tagExact': 'true', 'tag': 'blues', 'codec': 'mp3', 'order': 'clickcount', 'reverse': 'true', 'limit': '140'}, - # }) - self._search_history_index = 1 - return True - return False + self._dns_info = RadioBrowserDns() + + return self.read_config() @property def server(self): @@ -421,6 +388,14 @@ def stations(self, playlist_format=1): ret.append([n['name'], n['url'], enc, '']) return ret + def save_config(self): + ''' just an interface to config class save_config + ''' + return self.browser_config.save_config(self._search_history, + self._default_search_history_index, + self._default_server, + self._default_max_number_of_results) + def url(self, id_in_list): ''' Get a station's url using resolved_url @@ -594,11 +569,12 @@ def search(self, go_back_in_history=True): if not 'hidebroken' not in post_data.keys(): post_data['hidebroken'] = True - if logger.isEnabledFor(logging.DEBUG): - logger.debug(' == history = {}'.format(self._search_history[self._search_history_index])) - logger.debug(' == url = "{}"'.format(url)) - logger.debug(' == headers = "{}"'.format(self._headers)) - logger.debug(' == post_data = "{}"'.format(post_data)) + if logger.isEnabledFor(logging.INFO): + logger.info('>>> RadioBrowser Query:') + logger.info(' search term = {}'.format(self._search_history[self._search_history_index])) + logger.info(' url = "{}"'.format(url)) + logger.info(' headers = "{}"'.format(self._headers)) + logger.info(' post_data = "{}"'.format(post_data)) ''' keep server results here ''' new_raw_stations = [] @@ -629,7 +605,8 @@ def _get_search_elements(self, a_search): values from a search dict. To be used with the sort function ''' - logger.error('DE search in function _get_search_elements is\n\t"{}"'.format(a_search)) + if logger.isEnabledFor(logging.DEBUG): + logger.debug('_get_search_elements() :search term is\n\t"{}"'.format(a_search)) a_term = a_search['term'] p_data = a_search['post_data'] self.search_by = None @@ -640,7 +617,8 @@ def _get_search_elements(self, a_search): if 'reverse' in a_search['post_data']: self.reverse = True if a_search['post_data']['reverse'] == 'true' else False - logger.error('DE search by was "{}"'.format(self.search_by)) + if logger.isEnabledFor(logging.DEBUG): + logger.debug('searching by was: "{}"'.format(self.search_by)) if self.search_by is None: a_type = a_search['type'] if a_type == 'byname': @@ -667,13 +645,16 @@ def _get_search_elements(self, a_search): if p_data: if 'name' in p_data.keys(): self.search_by = 'name' - logger.error('DE search by is name (default)') + if logger.isEnabledFor(logging.DEBUG): + logger.error('p_data searching by: "name" (default)') if self.search_by is None: self.search_by = 'name' - logger.error('DE search by is name (default)') + if logger.isEnabledFor(logging.DEBUG): + logger.error('forced searching by: "name" (default)') - logger.error('DE search by is "{}"'.format(self.search_by)) + if logger.isEnabledFor(logging.DEBUG): + logger.debug('searching by: "{}"'.format(self.search_by)) def get_next(self, search_term, start=0, stop=None): if search_term: @@ -1140,7 +1121,10 @@ def get_columns_separators(self, def get_history_from_search(self): if self._search_win: - self._search_history_index, history = self._search_win.get_history() + self._search_history_index, self._default_search_history_index, history = self._search_win.get_history() + logger.error('DE search_history_index = {}'.format(self._search_history_index)) + logger.error('DE search_default_history_index = {}'.format(self._default_search_history_index)) + logger.error('DE history = {}'.format(history)) self._search_history = deepcopy(history) def get_internal_header(self, pad, width): @@ -1190,7 +1174,7 @@ def get_internal_header(self, pad, width): def select_servers(self): if self._server_selection_window is None: - self._server_selection_window = RadioBrowserInfoServersSelect( + self._server_selection_window = RadioBrowserServersSelect( self.parent, self._dns_info.server_urls, self._server) else: self._server_selection_window.set_parent(self.parent) @@ -1205,15 +1189,45 @@ def sort(self): self._get_search_elements( self._search_history[self._search_history_index] ) - self._sort = RadioBrowserInfoSort( + self._sort = RadioBrowserSort( parent=self.parent, search_by=self.search_by ) self.keyboard_handler = self._sort self._sort.show() + def read_config(self): + self.browser_config.read_config() + random_server = self._dns_info.give_me_a_server_url() + # logger.error('DE random_server = {}'.format(random_server)) + if random_server is None: + if logger.isEnabledFor(logging.ERROR): + logger.error('RadioBrowser: no server is reachable!') + return False + + self._default_max_number_of_results = int(self.browser_config.limit) + self._default_search_history_index = self._search_history_index = self.browser_config.default + self._search_history = self.browser_config.terms + self._default_server = self.browser_config.server + if self._default_server: + self._server = self._default_server + if logger.isEnabledFor(logging.INFO): + logger.info('RadioBrowser: server is set by user: ' + self._server) + else: + self._server = random_server + if logger.isEnabledFor(logging.INFO): + logger.info('RadioBrowser: using random server: ' + self._server) + if logger.isEnabledFor(logging.INFO): + logger.info('RadioBrowser: result limit = {}'.format(self._default_max_number_of_results)) + logger.info('RadioBrowser: default search term = {}'.format(self._default_search_history_index)) + logger.info('RadioBrowser: search history') + for i, n in enumerate(self._search_history): + logger.info(' {0}: {1}'.format(i, n)) + self._get_title() + return True + def keypress(self, char): - ''' RadioBrowserInfo keypress + ''' RadioBrowser keypress Returns: -1: Cancel @@ -1251,29 +1265,161 @@ def keypress(self, char): if ret == 0: self._server = self._server_selection_window.server if logger.isEnabledFor(logging.INFO): - logger.info('user selected server is ' + self._server) + logger.info('RadioBrowser: user selected server is ' + self._server) self._get_title() return ret def do_search(self, parent=None, init=False): if init: - self._search_win = RadioBrowserInfoSearchWindow( + self._search_win = RadioBrowserSearchWindow( parent=parent, + config=self.browser_config, limit=self._default_max_number_of_results, init=init ) self._search_win.set_search_history( - self._search_default_history_index, + self._default_search_history_index, self._search_history_index, self._search_history, init) self.keyboard_handler = self._search_win self._search_win.show() -class RadioBrowserInfoSearchWindow(object): + def redisplay_search(self): + self.keyboard_handler = self._search_win + self._search_win.show() + +class RadioBrowserConfig(object): + server = '' + default = 1 + limit = '100' + terms = [] + ditry = False + + def __init__(self, stations_dir): + self.config_file = path.join(stations_dir, 'radio-browser-config') + + def read_config(self): + self.terms = [{ 'type': '', + 'term': '100', + 'post_data': {} + }] + lines = [] + term_str = [] + try: + with open(self.config_file, 'r') as cfgfile: + lines = [line.strip() for line in cfgfile if line.strip() and not line.startswith('#') ] + + except: + self.terms.append({ + 'type': 'topvote', + 'term': '100', + 'post_data': {'reverse': 'true'} + }) + if logger.isEnabledFor(logging.DEBUG): + logger.debug('RadioBrowser: error reading config, reverting to defaults') + return False + + for line in lines: + if '=' in line: + # logger.error('DE line = "' + line + '"') + sp = line.split('=') + for n in range(0, len(sp)): + sp[n] = sp[n].strip() + # logger.error('DE sp = {}'.format(sp)) + if sp[1]: + if sp[0] == 'DEFAULT_SERVER': + self.server = sp[1] + elif sp[0] == 'DEFAULT_LIMIT': + self.limit = sp[1] + try: + x = int(self.limit) + except: + self.limit = '100' + elif sp[0] == 'SEARCH_TERM': + term_str.append(sp[1]) + + if term_str: + for n in range(0, len(term_str)): + if term_str[n].startswith('*'): + term_str[n] = term_str[n][1:] + self.default = n + 1 + + term_str[n] = term_str[n].replace("'", '"') + # logger.error('term {0} = "{1}"'.format(n, term_str[n])) + try: + self.terms.append(json.loads(term_str[n])) + except: + if logger.isEnabledFor(logging.ERROR): + logger.error('RadioBrowser: error inserting search term {}'.format(n)) + else: + if logger.isEnabledFor(logging.DEBUG): + logger.debug('RadioBrowser: no search terms found, reverting to defaults') + self.terms.append({ + 'type': 'topvote', + 'term': '100', + 'post_data': {'reverse': 'true'} + }) + return False + + + # logger.error('DE limit = ' + self.limit) + # logger.error('DE server = ' + self.server) + self.terms[0]['term'] = self.limit + # logger.error('DE default = {}'.format(self.default)) + # logger.error('DE terms = {}'.format(self.terms)) + return True + + + def save_config(self, + search_history, + search_default_history_index, + default_server, + default_max_number_of_results): + logger.error('DE saving history config') + txt = '''######################################## +# RadioBrowser config file for PyRadio # +######################################## +# +# Default server +# Default: empty string (use random server) +DEFAULT_SERVER = ''' + + txt += default_server + + txt += ''' + +# Default maximum number of returned results +# for any query to a RadioBrowser saerver +# Default value: 100 +DEFAULT_LIMIT = ''' + + txt += str(default_max_number_of_results) + + txt += ''' + +# List of "search terms" (queries) +# Default = "{'type': 'topvote', 'term': '100', 'post_data': {'reverse': 'true'}}" +''' + for n in range(1, len(search_history)): + asterisk = '*' if n == search_default_history_index else '' + txt += 'SEARCH_TERM = ' + asterisk + str(search_history[n]) + '\n' + + try: + with open(self.config_file, 'w') as cfgfile: + cfgfile.write(txt) + except: + logger.error('===== ERROR =====') + return False + self.dirty = False + return True + +class RadioBrowserSearchWindow(object): NUMBER_OF_WIDGETS_AFTER_SEARCH_SECTION = 3 + _cnf = None + search_by_items = ( 'Votes', 'Clicks', @@ -1329,24 +1475,26 @@ class RadioBrowserInfoSearchWindow(object): ''' columns widget ids ''' _columns_id = [] - ''' check boxrs ids to enable/disable columns widgets ''' - _check_box_to_enable_widgets = (0, 4) + ''' checkboxes ids to enable/disable columns widgets ''' + _checkbox_to_enable_widgets = (0, 4) _default_limit = 100 ''' _selected_history_id : current id in search window _history_id : current id (active in browser) - corresponds in browser to _search_history_index - _default_history_id : default id (autoload for service) - corresponds in browser to _search_default_history_index + _default_history_id : default id (autoload for service) - corresponds in browser to _default_search_history_index ''' _history_id = _selected_history_id = _default_history_id = 1 _history = [] def __init__(self, parent, + config, limit=100, init=False ): self._parent = parent + self._cnf = config self._default_limit = limit self._init = init self._too_small = False @@ -1567,7 +1715,7 @@ def _order_to_term(self, ret): ret['post_data']['reverse'] = 'true' def get_history(self): - return self._history_id, self._history + return self._selected_history_id, self._default_history_id, self._history def set_search_history( self, @@ -1832,7 +1980,7 @@ def show(self): self._win.refresh() self._calculate_widgets_yx(Y, X) # logger.error('== 1 widget[{0}].Y = {1}'.format(3, self._widgets[3].Y)) - for n in range(0,5): + for n in range(0, 6): ''' place editors' captions ''' # self._win.addstr( # self.yx[n+1][0], @@ -1841,10 +1989,11 @@ def show(self): # curses.color_pair(5) # ) ''' move exact check boxes ''' - self._widgets[5+n*2].move( - self.yx[n+1][0] + 1, - self.yx[n+1][1] + len(self.captions[n+1]) + 2 - ) + if type(self._widgets[5+n*2]).__name__ != 'DisabledWidget': + self._widgets[5+n*2].move( + self.yx[n+1][0] + 1, + self.yx[n+1][1] + len(self.captions[n+1]) + 2 + ) ''' move line editors ''' self._widgets[6+n*2].move( self._win, @@ -1870,18 +2019,15 @@ def show(self): self._display_all_widgets() def _print_history_legend(self): - self._win.addstr(self.maxY - 2, 2 , 'History item: ') - self._win.addstr('{}'.format(self._selected_history_id), curses.color_pair(4)) - self._win.addstr('/{} '.format(len(self._history)-1)) self._win.addstr(self.maxY - 3, 2, 25 * ' ') if self._selected_history_id == 0: - self._win.addstr(self.maxY - 3, 2, 'Template!!!', curses.color_pair(4)) + self._win.addstr(self.maxY - 3, 2, 'Empty item!!!', curses.color_pair(4)) elif self._selected_history_id == self._history_id: if self._default_history_id == self._history_id: - self._win.addstr(self.maxY - 3, 2, 'Item in Browser, Default', curses.color_pair(4)) + self._win.addstr(self.maxY - 3, 2, 'Last search, Default', curses.color_pair(4)) else: - self._win.addstr(self.maxY - 3, 2, 'Item in Browser', curses.color_pair(4)) + self._win.addstr(self.maxY - 3, 2, 'Last search', curses.color_pair(4)) elif self._selected_history_id == self._default_history_id: self._win.addstr(self.maxY - 3, 2, 'Default item', curses.color_pair(4)) @@ -1889,11 +2035,15 @@ def _print_history_legend(self): thisX = self.maxX - 2 - len(msg) self._win.addstr(self.maxY - 3, thisX, msg) self._carret_chgat(self.maxY-3, thisX, msg) - msg = 'Delete item: ^X, Make default: ^B, Save history: ^V' + msg = 'Add/Del: ^T/^X, Make default: ^B, Save history: ^V' thisX = self.maxX - 2 - len(msg) self._win.addstr(self.maxY - 2, thisX, msg) self._carret_chgat(self.maxY-2, thisX, msg) + self._win.addstr(self.maxY - 2, 2 , 'History item: ') + self._win.addstr('{}'.format(self._selected_history_id), curses.color_pair(4)) + self._win.addstr('/{} '.format(len(self._history)-1)) + def _carret_chgat(self, Y, X, a_string): indexes = [i for i, c in enumerate(a_string) if c == '^'] for n in indexes: @@ -1937,8 +2087,35 @@ def _update_focus(self): # logger.error('_update_focus: {} - False'.format(i)) x._focused = False + def _get_search_term_index(self, new_search_term): + ''' search for a search term in history + + if found return True, index + if not found return False, len(self._history) - 1 + and append the search term in the history + ''' + found = False + for a_search_term_index, a_search_term in enumerate(self._history): + if new_search_term == a_search_term: + # self._history_id = self._selected_history_id + index = a_search_term_index + found = True + if logger.isEnabledFor(logging.DEBUG): + logger.debug('New search term already in history, id = {}'.format(self._history_id)) + break + + if not found: + if logger.isEnabledFor(logging.DEBUG): + logger.debug('Adding new search term to history, id = {}'.format(len(self._history))) + self._history.append(new_search_term) + # self._history_id = self._selected_history_id = len(self._history) - 1 + index = len(self._history) - 1 + self._cnf.dirty = True + + return found, index + def keypress(self, char): - ''' RadioBrowserInfoSearchWindow keypress + ''' RadioBrowserSearchWindow keypress Returns ------- @@ -1953,10 +2130,14 @@ def keypress(self, char): 7 - Set default item ''' if char in ( - curses.KEY_EXIT, ord('q'), 27 + curses.KEY_EXIT, 27 ): return -1 + if char == ord('q') and \ + type(self._widgets[self._focus]).__name__ != 'SimpleCursesLineEdit': + return -1 + if self._too_small: return 1 @@ -1978,31 +2159,39 @@ def keypress(self, char): elif char in (ord(' '), curses.KEY_ENTER, ord('\n'), ord('\r')) and self._focus == len(self._widgets) - 2: ''' enter on ok button ''' - new_search_term = self._widgets_to_search_term() - if new_search_term: - for i, a_search_term in enumerate(self._history): - logger.error('{0} - {1}'.format(i, a_search_term)) - - # self._history_id = self._selected_history_id = -1 - found = False - for a_search_term in self._history: - if new_search_term == a_search_term: - self._history_id = self._selected_history_id - if logger.isEnabledFor(logging.DEBUG): - logger.debug('New search term already in history, id = {}'.format(self._history_id)) - found = True - break - - if not found: - if logger.isEnabledFor(logging.DEBUG): - logger.debug('Adding new search term to history, id = {}'.format(len(self._history))) - self._history.append(new_search_term) - self._history_id = self._selected_history_id = len(self._history) - 1 - - return 0 - else: - ''' error in search term ''' - return 4 + ret = self._handle_new_or_existing_search_term() + return 0 if ret == 1 else ret + # new_search_term = self._widgets_to_search_term() + # if new_search_term: + # for i, a_search_term in enumerate(self._history): + # logger.error('{0} - {1}'.format(i, a_search_term)) + + + # found, index = self._get_search_term_index(new_search_term) + # self._history_id = self._selected_history_id = index + # self._print_history_legend() + # self._win.refresh() + # ''' + # # self._history_id = self._selected_history_id = -1 + # found = False + # for a_search_term in self._history: + # if new_search_term == a_search_term: + # self._history_id = self._selected_history_id + # if logger.isEnabledFor(logging.DEBUG): + # logger.debug('New search term already in history, id = {}'.format(self._history_id)) + # found = True + # break + + # if not found: + # if logger.isEnabledFor(logging.DEBUG): + # logger.debug('Adding new search term to history, id = {}'.format(len(self._history))) + # self._history.append(new_search_term) + # self._history_id = self._selected_history_id = len(self._history) - 1 + # ''' + # return 0 + # else: + # ''' error in search term ''' + # return 4 elif char in (curses.ascii.SO, ): ''' ^N - Next history item ''' @@ -2024,15 +2213,38 @@ def keypress(self, char): elif char in (curses.ascii.SYN, ): ''' ^V - Save search history ''' + self._handle_new_or_existing_search_term() return 5 + elif char in (curses.ascii.DC4, ): + ''' ^T - Add history item ''' + self._handle_new_or_existing_search_term() + elif char in (curses.ascii.CAN, ): ''' ^X - Delete history item ''' - return 6 + if len(self._history) > 2 and \ + self._selected_history_id > 1: + if self._default_history_id == self._selected_history_id: + self._default_history_id = 1 + self._history.pop(self._selected_history_id) + + if self._selected_history_id == len(self._history): + self._selected_history_id -= 1 + self._print_history_legend() + self._activate_search_term(self._history[self._selected_history_id]) + + # return 6 elif char in (curses.ascii.STX, ): ''' ^B - Set default item ''' - return 7 + ret = self._handle_new_or_existing_search_term() + if self._selected_history_id > 0: + if ret == 1: + self._default_history_id = self._selected_history_id + self._print_history_legend() + self._win.refresh() + ''' returning 5 will triger history save ''' + return 5 elif char in (curses.ascii.EM, ): ''' ^Y - Set default item ''' @@ -2050,10 +2262,10 @@ def keypress(self, char): # cursor moved self._win.refresh() - elif self._focus in self._check_box_to_enable_widgets: + elif self._focus in self._checkbox_to_enable_widgets: ret = self._widgets[self._focus].keypress(char) if not ret: - tp = list(self._check_box_to_enable_widgets) + tp = list(self._checkbox_to_enable_widgets) tp.remove(self._focus) other = tp[0] self._widgets[other].checked = not self._widgets[self._focus].checked @@ -2120,11 +2332,27 @@ def keypress(self, char): ''' continue ''' return 1 + def _handle_new_or_existing_search_term(self): + ''' read alla widgets and create a search term + if it does not exist add it to history + ''' + test_search_term = self._widgets_to_search_term() + if test_search_term: + found, index = self._get_search_term_index(test_search_term) + # TODO: check if item is altered + self._selected_history_id = index + self._print_history_legend() + self._win.refresh() + else: + ''' parameter error''' + return 4 + return 1 + def _fix_widgets_enabling(self): self._fix_search_captions_color() col = True if self._widgets[0].checked else False self._widgets[1].enabled = col - for i in range(self._check_box_to_enable_widgets[1] + 1, len(self._widgets) - self.NUMBER_OF_WIDGETS_AFTER_SEARCH_SECTION): + for i in range(self._checkbox_to_enable_widgets[1] + 1, len(self._widgets) - self.NUMBER_OF_WIDGETS_AFTER_SEARCH_SECTION): self._widgets[i].enabled = not col # logger.error('DE widget {0} enabled: {1}'.format(i, not col)) @@ -2221,7 +2449,7 @@ def _get_column_list(self, this_id): elif this_id in self._right_column: return self._right_column.index(this_id), self._right_column -class RadioBrowserInfoData(object): +class RadioBrowserData(object): ''' Read search parameters for radio.browser.info service parameters are: @@ -2412,7 +2640,7 @@ def get_data_dict(data): lock.release() -class RadioBrowserInfoDns(object): +class RadioBrowserDns(object): ''' Preforms query the DNS SRV record of _api._tcp.radio-browser.info which gives the list of server names directly @@ -2468,7 +2696,7 @@ def servers(self): for a_url in self._urls: yield a_url -class RadioBrowserInfoSort(object): +class RadioBrowserSort(object): TITLE = ' Sort by ' @@ -2576,7 +2804,7 @@ def _refresh(self): self._win.refresh() def keypress(self, char): - ''' RadioBrowserInfoSort keypress + ''' RadioBrowserSort keypress Returns: -1: Cancel @@ -2645,7 +2873,7 @@ def keypress(self, char): return 1 -class RadioBrowserInfoServersSelect(object): +class RadioBrowserServersSelect(object): TITLE = ' Server Selection ' @@ -2654,7 +2882,7 @@ def __init__(self, parent, servers, current_server): self.items = list(servers) self.server = current_server - self.servers = RadioBrowserInfoServers( + self.servers = RadioBrowserServers( parent, servers, current_server ) self.maxY = self.servers.maxY + 2 @@ -2705,7 +2933,7 @@ def set_parent(self, parent): self.servers._parent = parent def keypress(self, char): - ''' RadioBrowserInfoServersSelect keypress + ''' RadioBrowserServersSelect keypress Returns: -1: Cancel @@ -2723,7 +2951,7 @@ def keypress(self, char): return ret -class RadioBrowserInfoServers(object): +class RadioBrowserServers(object): ''' Display RadioBrowser server This is supposed to be pluged into another widget @@ -2785,7 +3013,7 @@ def show(self): self._win.refresh() def keypress(self, char): - ''' RadioBrowserInfoServers keypress + ''' RadioBrowserServers keypress Returns: -1: Cancel diff --git a/pyradio/config.py b/pyradio/config.py index 59015b18..7d091d9a 100644 --- a/pyradio/config.py +++ b/pyradio/config.py @@ -1130,6 +1130,7 @@ def __init__(self): self._check_config_file(self.stations_dir) self.config_file = path.join(self.stations_dir, 'config') + self.force_to_remove_lock_file = False @property diff --git a/pyradio/radio.py b/pyradio/radio.py index 37ce3964..4c81427f 100644 --- a/pyradio/radio.py +++ b/pyradio/radio.py @@ -3085,7 +3085,7 @@ def _open_playlist(self, a_url=None): None) except TypeError: pass - logger.error('DE online browser = {}'.format(self._cnf._online_browser)) + # logger.error('DE online browser = {}'.format(self._cnf._online_browser)) if self._cnf.online_browser: tmp_stations = [] @@ -3377,6 +3377,7 @@ def _open_playlist_from_history(self, if logger.isEnabledFor(logging.INFO): logger.info('Closing online browser!') self._cnf.online_browser = None + self.online_browser = False ''' check if browsing_station_service has changed ''' if not self._cnf.browsing_station_service and \ removed_playlist_history_item[-1]: @@ -4201,6 +4202,7 @@ def _browser_search(self): ''' Redisplay browser search window ''' self._cnf._online_browser.do_search() + # self._cnf._online_browser.redisplay_search() def _browser_sort(self): self._cnf._online_browser.sort() @@ -5112,19 +5114,27 @@ def keypress(self, char): txt='___Error in search parameters!!!___', mode_to_set=self.ws.BROWSER_SEARCH_MODE, callback_function=self.refreshBody) - elif ret ==5: + elif ret == 5: ''' save search history ''' - self._show_notification_with_delay( - txt='___Function not implemented yet!!!___', - mode_to_set=self.ws.BROWSER_SEARCH_MODE, - callback_function=self.refreshBody) - elif ret ==6: + self._cnf._online_browser.get_history_from_search() + if self._cnf._online_browser.save_config(): + self._show_notification_with_delay( + txt='___History successfully saved!___', + mode_to_set=self.ws.BROWSER_SEARCH_MODE, + callback_function=self.refreshBody) + else: + self._show_notification_with_delay( + txt='___Error saving History!___', + delay=1.25, + mode_to_set=self.ws.BROWSER_SEARCH_MODE, + callback_function=self.refreshBody) + elif ret == 6: ''' save search history ''' self._show_notification_with_delay( txt='___Function not implemented yet!!!___', mode_to_set=self.ws.BROWSER_SEARCH_MODE, callback_function=self.refreshBody) - elif ret ==7: + elif ret == 7: ''' save search history ''' self._show_notification_with_delay( txt='___Function not implemented yet!!!___', @@ -6028,8 +6038,9 @@ def keypress(self, char): self.jumpnr = '' self._cnf.jump_tag = -1 self._update_status_bar_right(status_suffix='') - self._cnf.browsing_station_service = True - self.playSelectionBrowser(a_url='api.radio-browser.info') + if not self._cnf.browsing_station_service: + self._cnf.browsing_station_service = True + self.playSelectionBrowser(a_url='api.radio-browser.info') return elif char in (ord('o'), ): diff --git a/pyradio/simple_curses_widgets.py b/pyradio/simple_curses_widgets.py index e95a8306..e0f95d8d 100644 --- a/pyradio/simple_curses_widgets.py +++ b/pyradio/simple_curses_widgets.py @@ -275,7 +275,7 @@ def __init__( self._max = maximum self._step = step self._big_step = big_step - self._value = value + self._value = int(value) if self._value < self._min: self._value = self.min if self._value > self._max: @@ -299,7 +299,7 @@ def value(self): @value.setter def value(self, val): - self._value = val + self._value = int(val) @property def minimum(self): diff --git a/pyradio_rb.1 b/pyradio_rb.1 new file mode 100644 index 00000000..da3d8797 --- /dev/null +++ b/pyradio_rb.1 @@ -0,0 +1,252 @@ +.\" Copyright (C) 2011 Ben Dowling +.\" This manual is freely distributable under the terms of the GPL. +.\" +.TH pyradio_rb 1 "August 2021" pyradio + +.SH Name +.PP +pyradio \- a curses Internet radio player. + +.SH \fBPyRadio\ RadioBrowser\ Implementation + +\fBRadioBrowser\fR "is a community driven effort (like wikipedia) with the aim of collecting as many internet radio and TV stations as possible". + +\fBPyRadio\fR uses the API provided to integrate it and provide its users the possibility to enjoy this great project. + +.IP \fBNote:\fR +As of the writing of this (v. \fI0.8.9.6\fR, which should actually be called \fI0.9-beta3\fR), the implementation is complete, but the service's configuration is not. + +.IP \fBOpening\ RadioBrowser + +To open \fBRadioBrowser\fR one would just press "\fIO\fR" at the program's main window. Since at this point this is the only service supported, the service will be activated. + + +Upon activation, the \fBdefault query\fR will be preformed and (if successful) its results will be presented to the user. If unsuccessful, a relevant message will be displayed and the program will return to the local playlist that was previously opened. + +By default, \fBPyRadio\fR will load the first 100 most voted stations on \fBRadioBrowser\fR. + +.IP \fBClosing\ RadioBrowser + +\fBPyRadio\fR treats the service as a special kind of a playlist, thus to close the service it is enough to "\fIgo back to playlist history\fR", pressing "\fI\\\\\fR". + +.IP \fBHow\ it\ works + +The implementation uses a list structure (we'll call it \fBsearch history\fR from now on) to keep user specified queries (we'll call them \fIsearch terms\fR). + +The first item in the \fBsearch history\fR is the \fIempty search term\fR (or \fIempty item\fR), which cannot be deleted and cannot be used to actually query \fBRadioBrowser\fR; it is there to provide a \fIsearch term template\fR for user inserted search terms. + +Upon activation, the \fIdefault search term\fR is used to automatically query a randomly selected \fBRadioBrowser\fR server and display stations' results. + +Once the results are fetched, they act as a special kind of playlist (some of the features of a local playlist are not functional, such as station renaming and such), and other features are introduced (such as the sort function and the station database info function). + +Each search result, i.e. each station, has more data attached to it than just its name and URL (bitrate, votes, clicks, etc.). This data is displayed in field columns; the number of visible columns depend on the terminal of the window. The name of the column that matches the sorting criteria is "highlighted". + +.RS +.IP \fBSearching\ in\ the\ list\ of\ stations + +The normal local playlist search function has been enhanced in order to be able to search through the list of stations, since each station has a lot more info attached to it. + +Searching for any string will return matches in the \fIName\fR field only (just like in a local playlist), but starting the search string with a plus sign ("\fI+\fR") will produce results from all available fields (visible or not). + +.IP \fBSorting\ stations + +Pressing "\fIS\fR" will present the user with a sorting list. Selecting one of the items will sort the stations based on it; selecting it again will reverse sorting order. + +.RE + +.RS 14 +.IP \fBNote: +This sorting function is different than the query sorting criterion which can be selected in the [Search window](#search-window). This one just sorts a query result set, the one in the \fBSearch window\fR affects the actual stations that will be in the result set. +.RE + +.IP \fBControls + +These are the \fBRadioBrowser\fR specific keys one can use in addition to local playlist keys (if applicable). + +.RS 10 +.IP \fIO +Open RadioBrowser +.IP \fIc +Open config window +.IP \fIC +Select server to connect to +.IP \fIs +Search for stations +.IP \fIS +Sort search results +.IP \fII +Station database info (current selection) | +.IP \fIV +Vote for station +.IP \fI\\\\\\\\ +Close RadioBrowser +.RE + +.IP \fBConfiguration + +This feature has not been implemented yet. + +.IP \fBStation\ Database\ Information + +The database information of the selected station can be displayed by pressing "\fII\fR". Keep in mind that, this is different than the \fIStation Info\fR displayed by pressing "\fIi\fR", which is still available and presents live data. + +.IP \fBStation\ clicking\ and\ voting + +\fBRadioBrowser\fR provides two ways to measure a station's popularity: voting and clicking. + +\fIClicking\fR a station means that the station has been listened to; \fBPyRadio\fR will send a "click request" any time the user starts playback of a station; \fBRadioBrowser\fR will either reject or accept the action, and either ignore or increase click count for the station based on several criteria (time between consecutive clicks, possibly IP, etc.) + +For this reason \fBPyRadio\fR will in no case adjust the click count presented to the user. + +\fIVoting\fR for a station is a different thing; the user has to choose to vote for it. In \fBPyRadio\fR a "vote request" is sent when "\fIV\fR" is pressed. If the vote has been accepted, the vote counter will be increased by one. + +.RS +.IP \fBNote: +Inconsistencies between a voted for station's local vote counter value and the one reported in a consecutive server response should be expected, since it seems servers' vote counter sync may take some time to complete. +.RE + +.IP \fBServer\ Selection + +\fBRadioBrowser\fR provides several servers to the public (currently in Germany, France and The Netherlands), which are constantly kept in sync. Its API provides a way to "discover" these servers and then select the one to use. + +\fBPyRadio\fR will randomly select one of these servers and will display its location in its window title. + +Pressing "\fIC\fR" will provide a list of available servers to choose from. This selection will be honored until the service is closed. + +.SH The Search Window + +The \fBSearch window\fR opens when "\fIs\fR" is pressed and loads the \fIsearch term\fR that was used to fetch the stations currently presented in the \fBRadioBrowser window\fR. If this is the first time this window is opened within this session, the search term that's loaded is the \fIdefault search term\fR. + +.IP \fBNote: +In case the server returns no results, the window will automatically reopen so that you can redefine the \fIsearch term\fR. + +.PP +Navigation between the various fields is done using the "\fBTab\fR" (and "\fBShift-Tab\fR") key, the arrows and vim keys ("\fBj\fR", "\fBk\fR", "\fBh\fR" and "\fBl\fR"), provided that any given key is not already used by one of the on window "widgets". + +This window performs two functions: + +.RS 5 +.IP \fI1) 3 +composes a search term to be forwarded to the search function and +.IP \fI2) +manages the \fBsearch history\fR. +.RE + +.IP \fB1.\ Search\ term\ composition + +.RS 5 +.PP +The \fBSearch window\fR can be divided in four parts: + +.IP \fI1.\fR\ The\ \fBDisplay\fR\ part + +In this part one would select to fetch a list of stations based on a single criterion such as their vote count, click count, etc. + +.IP \fI2.\fR\ The\ \fBSearch\fR\ part + +In this part, the user would insert a search string to one or more of the available fields. + +Each of the fields has an \fIExact\fR checkbox. If checked, an exact match will be returned, hopefully. + +In the \fICountry\fR field one could either provide the name of a country or its two-letter code (based on [ISO 3166](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)). For example, to get a list of Greek stations, you would either insert \fIgreece\fR or the country code, which is \fIgr\fR. + +These two parts are mutually exclusive, since when one is activated through its corresponding checkbox, the other one gets disabled. + +.IP \fI3.\fR\ The\ \fBSort\fR\ part + +This part affects both previous parts. + +It provides the server with the sorting criteria upon which the results will be returned. + +.IP \fI4.\fR\ The\ \fBLimit\fR + +In this part the maximum number or returned stations is specified. The default value is 100 stations. + +The value can be changed using the left and right arrows or "\fIh\fR", "\fIl\fR" and "\fIPgUp\fR", "\fIPgDn\fR" for a step of 10. +.RE + +.IP \fB2.\ History\ Management + +.RS 5 +At the bottom of the \fBSearch window\fR you have the \fIhistory information\fR section; on the left the number of history items is displayed along with the number of the current history item (\fIsearch term\fR) and on the right there's the history help legend. + +The keys to manage the history are all \fBControl\fR combinations: + +.IP \fI^N\fR\ \fI^P\fR 5 +Move to next / previous \fIsearch term\fR definition. + +.IP \fI^Y\fR +Move to the \fIempty search term\fR (history item 0). This is a quick way to "reset" all settings and start new. Of course, one could just navigate to this history item using \fI^N\fR or \fI^P\fR, but it's here just for convenience. + +.IP \fI^T\fR +Add current item to history. + +.IP \fI^X\fR +Delete the current history item. + +There is no confirmation and once an item is deleted there's no undo function. + +These rules apply: + +.RS 5 +.IP \fI1. 3 +The first item (\fIsearch term template\fR) cannot be deleted. + +.IP \fI2. 3 +When the history contains only two items (the \fIsearch term template\fR will always be the first one; the second one is a user defined \fIsearch term\fR), no item deletion is possible. + +.IP \fI3. 3 +When the \fIdefault search term\fR is deleted, the first user defined \fIsearch term\fR becomes the default one. +.RE + +.IP \fI^B\fR +Make the current history item the \fIdefault\fR one for \fBRadioBrowser\fR and save the history. + +This means that, next time you open \fBRadioBrowser\fR this history item (\fIsearch term\fR) will be automatically loaded. + +.IP \fI^V\fR +Save the history. + +.RE +.RS 5 +Here is one thing you should take notice of: after inserting some data into any of the various fields, do not navigate to another \fIsearch term\fR before adding it to the history; all your changes will be lost. + +Another thing you should be aware of, is that the \fBSearch Window\fR actually works on a copy of the \fIsearch history\fR used by the service itself, so any changes made in it (adding and deleting items) are not passed to the service, until "\fIOK\fR" is pressed. Pressing "\fICancel\fR" will make all the changes go away. + +Even when "\fIOK\fR" is pressed, and the "\fBSearch Window\fR" is closed, the "new" history is loaded into the service, but \fBNOT\fR saved to the \fIconfiguration file\fR. + +To really save the "new" history, press "\fI^V\fR" in the \fBSearch Window\fR. + +.IP \fBNote: +The history is also saved to file when one changes the "\fIdefault\fR" item, pressing "\fI^B\fR" in the \fBSearch Window\fR. +.RE + +.SH Reporting Bugs +.PP +When a bug is found, please do report it by opening an issue at github at \<\fIhttps://github.com/coderholic/pyradio/issues\fR\>, as already stated above. + +In you report you should, at the very least, state your \fIpyradio version\fR, \fIpython version\fR and \fImethod of installation\fR (built from source, AUR, snap, whatever). + +It would be really useful to include \fB~/pyradio.log\fR in your report. + +To create it, enter the following commands in a terminal: + +.HP + +\fI$\fR \fBrm ~/pyradio.log\fR +.br +\fI$\fR \fBpyradio -d\fR + +.PP +Then try to reproduce the bug and exit pyradio. + +Finally, include the file produced in your report. + +.SH Files + +\fI~/.config/pyradio/radio-browser-config\fR + +.SH See also + + pyradio(1) + diff --git a/radio-browser.html b/radio-browser.html index 52229cd0..7f13da24 100644 --- a/radio-browser.html +++ b/radio-browser.html @@ -72,8 +72,8 @@

How it works The implementation uses a list structure (we’ll call it “search history” from now on) to keep user specified queries (we’ll call them “search terms”).

The first item in the “search history” is the “empty search term” (or “empty item”), which cannot be deleted and cannot be used to actually query RadioBrowser; it is there to provide a “search term template” for user inserted search terms.

Upon activation, the “default search term” is used to automatically query a randomly selected RadioBrowser server and display stations’ results.

-

Once the results are fetched, they act as a special kind of playlist (some of the features of a local playlist are not functional, such as station renaming and such), and other features are introduced (such as the sort function and the station info function).

-

Each search result, i.e. each station, has more data attached to it than just its name and URL (bitrate, votes, clicks, etc.). This data is displayed in field columns; the number of visible columns depend on the width of the window and the name of the column that matches the sorting criteria is “highlighted”.

+

Once the results are fetched, they act as a special kind of playlist (some of the features of a local playlist are not functional, such as station renaming and such), and other features are introduced (such as the sort function and the station database info function).

+

Each search result, i.e. each station, has more data attached to it than just its name and URL (bitrate, votes, clicks, etc.). This data is displayed in field columns; the number of visible columns depend on the terminal of the window. The name of the column that matches the sorting criteria is “highlighted”.

Searching in the list of stations

The normal local playlist search function has been enhanced in order to be able to search through the list of stations, since each station has a lot more info attached to it.

Searching for any string will return matches in the “Name” field only (just like in a local playlist), but starting the search string with a plus sign (“+”) will produce results from all available fields (visible or not).

@@ -127,12 +127,12 @@

Controls Configuration Top

This feature has not been implemented yet.

Station Database Information Top

-

The database information of the selected station can be displayed by pressing “I” (capital “i”). Keep in mind that, this is different than the “Station ino” displayed by pressing “i” (lowercase “i”), which is still abailable and presents live data.

+

The database information of the selected station can be displayed by pressing “I” (capital “i”). Keep in mind that, this is different than the “Station ino” displayed by pressing “i” (lowercase “i”), which is still available and presents live data.

Station clicking and voting Top

RadioBrowser provides two ways to measure a station’s popularity: voting and clicking.

Clicking a station means that the station has been listened to; PyRadio will send a “click request” any time the user starts playback of a station; RadioBrowser will either reject or accept the action, and either ignore or increase click count for the station based on several criteria (time between consecutive clicks, possibly IP, etc.)

For this reason PyRadio will in no case adjust the click count presented to the user.

-

Voting for a station is a different thing; the user has to choose to vote for it. In PyRadio a “vote request” is sent when “V” (capital “v”) is pressed. If the vote has been accepted, the cote counter will be increased by one.

+

Voting for a station is a different thing; the user has to choose to vote for it. In PyRadio a “vote request” is sent when “V” (capital “v”) is pressed. If the vote has been accepted, the vote counter will be increased by one.

Note: Inconsistencies between a voted for station’s local vote counter value and the one reported in a consecutive server response should be expected, since it seems servers’ vote counter sync may take some time to complete.

Server Selection Top

RadioBrowser provides several servers to the public (currently in Germany, France and The Netherlands), which are constantly kept in sync. Its API provides a way to “discover” these servers and then select the one to use.

@@ -193,20 +193,27 @@

History Management

Move to the “empty search term” (history item 0). This is a quick way to “reset” all settings and start new. Of course, one could just navigate to this history item using ^N or ^P, but it’s here just for convenience. -^X -Delete the current history item.
There is no confirmation and once an item is deleted there’s no undo function.

Not implemented yet +^T +Add current item to history. -^B -Make the current history item the default one for RadioBrowser.
This means that, next time you open RadioBrowser this history item (“search term”) will be automatically loaded.

Not implemented yet +^X +Delete the current history item.
There is no confirmation and once an item is deleted there’s no undo function.
These rules apply:
1. The first item (search term template) cannot be deleted.
2. When the history contains only two items (the search term template will always be the first one; the second one is a user defined search term), no item deletion is possible.
3. When the default search term is deleted, the first user defined search term becomes the default one. -^W -Save the history.

Not implemented yet +^B +Make the current history item the default one for RadioBrowser and save the history.
This means that, next time you open RadioBrowser this history item (“search term”) will be automatically loaded. + + +^V +Save the history. -

There is one thing you should take notice of: after inserting some data into the various fields, do not navigate to another “search term” before saving it; all your changes will be lost. You should just save the history and then move to a new item.

-

When “OK” is pressed and a query to the service is made, all your “search” terms are there for you to manage even if the history has not been saved. As long as you do not close “RadioBrowser” you are OK.

+

Here is one thing you should take notice of: after inserting some data into any of the various fields, do not navigate to another “search term” before adding it to history; all your changes will be lost.

+

Another thing you should be aware of, is that the Search Window actually works on a copy of the search history used by the service itself, so any changes made in it (adding and deleting items) are not passed to the service, until “OK” is pressed. Pressing “Cancel” will make all the changes go away.

+

Even when “OK” is pressed, and the “Search Window” is closed, the “new” history is loaded into the service, but NOT saved to the configuration file.

+

To really save the “new” history, press “^V” in the Search Window.

+

Note: The history is also saved to file when one changes the “default” item, pressing “^B” in the Search Window.

diff --git a/radio-browser.md b/radio-browser.md index 22ed73ac..cb32a038 100644 --- a/radio-browser.md +++ b/radio-browser.md @@ -49,9 +49,9 @@ The first item in the "**search history**" is the "**empty search term**" (or "* Upon activation, the "**default search term**" is used to automatically query a randomly selected **RadioBrowser** server and display stations' results. -Once the results are fetched, they act as a special kind of playlist (some of the features of a local playlist are not functional, such as station renaming and such), and other features are introduced (such as the sort function and the station info function). +Once the results are fetched, they act as a special kind of playlist (some of the features of a local playlist are not functional, such as station renaming and such), and other features are introduced (such as the sort function and the station database info function). -Each search result, i.e. each station, has more data attached to it than just its name and URL (bitrate, votes, clicks, etc.). This data is displayed in field columns; the number of visible columns depend on the width of the window and the name of the column that matches the sorting criteria is "highlighted". +Each search result, i.e. each station, has more data attached to it than just its name and URL (bitrate, votes, clicks, etc.). This data is displayed in field columns; the number of visible columns depend on the terminal of the window. The name of the column that matches the sorting criteria is "highlighted". ### Searching in the list of stations @@ -87,7 +87,7 @@ This feature has not been implemented yet. ## Station Database Information -The database information of the selected station can be displayed by pressing "**I**" (capital "i"). Keep in mind that, this is different than the "Station ino" displayed by pressing "**i**" (lowercase "i"), which is still abailable and presents live data. +The database information of the selected station can be displayed by pressing "**I**" (capital "i"). Keep in mind that, this is different than the "Station ino" displayed by pressing "**i**" (lowercase "i"), which is still available and presents live data. ## Station clicking and voting @@ -97,7 +97,7 @@ The database information of the selected station can be displayed by pressing "* For this reason **PyRadio** will in no case adjust the click count presented to the user. -**Voting** for a station is a different thing; the user has to choose to vote for it. In **PyRadio** a "vote request" is sent when "**V**" (capital "v") is pressed. If the vote has been accepted, the cote counter will be increased by one. +**Voting** for a station is a different thing; the user has to choose to vote for it. In **PyRadio** a "vote request" is sent when "**V**" (capital "v") is pressed. If the vote has been accepted, the vote counter will be increased by one. **Note:** Inconsistencies between a voted for station's local vote counter value and the one reported in a consecutive server response should be expected, since it seems servers' vote counter sync may take some time to complete. @@ -165,10 +165,17 @@ The keys to manage the history are all **Control** combinations: |---------------|------------------------------------------------------| |**^N** **^P** |Move to next / previous "**search term**" definition. | |**^Y** |Move to the "**empty search term**" (history item 0). This is a quick way to "reset" all settings and start new. Of course, one could just navigate to this history item using **^N** or **^P**, but it's here just for convenience.| -|**^X** |Delete the current history item.
There is no confirmation and once an item is deleted there's no undo function.

**Not implemented yet**| -|**^B** |Make the current history item the **default** one for **RadioBrowser**.
This means that, next time you open **RadioBrowser** this history item ("**search term**") will be automatically loaded.

**Not implemented yet**| -|**^W** |Save the history.

**Not implemented yet**| +|**^T** |Add current item to history.| +|**^X** |Delete the current history item.
There is no confirmation and once an item is deleted there's no undo function.
These rules apply:
1. The first item (**search term template**) cannot be deleted.
2. When the history contains only two items (the **search term template** will always be the first one; the second one is a user defined **search term**), no item deletion is possible.
3. When the **default search term** is deleted, the first user defined **search term** becomes the default one.| +|**^B** |Make the current history item the **default** one for **RadioBrowser** and save the history.
This means that, next time you open **RadioBrowser** this history item ("**search term**") will be automatically loaded.| +|**^V** |Save the history.| -There is one thing you should take notice of: after inserting some data into the various fields, do not navigate to another "**search term**" before saving it; all your changes will be lost. You should just save the history and then move to a new item. +Here is one thing you should take notice of: after inserting some data into any of the various fields, do not navigate to another "**search term**" before adding it to history; all your changes will be lost. -When "**OK**" is pressed and a query to the service is made, all your "**search**" terms are there for you to manage even if the history has not been saved. As long as you do not close "**RadioBrowser**" you are OK. +Another thing you should be aware of, is that the **Search Window** actually works on a copy of the **search history** used by the service itself, so any changes made in it (adding and deleting items) are not passed to the service, until "**OK**" is pressed. Pressing "**Cancel**" will make all the changes go away. + +Even when "**OK**" is pressed, and the "**Search Window**" is closed, the "new" history is loaded into the service, but NOT saved to the *configuration file*. + +To really save the "new" history, press "**^V**" in the **Search Window**. + +**Note:** The history is also saved to file when one changes the "**default**" item, pressing "**^B**" in the **Search Window**.