In this tutorial, I'll walk you through the process of creating a command-line radio player using Python and ffplay, a simple and efficient media player. This radio player allows you to listen to your favorite radio stations by selecting them from a list and controlling playback with intuitive command-line commands. We will break down the code step by step, explain how to set up the project and provide you with the complete Python script.
Traditional radio players are often graphical applications with user-friendly interfaces. However, command-line radio players can be useful when you want to listen to radio stations while working in a terminal or remotely accessing a system. In this tutorial, we'll leverage Python and ffplay to create a command-line radio player that's easy to use and customize.
The Charm of Command-Line Radio
Command-line radio players might seem like a throwback to a simpler time, but they offer a distinct charm and practicality. Here are a few reasons why you might find them appealing:
- Minimalistic Interface: Command-line radio players are stripped down to the essentials. You interact with them solely through text commands, which can be refreshing in an age of cluttered graphical user interfaces.
- Resource Efficiency: Running a command-line player consumes fewer system resources compared to running a full-fledged graphical application. This can be especially valuable if you're on a resource-constrained system or managing remote servers.
- Task Integration: When you're working in a terminal or remotely accessing a system, having the radio integrated into the command-line environment can be quite convenient. You can easily switch between tasks without leaving the terminal.
- Customization: Command-line radio players are highly customizable. You can tailor the player's behavior and appearance to your liking and add features as needed.
Meet ffplay: A Simple and Efficient Media Player
To bring our command-line radio player to life, we'll make use of ffplay. ffplay is a component of FFmpeg, a powerful multimedia framework. It's a lightweight media player that excels at playing audio and video files. In our case, we'll use it to stream audio from internet radio stations.
Prerequisites
Before you can start building your command-line radio player using Python and ffplay, it's essential to ensure that you have all the necessary software and libraries installed on your system. In this section, I'll provide a more detailed explanation of the prerequisites, including how to install them and why they are essential for the project.
Python 3.x
Python is the programming language we'll use to create our command-line radio player. Python is known for its simplicity, readability, and versatility, making it an excellent choice for a wide range of projects, including this one. To check if Python is installed on your system, open your terminal and run the following command:
python3 --version
ffplay
ffplay is a component of FFmpeg, a powerful multimedia framework that includes tools for handling audio and video. We'll use ffplay as our audio playback engine to stream audio from internet radio stations. It's a lightweight and efficient media player.
To check if ffplay is installed on your system, open your terminal and run:
ffplay -version
If you see version information for ffplay, it's already installed. If not, you'll need to install FFmpeg, which includes ffplay. The installation method depends on your operating system:
For Linux (Debian/Ubuntu):
sudo apt-get install ffmpeg
For Linux (Red Hat/Fedora):
sudo dnf install ffmpeg
For macOS (Homebrew):
brew install ffmpeg
For Windows:
- Visit the FFmpeg official website: FFmpeg Builds.
- Download an executable version of FFmpeg that includes ffplay for Windows.
- Follow the installation instructions provided with the download.
After installing FFmpeg, you should have ffplay available in your terminal.
columnar Library
The columnar library is a Python package that we'll use to format and display radio station information in a neat table within our command-line interface. This library simplifies the process of presenting data in columns, making our radio player more user-friendly.
You can install the columnar library using pip, Python's package manager, as follows:
pip install columnar
With these prerequisites installed, you'll have all the tools and libraries needed to create your own command-line radio player. In the following sections, we'll guide you through setting up the project, understanding the provided code, and enjoying your favorite radio stations from the comfort of your terminal.
Setting Up the Project
Setting up the project for your command-line radio player involves a series of steps to ensure that you have the necessary files and configurations in place. In this section, I'll provide a detailed guide on how to set up your project, including creating the Python script, setting up the station configuration file, and organizing your project directory.
Create a Project Directory
Start by creating a dedicated directory for your command-line radio player project. You can choose any name for this directory. For example, you might create a directory called “pyradio_player” or something similar.
Create the Python Script
Within your project directory, you'll need to create a Python script that contains the code for your command-line radio player. You can use a text editor or integrated development environment (IDE) of your choice to create this script.
- Open your text editor or IDE.
- Create a new file and save it with a suitable name, such as pyradio.py, in your project directory.
- Copy the provided Python code for the radio player into your pyradio.py file. This code includes the necessary functions and logic to run your radio player.
Create a Station Configuration File
To store information about the radio stations you want to listen to, you'll create a configuration file in the INI format. Each station entry in this file will include the station name, stream URL, and genre. Here's an example of what the configuration file, named stations.ini, could look like:
[Station Name 1] stream = http://streamurl1.com genre = Genre 1 [Station Name 2] stream = http://streamurl2.com genre = Genre 2
Replace “Station Name 1”, “Station Name 2”, “http://streamurl1.com”, “http://streamurl2.com”, “Genre 1”, and “Genre 2” with the actual station names, stream URLs, and genres you want to include in your radio player. You can add as many stations as you like to this configuration file.
Save the stations.ini file in the same directory as your pyradio.py script.
Organize Your Project Directory
Now that you have created the Python script and the station configuration file, your project directory should look like this:
pyradio_player/ pyradio.py stations.ini
With these files in place, your project is set up and you are ready to start setting up the actual Python script. In the next sections of this tutorial, we'll dive into the code explanation and guide you through using your radio player.
The Full Code
In this section, I'll provide a comprehensive explanation of the complete Python code for your command-line radio player. This code forms the core of your radio player and enables you to listen to your favorite radio stations from the command line. I'll break down each part of the code, explaining its purpose and functionality.
Let's dive into the code:
#!/usr/bin/python3 import os import subprocess as sp import sys import time import configparser from columnar import columnar # Constants APP_TITLE = "PyRadio" STATIONS_FILE = 'stations.ini' PLAYING_PID_FILE = 'playing.pid' # Radio class definition class Radio: def __init__(self): """ Radio class constructor initializes instance variables and reads station information from the configuration file. """ self.stations = configparser.ConfigParser() self.stations.read(STATIONS_FILE) self.station_lst = list(self.stations.sections()) self.radio_playing = False self.is_exited = False def cleanup(self, refresh=True): """ Cleans up any existing radio processes and stops the radio. Optionally refreshes the display. Parameters: refresh (bool): If True, refresh the display after cleanup. """ try: # Attempt to read existing process IDs from the playing.pid file with open(PLAYING_PID_FILE, "r") as f: # Filter out the current process ID and kill the remaining processes pids = [int(line.strip()) for line in f if int(line.strip()) != os.getpid()] for pid in pids: os.system(f"kill {pid}") except FileNotFoundError: # If the playing.pid file doesn't exist, create an empty one open(PLAYING_PID_FILE, "w").close() # If refresh is True and radio was playing, stop the radio if refresh and self.radio_playing: self.radio_playing = False self.main("Radio has been stopped.") def play_station(self, stream, station): """ Plays the specified radio station using ffplay subprocess. Parameters: stream (str): URL or stream information for the radio station. station (str): Name of the radio station. """ # Clean up without refreshing the display self.cleanup(False) try: # Launch ffplay subprocess to play the radio stream radio_p = sp.Popen(['ffplay', '-hide_banner', '-loglevel', 'panic', stream, '-autoexit', '-nodisp']) # Append the process ID to the playing.pid file with open(PLAYING_PID_FILE, 'a') as file: file.write(str(radio_p.pid) + "\n") # Set radio_playing to True self.radio_playing = True # Display a message indicating the station is now playing self.main(f" {station} is now playing.") except sp.CalledProcessError: print(f"Failed to play {station}. Check your internet connection and try again.") self.cleanup() def info_header(self): """ Generates and returns the header information for the console display. Returns: str: Console header information. """ os.system('cls' if os.name == 'nt' else 'clear') sep = "###############################################################################" console = f" {sep}\n # {APP_TITLE} - Choose a Station\n {sep}\n" headers = ['id', 'station', 'genre'] stations_tmp = [[id + 1, station, self.stations.get(station, 'genre')] for id, station in enumerate(self.station_lst)] console += columnar(stations_tmp, headers, no_borders=True) console += "\n S - Stop Music | E - Exit Radio\n\n" if self.radio_playing else "\n E - Exit Radio\n\n" console += f" {sep}\n" return console def exit_radio(self, tm=0): """ Exits the radio application after a specified time delay. Parameters: tm (int): Time delay before exit. """ time.sleep(tm) sys.exit() def main(self, response=None): """ Main method to interact with the user, process user input, and perform corresponding actions. Parameters: response (str): Optional response message to be displayed. """ if self.is_exited: self.exit_radio() os.system('cls' if os.name == 'nt' else 'clear') print(self.info_header()) if response: print(response + "\n") action = input(" What would you like to do? ") try: # Try to convert user input to an integer (assuming it's a station ID) selected_id = int(action) selected_id -= 1 if 0 <= selected_id < len(self.station_lst): if selected_id == self.station_lst.index(self.station_lst[selected_id]): # Play the selected station self.play_station(self.stations.get(self.station_lst[selected_id], 'stream'), self.station_lst[selected_id]) else: print("Invalid station ID.") else: print("Invalid station ID.") except ValueError: # If input is not an integer, check for special commands (S for stop, E for exit) if action.lower() == "s" and self.radio_playing: # Stop the radio if it is playing self.cleanup() elif action.strip().lower() == "e": # Set the exit flag and perform cleanup self.is_exited = True self.cleanup(True) else: # If input is not recognized, prompt the user again self.main() # Entry point for the script if __name__ == '__main__': # Create an instance of Radio radio_app = Radio() # Set the terminal title to the application title os.system(f"printf '\033]2;{APP_TITLE}\a'") # Clear the terminal screen os.system('cls' if os.name == 'nt' else 'clear') # Start the main loop of the radio application radio_app.main()
Importing Required Modules
We start by importing the necessary Python modules. These modules provide the functionality required for our radio player:
- os: Used for executing operating system commands and interacting with the terminal.
- subprocess as sp: Allows us to create and manage subprocesses, including running ffplay.
- sys: Provides access to Python system-specific parameters and functions.
- time: Used for managing time-related operations, such as delaying the exit of the radio player.
- configparser: Enables reading and parsing configuration files, specifically our stations.ini.
- columnar: A Python package that helps format and display data in columns for a neat console interface.
Constants
We define several constants that are used throughout the code:
- APP_TITLE: A string containing the title of our radio player, displayed in the terminal.
- STATIONS_FILE: The filename of the station configuration file (e.g., stations.ini).
- PLAYING_PID_FILE: The filename of the PID (Process ID) file used to manage ffplay processes.
Radio Class Constructor
The Radio class constructor (__init__) initializes various instance variables and reads station information from the stations.ini configuration file:
- self.stations: An instance of configparser.ConfigParser used to store and manage station data.
- self.station_lst: A list containing the names of all the radio stations defined in the configuration.
- self.radio_playing: A boolean flag to track whether a station is currently playing.
- self.is_exited: A boolean flag to indicate if the user has exited the application.
Next, let's continue exploring the remaining parts of the code:
Cleanup Method
def cleanup(self, refresh=True): """ Cleans up any existing radio processes and stops the radio. Optionally refreshes the display. Parameters: refresh (bool): If True, refresh the display after cleanup. """ try: # Attempt to read existing process IDs from the playing.pid file with open(PLAYING_PID_FILE, "r") as f: # Filter out the current process ID and kill the remaining processes pids = [int(line.strip()) for line in f if int(line.strip()) != os.getpid()] for pid in pids: os.system(f"kill {pid}") except FileNotFoundError: # If the playing.pid file doesn't exist, create an empty one open(PLAYING_PID_FILE, "w").close()
The cleanup method is responsible for cleaning up any existing radio processes and, optionally, refreshing the display. Its functionality includes:
- Reading existing process IDs from the playing.pid file, which contains the IDs of ffplay processes associated with the radio player.
- Filtering out the current process ID (the Python script itself) to avoid killing it.
- Killing the remaining processes listed in the playing.pid file.
- Handling the case where the playing.pid file doesn't exist by creating an empty one.
# If refresh is True and radio was playing, stop the radio if refresh and self.radio_playing: self.radio_playing = False self.main("Radio has been stopped.")
- After cleaning up the processes, the method checks whether a refresh is requested and if the radio was playing. If both conditions are met, it stops the radio by setting self.radio_playing to False and displays a message indicating that the radio has been stopped.
Play Station Method
def play_station(self, stream, station): """ Plays the specified radio station using ffplay subprocess. Parameters: stream (str): URL or stream information for the radio station. station (str): Name of the radio station. """ # Clean up without refreshing the display self.cleanup(False) try: # Launch ffplay subprocess to play the radio stream radio_p = sp.Popen(['ffplay', '-hide_banner', '-loglevel', 'panic', stream, '-autoexit', '-nodisp']) # Append the process ID to the playing.pid file with open(PLAYING_PID_FILE, 'a') as file: file.write(str(radio_p.pid) + "\n") # Set radio_playing to True self.radio_playing = True # Display a message indicating the station is now playing self.main(f" {station} is now playing.") except sp.CalledProcessError: print(f"Failed to play {station}. Check your internet connection and try again.") self.cleanup()
The play_station method is responsible for playing the specified radio station using the ffplay subprocess. Here's how it works:
- It starts by cleaning up any existing radio processes without refreshing the display. This ensures that only one station plays at a time.
- Inside a try-except block, it attempts to launch an ffplay subprocess to play the radio stream specified by the stream parameter. Several options are provided to ffplay, including -hide_banner to hide informational messages, -loglevel panic to suppress log output, -autoexit to exit ffplay after the stream finishes, and -nodisp to disable video display (since we're only interested in audio).
- The process ID (PID) of the ffplay subprocess is obtained using radio_p.pid, and it is appended to the playing.pid file.
- self.radio_playing is set to True to indicate that a station is now playing.
- A message is displayed in the console, indicating that the specified station is now playing.
- In case of a sp.CalledProcessError, which can occur if ffplay fails to play the station (e.g., due to an internet connectivity issue), an error message is displayed, and the cleanup method is called to ensure a clean state.
Info Header Method
The info_header method generates the console header information, which includes a table of available stations, their genres, and additional control options. It is used to create a visually appealing and informative interface for the radio player:
def info_header(self): """ Generates and returns the header information for the console display. Returns: str: Console header information. """ os.system('cls' if os.name == 'nt' else 'clear') sep = "###############################################################################" console = f" {sep}\n # {APP_TITLE} - Choose a Station\n {sep}\n" headers = ['id', 'station', 'genre'] stations_tmp = [[id + 1, station, self.stations.get(station, 'genre')] for id, station in enumerate(self.station_lst)] console += columnar(stations_tmp, headers, no_borders=True) console += "\n S - Stop Music | E - Exit Radio\n\n" if self.radio_playing else "\n E - Exit Radio\n\n" console += f" {sep}\n" return console
The info_header method generates and returns the header information for the console display. Here's how it works:
- It starts by clearing the terminal screen using os.system(‘cls' if os.name == ‘nt' else ‘clear') to ensure a clean display.
- It defines a separator line sep to visually separate the header from the station list.
- The console variable is used to build the header text, which includes the title of the radio player (APP_TITLE) and the header itself.
- The columnar package is used to create a table of available stations, their genres, and an “id” column. This table is based on the data stored in the stations.ini file.
- Depending on whether a station is currently playing (self.radio_playing), the method appends control options to the console header. If music is playing, it provides the option to stop it (press ‘S'). If not, it provides the option to exit the radio player (press ‘E').
- The sep separator line is appended to the console header.
Exit Radio Method
def exit_radio(self, tm=0): """ Exits the radio application after a specified time delay. Parameters: tm (int): Time delay before exit. """ time.sleep(tm) sys.exit()
The exit_radio method is responsible for exiting the radio application after a specified time delay. Here's how it works:
- It takes an optional parameter tm, which represents the time delay in seconds before exiting. By default, it is set to 0 seconds.
- The time.sleep(tm) function is used to pause the execution of the script for the specified time delay.
- After the time delay (if any), the sys.exit() function is called to terminate the Python script and exit the radio player.
Main Method
def main(self, response=None): """ Main method to interact with the user, process user input, and perform corresponding actions. Parameters: response (str): Optional response message to be displayed. """ if self.is_exited: self.exit_radio() os.system('cls' if os.name == 'nt' else 'clear') print(self.info_header()) if response: print(response + "\n") action = input(" What would you like to do? ") try: # Try to convert user input to an integer (assuming it's a station ID) selected_id = int(action) selected_id -= 1 if 0 <= selected_id < len(self.station_lst): if selected_id == self.station_lst.index(self.station_lst[selected_id]): # Play the selected station self.play_station(self.stations.get(self.station_lst[selected_id], 'stream'), self.station_lst[selected_id]) else: print("Invalid station ID.") else: print("Invalid station ID.") except ValueError: # If input is not an integer, check for special commands (S for stop, E for exit) if action.lower() == "s" and self.radio_playing: # Stop the radio if it is playing self.cleanup() elif action.strip().lower() == "e": # Set the exit flag and perform cleanup self.is_exited = True self.cleanup(True) else: # If input is not recognized, prompt the user again self.main()
The main method is the central part of the application, responsible for interacting with the user, processing user input, and performing corresponding actions. Here's how it works:
- It first checks whether the user has already exited the radio player (self.is_exited). If so, it proceeds to exit the radio player.
- The terminal screen is cleared (os.system(‘cls' if os.name == ‘nt' else ‘clear')) to provide a clean interface.
- The info_header method is called to generate the console header and station list, which are displayed in the terminal.
- If there is an optional response parameter (a message), it is displayed in the console.
- The user is prompted with the message “What would you like to do? “, and their input is stored in the action variable.
- Inside a try-except block, it attempts to process the user input:
- If the input can be converted to an integer (assuming it's a station ID), it checks whether the input is within the valid range of station IDs. If it's valid, it attempts to play the selected station using the play_station method.
- If the input is not a valid integer or doesn't correspond to a station ID, it checks for special commands. If the input is “s” (case-insensitive) and music is playing (self.radio_playing), it stops the radio using the cleanup method. If the input is “e” (case-insensitive), it sets the exit flag (self.is_exited) and performs cleanup.
- If the input is not recognized, it prompts the user again by calling self.main() recursively.
That concludes the explanation of the complete code for your command-line radio player. With this code, you have the foundation to create a functional and customizable radio player that allows you to listen to your favorite stations right from the command line.
Finding Radio Stream Addresses
To build your own command-line radio player, you'll need access to the stream addresses (URLs) of the radio stations you want to listen to. Radio stream addresses are essential because they determine where your player fetches audio content. In this section, we'll explore how to find radio stream addresses, which can vary depending on the source and type of station.
Official Radio Station Websites
One of the most reliable sources for finding stream addresses is the official website of the radio station you're interested in. Follow these steps:
- Search Online: Use your preferred search engine to find the official website of the radio station. For example, if you're looking for a specific local radio station, search for its name along with “official website.”
- Visit the Website: Once you've identified the official website, visit it. Radio stations often provide a “Listen Live” or “Streaming” section on their websites.
- Locate the Stream Address: Look for a direct stream address or a link to a stream. It may be labeled as “Listen Live,” “Stream Now,” or something similar. Right-click on the link and copy the URL.
- Test the URL: Paste the URL into a web browser to ensure it plays the station's stream. If it works, you can use this URL in your radio player.
Radio Directory Websites
Several online directories specialize in aggregating radio stations from around the world. These directories often provide direct stream addresses or links to streams. Some popular radio directories include TuneIn Radio, Streema, InternetRadio, and RadioBrowser.
Visit these directories, search for your desired station, and you'll typically find stream addresses that you can use in your radio player.
Internet Radio Apps
Mobile and desktop apps designed for internet radio streaming often list station streams. Apps like “TuneIn Radio,” “iHeartRadio,” and “Radio.com” have extensive databases of radio stations and their stream addresses. You can use these apps to discover stations and find their stream URLs.
Radio Station Database APIs
Some websites and services provide APIs (Application Programming Interfaces) that allow developers to access radio station data programmatically. These APIs often include stream addresses. For example:
- RadioBrowser API: RadioBrowser provides a free API that offers access to a vast database of radio stations and their stream URLs.
You can use such APIs to fetch station information, including stream addresses, for integration into your radio player.
User-Contributed Lists
Online communities, forums, and websites dedicated to radio enthusiasts often share lists of radio station stream addresses. These lists may cover various genres, regions, and languages. While these lists can be valuable, it's essential to verify the URLs to ensure they are still valid and functional.
Radio Station Apps and Streams
Some radio stations offer their own mobile apps or streaming links on their websites. These apps may provide convenient access to the station's stream.
Keep in mind that radio station stream addresses may change over time, so it's a good practice to periodically check and update the stream URLs in your player to ensure uninterrupted listening.
Once you've found the stream addresses for your favorite radio stations, you can add them to your stations.ini configuration file as shown in the “Setting Up the Project” section of this tutorial. With the stream addresses in place, your command-line radio player will be ready to provide you with hours of music and entertainment from the comfort of your terminal. Enjoy your radio listening experience!
Running the Script
Now that you have set up your command-line radio player project and understood the code, it's time to run the script and start enjoying your favorite radio stations. Follow these steps to run the script successfully:
Navigate to Your Project Directory
Open your terminal and navigate to the directory where you have saved your pyradio.py script and stations.ini configuration file. Use the cd command to change directories if needed.
cd path/to/your/project/directory
Run the Script:
To run the script, use the python3 command followed by the name of your Python script, which is pyradio.py in this case.
python3 pyradio.py
Conclusion
Congratulations on building your very own command-line radio player with Python and ffplay! You've taken a journey from setting up the project to understanding the code, and now you have a functional tool for enjoying radio stations right from your terminal.
As you explore and customize your radio player further, remember to stay curious, keep learning, and have fun along the way. Building and enhancing projects like this one are excellent opportunities to hone your programming skills and create something useful and enjoyable. Happy listening!