Added a method for adding and removing stations. Alternating colors on track list. Only works when the app is running. After closing and restarting the default will be the only stations.
#! /usr/bin/env python3.9 import sys from mutagen.mp3 import MP3 from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist, QMediaMetaData from PyQt5.QtCore import QUrl, Qt from PyQt5.QtWidgets import QApplication, QMainWindow, QListWidget, QWidget, QHBoxLayout, \ QVBoxLayout, QGridLayout, QLabel, QPushButton, QFrame, QSlider, \ QStyle, QRadioButton, QFileDialog, QLineEdit, QMessageBox from PyQt5.QtGui import QIcon, QPixmap, QGradient, QPalette, QBrush class Window(QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Set the window title self.setWindowTitle('PyQt5 Audio Player') # Set window background color p = QPalette() gradient = QGradient(QGradient.RichMetal) p.setBrush(QPalette.Window, QBrush(gradient)) self.setPalette(p) # Set some variables self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.musiclist = QListWidget() self.entry = QLineEdit() self.entry.setPlaceholderText('Add a station. Example - https://url/to/internet/station/stream') self.entry.setFocusPolicy(Qt.StrongFocus) self.player.setPlaylist(self.playlist) # Changes cursor to hand when over button or link self.hand = Qt.PointingHandCursor # Set the spacing on the musiclist self.musiclist.setSpacing(2) # Create the needed containers container = QGridLayout() container.setSpacing(0) info_container = QGridLayout() info_frame = QFrame() info_frame.setContentsMargins(0, 3, 2, 3) info_frame.setMinimumHeight(300) info_frame.setMaximumWidth(361) info_frame.setFrameShape(QFrame.Box) info_frame.setFrameShadow(QFrame.Sunken) info_frame.setLayout(info_container) radio_frame = QFrame() radio_frame.setContentsMargins(0,0,0,0) radio_frame.setFrameShape(QFrame.Box) radio_frame.setFrameShadow(QFrame.Sunken) radio_frame.setMinimumHeight(60) radio_frame.setMaximumHeight(60) radio_container = QGridLayout() radio_frame.setLayout(radio_container) # Frame for the slider slider_box = QGridLayout() slider_frame = QFrame() slider_frame.setContentsMargins(0, 3, 2, 3) slider_frame.setFrameShape(QFrame.Box) slider_frame.setFrameShadow(QFrame.Sunken) slider_frame.setLayout(slider_box) # Control Box to hold volume, play, next, prev, stop control_box = QGridLayout() control_frame = QFrame() control_frame.setFrameShape(QFrame.Box) control_frame.setFrameShadow(QFrame.Sunken) control_frame.setContentsMargins(4,4,1,4) control_frame.setLayout(control_box) # Station edit frame self.station_frame = QFrame() self.station_frame.setFrameShape(QFrame.Box) self.station_frame.setFrameShadow(QFrame.Sunken) station_box = QGridLayout() self.station_frame.setLayout(station_box) self.station_frame.setMaximumHeight(60) self.station_frame.hide() # Create some common styles self.musiclist.setFrameShadow(QFrame.Sunken) self.musiclist.setAlternatingRowColors(True) self.musiclist.setStyleSheet(''' margin-top: 3px; margin-bottom: 3px; margin-left: 2px; alternate-background-color: lightblue; background-color:white; padding: 12px; ''') style = ''' background-color: snow; padding-left: 8px; padding-right: 20px; border: 1px solid lightgray; font-weight: bold; font-size: 10pt; ''' style2 = ''' background-color: snow; padding-left: 8px; padding-right: 20px; border: 1px solid lightgray; font-weight: 400; font-size: 10pt; color: navy; ''' # Common button style self.btn_style = ''' QPushButton{background-color: skyblue; color: navy; font-size: 10pt; font-weight: 500; padding: 6px; margin-left: 6px;} QPushButton:hover{background-color: lightskyblue; color: blue; font-weight: 600; padding: 6px;} QPushButton:pressed{background-color: dodgerblue; color: lightblue; font-weight: 400; padding: 4px;} ''' # Create the needed labels. Labels without self will not change data. #Labels with self will change data status_label = QLabel('Status:') status_label.setMinimumHeight(30) status_label.setMaximumHeight(30) status_label.setMinimumWidth(80) status_label.setMaximumWidth(80) status_label.setStyleSheet(style) self.status_label = QLabel('Now Stopped') self.status_label.setMinimumHeight(30) self.status_label.setMaximumHeight(30) self.status_label.setMinimumWidth(200) self.status_label.setMaximumWidth(200) self.status_label.setStyleSheet(style2) track_label = QLabel('Track:') track_label.setMinimumHeight(30) track_label.setMaximumHeight(30) track_label.setMinimumWidth(80) track_label.setMaximumWidth(80) track_label.setStyleSheet(style) self.track_label = QLabel() self.track_label.setMinimumHeight(30) self.track_label.setMaximumHeight(30) self.track_label.setMinimumWidth(420) self.track_label.setStyleSheet(style2) artist = QLabel('Artist:') artist.setStyleSheet(style) artist.setMaximumHeight(30) artist.setMinimumHeight(30) title = QLabel('Title:') title.setStyleSheet(style) title.setMaximumHeight(30) title.setMinimumHeight(30) album = QLabel('Album') album.setStyleSheet(style) album.setMaximumHeight(30) album.setMinimumHeight(30) released = QLabel('Released:') released.setStyleSheet(style) released.setMaximumHeight(30) released.setMinimumHeight(30) genre = QLabel('Genre:') genre.setStyleSheet(style) genre.setMaximumHeight(30) genre.setMinimumHeight(30) self.artist = QLabel() self.artist.setStyleSheet(style2) self.artist.setMinimumWidth(235) self.artist.setMaximumHeight(30) self.artist.setMinimumHeight(30) self.title = QLabel() self.title.setStyleSheet(style2) self.title.setMinimumWidth(235) self.title.setMaximumHeight(30) self.title.setMinimumHeight(30) self.album = QLabel() self.album.setStyleSheet(style2) self.album.setMinimumWidth(235) self.album.setMaximumHeight(30) self.album.setMinimumHeight(30) self.released = QLabel() self.released.setStyleSheet(style2) self.released.setMinimumWidth(235) self.released.setMaximumHeight(30) self.released.setMinimumHeight(30) self.genre = QLabel() self.genre.setStyleSheet(style2) self.genre.setMinimumWidth(235) self.genre.setMaximumHeight(30) self.genre.setMinimumHeight(30) self.cover_art = QLabel() self.cover_art.setMinimumSize(300, 300) self.cover_art.setFrameShape(QFrame.Box) self.cover_art.setFrameShadow(QFrame.Sunken) # Just a couple of spacers to keep everything push up when expanding self.spacer = QLabel() self.spacer2 = QLabel() self.duration_labelheader = QLabel('Duration:') self.duration_labelheader.setMinimumHeight(40) self.duration_labelheader.setMaximumHeight(40) self.duration_labelheader.setStyleSheet('font-size: 11pt; font-weight:500;') self.duration_timer = QLabel('00:00:00/00:00:00') self.duration_timer.setStyleSheet('font-size: 10pt; font-weight: 400; color: gray;') # Create the buttons self.get_btn = QPushButton('Get Audio') self.get_btn.setStyleSheet(self.btn_style) self.get_btn.setCursor(self.hand) self.get_btn.released.connect(self._files) clear_btn = QPushButton('Clear Playlist') clear_btn.setStyleSheet(self.btn_style) clear_btn.setCursor(self.hand) clear_btn.released.connect(self._clear) self.volume_slider = QSlider(Qt.Horizontal) self.volume_slider.setRange(0, 100) self.volume_slider.setTickInterval(10) self.volume_slider.setValue(70) self.volume_slider.setTickPosition(QSlider.TicksAbove) self.volume_label = QLabel(f'Volume: {self.volume_slider.value()}') self.volume_label.setMinimumWidth(200) self.volume_label.setStyleSheet('font-size: 11pt; padding-left: 25px;') self.play_btn = QPushButton() self.play_btn.setIcon(self.play_btn.style().standardIcon(QStyle.SP_MediaPlay)) self.play_btn.setCursor(self.hand) self.play_btn.released.connect(self.player.play) stop_btn = QPushButton() stop_btn.setIcon(stop_btn.style().standardIcon(QStyle.SP_MediaStop)) stop_btn.setCursor(self.hand) stop_btn.released.connect(self.player.stop) prev_btn = QPushButton() prev_btn.setIcon(prev_btn.style().standardIcon(QStyle.SP_MediaSkipBackward)) prev_btn.setCursor(self.hand) prev_btn.released.connect(self._prev) next_btn = QPushButton() next_btn.setIcon(next_btn.style().standardIcon(QStyle.SP_MediaSkipForward)) next_btn.setCursor(self.hand) next_btn.released.connect(self._next) # Radio buttons to choose internet radio or local files self.radio_btn = QRadioButton('Internet Radio') self._local_file_btn = QRadioButton('Local Files') self._local_file_btn.setChecked(True) # Buttons for adding and removing stations self.add_btn = QPushButton('Add Station') self.add_btn.setStyleSheet(self.btn_style) self.add_btn.setCursor(self.hand) self.add_btn.released.connect(self.add_station) self.del_btn = QPushButton('Remove Station') self.del_btn.setStyleSheet(self.btn_style) self.del_btn.setCursor(self.hand) self.del_btn.released.connect(self.remove_station) # Create the slider for track length self.track_slider = QSlider(Qt.Horizontal) self.track_slider.setRange(0, 100) # self.track_slider.setTickInterval(10) # self.track_slider.setTickPosition(QSlider.TicksAbove) # Add the control buttons to the control box control_box.addWidget(self.volume_slider, 0,0, 1, 1) control_box.addWidget(self.volume_label, 0, 1, 1, 1) control_box.addWidget(self.play_btn, 0, 2, 1, 1) control_box.addWidget(stop_btn,0, 3, 1, 1) control_box.addWidget(prev_btn,0, 4, 1, 1) control_box.addWidget(next_btn, 0, 5, 1, 1) # Add station edit controls to station grid box station_box.addWidget(self.add_btn, 0, 0, 1, 1) station_box.addWidget(self.del_btn, 0, 1, 1, 1) station_box.addWidget(self.entry, 0, 2, 1, 3) # Add the radio buttons to the radio container radio_container.addWidget(self.radio_btn, 0, 0, 1, 1) radio_container.addWidget(self._local_file_btn, 0, 1, 1, 1) # Add the slider to the slider box slider_box.addWidget(self.duration_labelheader, 0, 0, 1, 1) slider_box.addWidget(self.duration_timer, 0, 1, 1, 1) slider_box.addWidget(self.track_slider,1, 0,1, 3) # Add widgets to info_frame/container info_container.addWidget(artist, 0, 0, 1, 1) info_container.addWidget(title, 1, 0, 1, 1) info_container.addWidget(album, 2, 0, 1, 1) info_container.addWidget(released, 3, 0, 1, 1) info_container.addWidget(genre, 4, 0, 1, 1) info_container.addWidget(self.artist, 0, 1, 1, 1) info_container.addWidget(self.title, 1, 1, 1, 1) info_container.addWidget(self.album, 2, 1, 1, 1) info_container.addWidget(self.released, 3, 1, 1, 1) info_container.addWidget(self.genre, 4, 1, 1, 1) info_container.addWidget(self.cover_art, 5, 0, 1, 3) info_container.addWidget(self.spacer, 6, 0, 1, 3) info_container.addWidget(self.spacer2, 7, 0, 1, 3) info_container.addWidget(radio_frame, 8, 0, 1, 3) # Add widgets and layout to container layout container.addWidget(status_label, 1, 0, 1, 1) container.addWidget(self.status_label, 1, 1, 1, 1) container.addWidget(track_label, 1, 2, 1, 1) container.addWidget(self.track_label, 1, 3, 1, 1) container.addWidget(self.get_btn, 1, 4, 1, 1) container.addWidget(clear_btn, 1, 5, 1, 1) container.addWidget(info_frame, 2, 0, 1, 4) container.addWidget(self.musiclist,2, 3, 1, 3) container.addWidget(self.station_frame, 3, 0, 1, 6) container.addWidget(slider_frame, 4, 0, 1, 3) container.addWidget(control_frame, 4, 3, 1, 3) widget = QWidget() widget.setLayout(container) self.setCentralWidget(widget) # Set channels for updates and changes happening in the gui self.volume_slider.valueChanged.connect(self._volume, self.volume_slider.value()) self.radio_btn.toggled.connect(self._music) self.player.metaDataChanged.connect(self._update) self.player.stateChanged.connect(self._state) self.player.positionChanged.connect(self._slider_pos) self.player.durationChanged.connect(self._duration) self.track_slider.valueChanged.connect(self._timer) self.musiclist.itemDoubleClicked.connect(self._doubleclick) # Method for adding stations def add_station(self): station = self.entry.text() if station and 'http://' in station or 'https://' in station: self.musiclist.addItem(station) self.playlist.addMedia(QMediaContent(QUrl(station))) else: message = QMessageBox.warning(self,'Warning!','Please use the correct \ format to add your station. \nExample: https://some/url/to/station/stream') self.entry.clear() # Method for removing stations def remove_station(self): if self.musiclist.count() > 0: row = self.musiclist.currentRow() item = self.musiclist.takeItem(row) del item self.playlist.removeMedia(row) else: message = QMessageBox.warning(self, 'Warning!', 'There is no more stations to remove!') # Method for playing a track if double clicked in the music list def _doubleclick(self): self.playlist.setCurrentIndex(self.musiclist.currentRow()) self.player.play() # Method for updating slider position def _slider_pos(self, position): self.track_slider.setValue(position) # Method for setting the range of the duration def _duration(self, duration): self.track_slider.setRange(0, duration) # Updates the duration timer def _timer(self): total_milliseconds = self.player.duration() total_seconds, total_milliseconds = divmod(total_milliseconds,1000) total_minutes, total_seconds = divmod(total_seconds,60) total_hours, total_minutes = divmod(total_minutes, 60) elapsed_milliseconds = self.track_slider.value() elapsed_seconds, elapsed_milliseconds = divmod(elapsed_milliseconds,1000) elapsed_minutes, elapsed_seconds = divmod(elapsed_seconds, 60) elapsed_hours, elapsed_minutes = divmod(elapsed_minutes, 60) self.duration_timer.setText(f'{elapsed_hours:02d}:{elapsed_minutes:02d}:{elapsed_seconds:02d} / {total_hours:02d}:{total_minutes:02d}:{total_seconds:02d}') # Checks player state and updates accorrdingly def _state(self): if self.player.state() == self.player.PlayingState: self.play_btn.setIcon(self.play_btn.style().standardIcon(QStyle.SP_MediaPause)) self.play_btn.released.connect(self.player.pause) self.status_label.setText('Now Playing') elif self.player.state() == self.player.PausedState: self.play_btn.setIcon(self.play_btn.style().standardIcon(QStyle.SP_MediaPlay)) self.play_btn.released.connect(self.player.play) self.status_label.setText('Now Paused') else: self.play_btn.setIcon(self.play_btn.style().standardIcon(QStyle.SP_MediaPlay)) self.play_btn.released.connect(self.player.play) self.status_label.setText('Now Stopped') # Methods for the next/prev actions def _prev(self): if self.playlist.previousIndex() == -1: self.playlist.setCurrentIndex(self.playlist.mediaCount()-1) else: self.playlist.previous() if self.playlist.currentIndexChanged: self.musiclist.setCurrentRow(self.playlist.currentIndex()) self.player.play() def _next(self): self.playlist.next() if self.playlist.currentIndex() == -1: self.playlist.setCurrentIndex(0) self.musiclist.setCurrentRow(self.playlist.currentIndex()) self.player.play() # Method for getting the music files def _files(self): files = QFileDialog.getOpenFileNames(None, 'Get Audio Files', filter='Audio Files (*.mp3 *.ogg *.wav)') for file in files[0]: self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(file))) try: self.track = MP3(file) self.musiclist.addItem(str(self.track['TIT2'])) except: self.track = self._truncate(file.rpartition('/')[2].rpartition('.')[0]) self.musiclist.addItem(self.track) self.musiclist.setCurrentRow(0) self.playlist.setCurrentIndex(0) # Method for updating much of the text information def _update(self): try: self.musiclist.setCurrentRow(self.playlist.currentIndex()) except: pass try: if self.player.isMetaDataAvailable(): if self.player.metaData(QMediaMetaData.AlbumArtist): self.artist.setText(self.player.metaData(QMediaMetaData.AlbumArtist)) if self.player.metaData(QMediaMetaData.Title): if self.radio_btn.isChecked(): info = self.player.metaData(QMediaMetaData.Title).split('-') self.artist.setText(self._truncate(info[0])) self.title.setText(self._truncate(info[1])) self.track_label.setText(info[1]) else: self.title.setText(self._truncate(self.player.metaData(QMediaMetaData.Title))) self.track_label.setText(self._truncate(self.player.metaData(QMediaMetaData.Title))) if self.player.metaData(QMediaMetaData.AlbumTitle): self.album.setText(self._truncate(self.player.metaData(QMediaMetaData.AlbumTitle))) if self.player.metaData(QMediaMetaData.Year): self.released.setText(f'{self.player.metaData(QMediaMetaData.Year)}') if self.player.metaData(QMediaMetaData.Genre): self.genre.setText(self.player.metaData(QMediaMetaData.Genre)) if self.player.metaData(QMediaMetaData.CoverArtImage): pixmap = QPixmap(self.player.metaData(QMediaMetaData.CoverArtImage)) pixmap = pixmap.scaled(328, 295) self.cover_art.setPixmap(pixmap) self.cover_art.setStyleSheet('padding: 5px;') else: self.artist.setText('') self.title.setText('') self.album.setText('') self.released.setText('') self.genre.setText('') self.cover_art.setPixmap(QPixmap()) self.track_label.setText('') except TypeError: pass # Method for shortening text def _truncate(self, text, length=25): if text: if len(text) <= length: return text else: return f"{' '.join(text[:length+1].split(' ')[0:-1])} ...." # Method for playing either radio or local audio files def _music(self): if self.radio_btn.isChecked(): self.station_frame.show() self.entry.setFocus() self.musiclist.clear() self.playlist.clear() self.get_btn.setEnabled(False) self.get_btn.setStyleSheet('''QPushButton{background-color: lightgray; color: black; font-size: 10pt; font-weight: 50; padding: 6px; margin-left: 6px;}''') stations = [ 'http://us4.internet-radio.com:8258/stream', 'http://us5.internet-radio.com:8267/stream', 'https://us9.maindigitalstream.com/ssl/bigrock991', 'https://playerservices.streamtheworld.com/api/livestream-redirect/KGFKAM.mp3', 'https://cob-ais.leanstream.co/CFJBFM-MP3', 'http://37.59.195.28:8045', 'https://playerservices.streamtheworld.com/api/livestream-redirect/WIRLAM.mp3', 'https://ais-sa2.cdnstream1.com/2383_128' ] for station in stations: self.playlist.addMedia(QMediaContent(QUrl(station))) self.musiclist.addItem(station) self.player.play() else: self._clear() self.get_btn.setEnabled(True) self.get_btn.setStyleSheet(self.btn_style) self.station_frame.hide() # Method for updating the volume def _volume(self, value): self.volume_label.setText(f'Volume: {value}') self.player.setVolume(value) # Method for clearing playlist, musiclist, and other data def _clear(self): self.playlist.clear() self.musiclist.clear() self.status_label.setText('Now Stopped') self.track_label.setText('') self._local_file_btn.setChecked(True) self.player.setVolume(70) self.volume_slider.setValue(70) self._update() def main(): app = QApplication(sys.argv) window = Window() window.show() sys.exit(app.exec()) if __name__ == '__main__': main()
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags