[développé sous Python v3.5 et PyQt5 v5.9 avec Windows 10]
Python, que certains considèrent encore comme un “langage de script” (!) dispose, grâce à des bibliothèques comme PyQt5, de possibilités importantes dans le domaine multimédia. J'ai eu alors envie d'avoir un lecteur de radios internet pour mon propre usage, et comme ça marche très bien, je vais vous en faire profiter!
Je n'ai pas utilisé d'instructions spécifiques à l'un des OS courants (Windows, Linux, MacOS), aussi je pense que ça fonctionnera sur ces 3 OS. Cependant, j'ai fait le développement sous Windows 10, et j'ai pu voir que sous Linux, il pouvait y avoir des subtilités d'installation à résoudre avant que ça marche… Je n'ai pas (encore) essayé sous Mac OS.
Vous pouvez trouver ici les différents fichiers dont on parle plus bas, sous forme d'archive compressée “zip”:
Contenu:
Je n'ai pas mis les scripts pour pyinstaller, mais il est facile de les récupérer ci-dessous par copier-coller. Je n'ai pas mis non plus les exécutables “.exe” qui sont générés par pyinstaller pour être sûr de ne pas diffuser de virus (on n'est plus sûr de rien de nos jours sur ce sujet…).
Voilà une version minimum. Vous voyez que la partie purement écoute prend… 3 lignes! Et en fait, il a fallu créer une petite fenêtre uniquement pour pouvoir arrêter le programme.
L'écoute de la radio internet utilise le module QtMultimedia de PyQt5.
Le programme est prévu pour être lancé avec l'url (l'adresse web) de la radio internet en argument de la ligne de commande. Avec un bureau graphique (Windows, Linux, MacOS), on peut créer une icône sur le bureau, qui lancera le programme avec la radio souhaitée en argument.
Bien que ce ne soit pas son but, ce programme accepte sans problème les fichiers audio locaux de type mp3! Dans le code, on regarde si le fichier existe sur disque, et si oui, on utilise QUrl.fromLocalFile(url) au lieu de QUrl(url), ce qui ajoutera "file:///" au début de l'adresse. Pour tenir compte de Windows, on en profite pour remplacer les éventuels '\' du chemin disque, par des '/'.
Voilà le code proposé, très largement documenté. Il s'appelle chez moi “radiowebmini.py (on peut mettre .pyw sous Windows pour éviter l'affichage de la console):
#!/usr/bin/python # -*- coding: utf-8 -*- # Python3 v3.5 PyQt5 v5.9 """ Lecteur basique d'une radio internet Auteur: Jean-Paul Vidal dit "Tyrtamos" (mai 2018) Pour les adresses web des radios francophones, voir par exemple: http://fluxradios.blogspot.fr/ Exemple pour la radio "Frequence 3": http://ice.stream.frequence3.net/frequence3-128.mp3 Pour télécharger une icône, voir par exemple: https://icones8.fr/icon/2308/tour-de-radio """ import sys import os from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QGridLayout, QMessageBox, QStyleFactory) from PyQt5.QtGui import QIcon from PyQt5.QtCore import Qt, QUrl from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent ############################################################################# class RadioWeb(QWidget): def __init__(self, qurlradio, parent=None): super().__init__(parent) self.setWindowTitle("Radio internet") self.resize(250,50) self.setAttribute(Qt.WA_DeleteOnClose, True) # ajoute un bouton pour arrêter le programme (et l'écoute) self.bouton = QPushButton("Arrêter", self) self.bouton.setStyleSheet("background-color:lightgreen;") self.bouton.clicked.connect(self.close) # fermeture layout = QGridLayout() layout.addWidget(self.bouton, 0, 0) # bouton au milieu de fenêtre self.setLayout(layout) # crée le player self.player = QMediaPlayer() # et lance l'écoute de la radio demandée self.player.setMedia(QMediaContent(qurlradio)) self.player.play() ############################################################################# if __name__ == "__main__": # active la bibliothèque graphique app = QApplication(sys.argv) # met un style pour toute l'application app.setStyle(QStyleFactory.create("Fusion")) # option: met une icone pour l'application icone = "icons8-tour-de-radio-50.png" icone = os.path.join(os.path.dirname(os.path.abspath(__file__)), icone) app.setWindowIcon(QIcon(icone)) # récupére l'argument en ligne de commande if len(sys.argv)>1: url = sys.argv[1] if os.path.exists(url): # l'url est un fichier audio local sur disque qurl = QUrl.fromLocalFile(url.replace('\\', '/')) else: # l'url est une adresse internet qurl = QUrl(url) else: QMessageBox.information(None, "Chargement de la radio", "Aucune radio n'a été demandée") app.quit() sys.exit() # lance la fenêtre pour l'écoute de la radio radioweb = RadioWeb(qurl) radioweb.show() # boucle de traitement des évènements et sortie du programme sys.exit(app.exec_())
Si ce programme s'appelle “radiowebmini.py”, il faudra pour écouter l'excellente radio “fréquence 3” faire dans une console (cmd.exe pour Windows):
python radiowebmini.py http://ice.stream.frequence3.net/frequence3-128.mp3
Il faut adapter cette ligne de code selon l'OS. Par exemple, sous Linux, il faut en général utiliser python3 pour avoir l'interpréteur Python version 3.x. etc…
Au lancement, la radio démarre au bout de quelques secondes (temps de mise en cache), et une petite fenêtre apparait dont la vocation est de permettre l'arrêt du programme (et donc de l'écoute).
Il s'agit ici d'une version minimale pour l'écoute d'une seule radio. Mais, on peut faire mieux en ajoutant une “playlist” qui permet à tout moment de sélectionner la radio qu'on a envie d'écouter parmi les radios disponibles. C'est l'objet du chapitre suivant.
Avec cette version plus complète et plus confortable, le lancement du programme affiche une fenêtre (QWidget) avec la liste des radios choisies (QTableWidget).
Un double-clic sur n'importe quelle radio de cette liste lancera son écoute. On peut aussi naviguer avec le clavier, et la touche “entrée” sélectionnera la radio à écouter et lancera son écoute.
Pour faire une pause dans l'écoute, on peut faire “clic droit ⇒ Pause” dans le petit menu popup, ou faire “Alt-P” au clavier. Pour reprendre, on fera “clic-droit ⇒ Reprendre” ou “Alt-R” au clavier.
Pour arrêter le programme (et donc l'écoute), on peut faire “clic-droit ⇒ Quitter” ou simplement cliquer sur la croix de la fenêtre ou encore faire “Alt-Q” au clavier.
Les radios sont dans le fichier “radioweb.txt” (encodé 'utf-8') et chaque ligne désigne une radio avec un format: “nom_radio | url_radio”. Le séparateur '|' a été choisi parce qu'il a peu de chance de se retrouver dans un nom de radio ou dans son adresse web. Voilà par exemple mon fichier radioweb.txt avec toutes les radios que j'ai retenues pour mon usage:
# liste des radios internet francophones. Voir ici par exemple: # http://fluxradios.blogspot.fr # format des lignes: nom_radio | url_radio # les lignes vides ou commençant par '#' ne comptent pas RadioSwissClassic | http://stream.srg-ssr.ch/m/rsc_fr/mp3_128 France Musique | http://direct.francemusique.fr/live/francemusique-midfi.mp3 RadioClassique | http://radioclassique.ice.infomaniak.ch/radioclassique-high.mp3 Nostalgie | http://cdn.nrjaudio.fm/audio1/fr/30601/mp3_128.mp3 Fréquence3 | http://ice.stream.frequence3.net/frequence3-128.mp3 NRJ | http://cdn.nrjaudio.fm/audio1/fr/30001/mp3_128.mp3 Skyrock | http://www.skyrock.fm/stream.php/tunein16_128mp3.mp3 Cherie FM | http://cdn.nrjaudio.fm/audio1/fr/30201/mp3_128.mp3 Rires et Chansons | http://cdn.nrjaudio.fm/audio1/fr/30401/mp3_128.mp3 Fun radio | http://streaming.radio.funradio.fr/fun-1-44-128 RTL2 | http://streaming.radio.rtl2.fr/rtl2-1-44-128 OuiFM | http://ouifm.ice.infomaniak.ch/ouifm-high.mp3 Mistral FM (Toulon) | http://mistralfm.ice.infomaniak.ch/mistralfm-high.mp3
Attention: ces adresses peuvent changer à tout moment! Alors, si une radio ne marche plus, cherchez une autre adresse sur le web, et mettez à jour votre fichier avec un simple éditeur de texte (avec encodage 'utf-8' pour les accents).
Même si ce n'est pas le but du programme, les fichiers “audio” locaux type mp3 sont acceptés avec le même format. On peut par exemple avoir la ligne suivante (cas de Windows: aucun problème pour avoir des espaces ou des caractères accentués dans le chemin, ou pour conserver les '\' qui seront neutralisés dans le code):
Titre du morceau | C:\chemin_vers_le_fichier\fichier.mp3
A noter que j'ai essayé le format traditionnel des playlists ”.m3u“ (et même ”.m3u8“ pour l'encodage 'utf-8') créées initialement pour le lecteur WinAmp. Malheureusement, QtMultimedia intègre bien les url sans générer d'erreur, mais pas les noms des radios qui sont pourtant nécessaires ici. Les versions suivantes de PyQt5 ajouteront peut-être cette fonctionnalité?
Petite particularité du code proposé: il peut être exécuté directement, mais aussi importé pour l'utilisation à partir de la zone de notification (voir chapitre suivant). La variable booléenne globale NOTIF permet de tenir compte des 2 cas, ce qui permet de n'avoir qu'un seule page de codes pour les 2 utilisations.
Voilà le code proposé, largement commenté. Le programme s'appelle chez moi “radioweb.py” (on peut mettre .pyw sous Windows pour éviter l'affichage de la console):
#!/usr/bin/python # -*- coding: utf-8 -*- # Python3 v3.5 PyQt5 v5.9 """ Lecteur avec playlist d'une radio internet Auteur: Jean-Paul Vidal dit "Tyrtamos" (mai 2018) Les noms et adresses des radios sont dans le fichier radioweb.txt Pour les adresses web des radios francophones, voir par exemple: http://fluxradios.blogspot.fr/ Aussi sur les sites web des radios. Exemple pour "Frequence 3": http://ice.stream.frequence3.net/frequence3-128.mp3 Pour télécharger une icône, voir par exemple: https://icones8.fr/icon/2308/tour-de-radio """ import sys import os from PyQt5.QtWidgets import (QApplication, QWidget, QTableWidget, QMenu, QTableWidgetItem, QAbstractItemView, QGridLayout, QMessageBox, QStyleFactory, QAction) from PyQt5.QtGui import QFont, QIcon from PyQt5.QtCore import (Qt, QUrl, pyqtSlot, QLocale, QLibraryInfo, QTranslator, qInstallMessageHandler) from PyQt5.QtMultimedia import QMediaPlayer, QMediaPlaylist, QMediaContent ############################################################################## # variables globales # indique si ce script est exécuté directement ou seulement importé NOTIF = True ############################################################################## def chargeradios(fichieradios): """charge les radios du fichier radioweb.txt format de chaque ligne de ce fichier: "nom | url" retourne une liste de listes: [..., [nom, url], ...] """ if not os.path.exists(fichieradios): raise ValueError("Le fichier 'radioweb.txt' n'est pas trouvé") radios = [] with open(fichieradios, "r", encoding="utf-8") as fs: for ligne in fs: ligne = ligne.strip() if ligne=="": continue # ligne vide => non utilisée if ligne.startswith('#'): continue # ligne commentaire => non utilisée nom, url = ligne.split('|') radios.append([nom.rstrip(), url.lstrip()]) return radios ############################################################################## class RadioWeb(QWidget): """fenêtre qui présente la liste des radios disponibles et qui permet de sélectionner celle qu'on veut écouter """ #========================================================================= def __init__(self, radios=(), parent=None): super().__init__(parent) self.resize(250, 500) self.setWindowTitle("Radio Internet") # fait que la fenêtre sera détruite après sa fermeture self.setAttribute(Qt.WA_DeleteOnClose, True) # stocke la liste des radios [..., [nom, url], ...] self.radios = radios # définit les polices de caractères à utiliser famille = "DejaVu Sans" taille = 9 # police normale self.font = QFont() self.font.setFamily(famille) self.font.setPointSize(taille) # police avec gras et italique pour la radio sélectionnée self.fontselect = QFont() self.fontselect.setFamily(famille) self.fontselect.setPointSize(taille) self.fontselect.setBold(True) self.fontselect.setItalic(True) # Crée un QTableWidget pour afficher les radios disponibles self.tw = QTableWidget(self) self.tw.setFont(self.font) self.nbrow, self.nbcol = len(self.radios), 1 self.tw.setRowCount(self.nbrow) self.tw.setColumnCount(self.nbcol) # positionne le QTableWidget dans la fenêtre posit = QGridLayout() posit.addWidget(self.tw, 0, 0) self.setLayout(posit) # un double-clic lancera la radio sélectionnée self.tw.cellDoubleClicked.connect(self.selection) # peuple le QTableWidget avec les noms des radios (lecture seule) for row in range(0, self.nbrow): item = QTableWidgetItem(self.radios[row][0]) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.tw.setItem(row, 0, item) # met un titre en haut de la colonne affichée self.tw.setHorizontalHeaderItem(0, QTableWidgetItem("Radios")) # ajuste pour que la colonne affichée prenne toute la place horiz. h = self.tw.horizontalHeader() h.setStretchLastSection(True) # pour faire la sélection d'une seule ligne à la fois self.tw.setSelectionMode(QAbstractItemView.SingleSelection) # la sélection pourra être faite avec la souris et le clavier self.tw.setFocusPolicy(Qt.StrongFocus) # crée la playlist à partir de la liste des radios demandées self.playlist = QMediaPlaylist() for _, urlradio in radios: if os.path.exists(urlradio): # l'url est un fichier audio local sur disque qurlradio = QUrl.fromLocalFile(urlradio.replace('\\', '/')) else: # l'url est une adresse internet qurlradio = QUrl(urlradio) self.playlist.addMedia(QMediaContent(qurlradio)) # crée le player self.player = QMediaPlayer() # met la playlist dans le player self.player.setPlaylist(self.playlist) # met en place un menu 'popup' pour le QTableWidget self.tw.setContextMenuPolicy(Qt.CustomContextMenu) self.tw.customContextMenuRequested.connect(self.popupmenutphotos) # met le focus sur le QTableWidget et la case courante sur [0,0] self.tw.setFocus() self.tw.setCurrentCell(0, 0) # ======================================================================= @pyqtSlot("QPoint") def popupmenutphotos(self, position): """crée et affiche le menu popup du QTableWidget """ popupmenu = QMenu(self) actionPause = QAction("Pause", self) actionPause.triggered.connect(self.pause) popupmenu.addAction(actionPause) actionReprendre = QAction("Reprendre", self) actionReprendre.triggered.connect(self.reprendre) popupmenu.addAction(actionReprendre) popupmenu.addSeparator() if NOTIF: actionCacher = QAction("Cacher", self) actionCacher.triggered.connect(self.hide) popupmenu.addAction(actionCacher) else: actionQuitter = QAction("Quitter", self) actionQuitter.triggered.connect(self.close) popupmenu.addAction(actionQuitter) popupmenu.exec_(self.tw.viewport().mapToGlobal(position)) #========================================================================= @pyqtSlot() def pause(self): """Méthode lancée par le choix 'pause' du menu popup """ self.player.pause() #========================================================================= @pyqtSlot() def reprendre(self): """Méthode lancée par le choix 'reprendre' du menu """ rowpl = self.player.playlist().currentIndex() # relance la lecture seulement si une radio a déjà été lue avant if self.tw.item(rowpl, 0).font() == self.fontselect: self.player.play() #========================================================================= @pyqtSlot(int, int) def selection(self, row, _): """Méthode lancée par un double-clic sur la radio sélectionnée => déclenche la lecture de cette radio (=> nom en gras-italique) """ self.player.playlist().setCurrentIndex(row) self.player.play() # met en police normale toutes les radios for i in range(0, self.nbrow): self.tw.item(i, 0).setFont(self.font) # et met en gras-italique la radio sélectionnée self.tw.item(row, 0).setFont(self.fontselect) #========================================================================= @pyqtSlot("QEvent") def keyPressEvent(self, event): """permet de sélectionner une radio au clavier """ if event.key()==Qt.Key_Enter or event.key()==Qt.Key_Return: # sélectionne la radio avec l'une des 2 touches "entrée" self.selection(self.tw.currentRow(), 0) event.accept() elif event.key()==Qt.Key_P and (event.modifiers() and Qt.Key_Alt): # Alt-P => met en pause au clavier la radio écoutée self.pause() event.accept() elif event.key()==Qt.Key_R and (event.modifiers() and Qt.Key_Alt): # Alt-R => reprend la lecture de la radio mise en pause avant self.reprendre() event.accept() elif event.key()==Qt.Key_Q and (event.modifiers() and Qt.Key_Alt): # Alt-Q => ferme ou cache la fenêtre self.close() event.accept() else: # autres cas: transmet l'évènement clavier à l'ancêtre super().keyPressEvent(event) #========================================================================= @pyqtSlot("QEvent") def closeEvent(self, event): """selon l'utilisation, ferme ou cache la fenêtre """ if NOTIF: # cas d'importation pour QSystemTrayIcon => cache la fenêtre self.hide() event.ignore() else: # cas d'exécution directe => ferme la fenêtre event.accept() ############################################################################# if __name__ == "__main__": # indique que ce script est exécuté directement NOTIF = False #======================================================================== # initialise la bibliothèque graphique app = QApplication(sys.argv) #======================================================================== # Répertoire d'exécution avec ou sans pyinstaller if getattr(sys, 'frozen', False): REPEXE = sys._MEIPASS # programme traité par pyinstaller else: REPEXE = os.path.dirname(os.path.abspath(__file__)) # prog. normal #======================================================================== # met la même icône pour toutes les fenêtres du programme icone = "icons8-tour-de-radio-50.png" app.setWindowIcon(QIcon(os.path.join(REPEXE, icone))) #======================================================================== # met un style pour toute l'application if "Fusion" in [st for st in QStyleFactory.keys()]: app.setStyle(QStyleFactory.create("Fusion")) elif sys.platform=="win32": app.setStyle(QStyleFactory.create("WindowsVista")) elif sys.platform=="linux": app.setStyle(QStyleFactory.create("gtk")) elif sys.platform=="darwin": app.setStyle(QStyleFactory.create("macintosh")) app.setPalette(QApplication.style().standardPalette()) #======================================================================== # assure la traduction automatique du conversationnel à la locale locale = QLocale.system().name() translator = QTranslator() reptrad = QLibraryInfo.location(QLibraryInfo.TranslationsPath) translator.load("qtbase_" + locale, reptrad) # qtbase_fr.qm app.installTranslator(translator) #======================================================================== # charge les radios du fichier radioweb.txt replancement = os.path.dirname(os.path.abspath(__file__)) fichieradios = os.path.join(replancement,"radioweb.txt") try: listeradios = chargeradios(fichieradios) except ValueError: QMessageBox.information(None, "Chargement des radios", "Le fichier 'radioweb.txt' n'est pas trouvé") app.quit() sys.exit() if len(listeradios)==0: QMessageBox.information(None, "Chargement des radios", "Le fichier 'radioweb.txt' est vide") app.quit() sys.exit() #======================================================================== # lance et affiche la fenêtre radioweb = RadioWeb(listeradios) radioweb.show() #======================================================================== # boucle de traitement des évènements sys.exit(app.exec_())
On peut convertir le programme précédent pour avoir un fichier exécutable autonome, avec tout ce qui est nécessaire à son bon fonctionnement (interpréteur Python et bibliothèques utilisées).
Sous Windows, voilà la ligne de commande qu'on peut utiliser sous forme d'un fichier de commande pour la console cmd.exe, qui s'appelle chez moi: “radioweb.bat” et qui fabrique un fichier exécutable unique “radioweb.exe:
SET programme=radioweb IF EXIST build_onefile RMDIR /S /Q build_onefile IF EXIST dist_onefile RMDIR /S /Q dist_onefile pyinstaller ^ --clean ^ --noconfirm ^ --noconsole ^ --onefile ^ --noupx ^ --distpath ".\dist_onefile" ^ --workpath ".\build_onefile" ^ --add-data ".\icons8-tour-de-radio-50.png;." ^ --add-data "E:\Programmes\Python35\Lib\site-packages\PyQt5\Qt\translations;PyQt5\Qt\translations" ^ --icon ".\icons8-tour-de-radio-50.ico" ^ %programme%.py PAUSE
Il faut, bien sûr, adapter les noms et adresses selon la configuration qu'on a, et ne pas oublier de copier à la main le fichier des radios “radioweb.txt” dans le même répertoire.
Comme pyinstaller est multiplateforme, on devrait pouvoir faire quelque chose de similaire sous Linux et sous MacOS, mais je n'ai pas encore essayé.
La version ci-dessus est confortable, mais elle prend la place d'un programme normal dans la barre des tâches. Une autre solution plus intéressante est de l'intégrer dans la zone de notification.
Dans ce cas, il y a une icône spécifique qui s'ajoute dans cette zone. Voilà comment ça marche:
En plus, on peut faire que ce programme soit lancé automatiquement à l'allumage du PC. Sous Windows 10, voilà comment on peut faire: mettre un raccourci du programme dans le répertoire suivant:
C:\Users\utilisateur\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
Voilà le code proposé pour intégrer le programme précédent dans la zone de notification. Il s'appelle chez moi “radioweb_tray.py” (on peut mettre .pyw sous Windows pour éviter l'affichage de la console):
#! /usr/bin/python # -*- coding: utf-8 -*- # Python3 v3.5, PyQt5 v5.9 r""" Lancement dans la zone de notification d'un lecteur avec playlist d'une radio internet Auteur: Jean-Paul Vidal dit "Tyrtamos" (mai 2018) Action sur l'icone de notification: clic gauche sur l'icone => affiche la fenêtre du programme clic droit sur l'icone => menu contextuel: pause: met la radio écoutée en pause reprendre: remet la radio en écoute quitter: arrête le programme Les noms et adresses des radios sont dans le fichier radioweb.txt Pour les adresses web des radios francophones, voir par exemple: http://fluxradios.blogspot.fr/ Aussi sur les sites web des radios. Exemple pour "Frequence 3": http://ice.stream.frequence3.net/frequence3-128.mp3 Pour télécharger une icône, voir par exemple: https://icones8.fr/icon/2308/tour-de-radio Pour exécuter automatiquement au lancement du PC sous Windows, ajouter un raccourcis ici (mettre le bon chemin avant AppData): C:\Users\utilisateur\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup """ import sys import os from PyQt5.QtWidgets import (QApplication, QMenu, QMessageBox, QStyleFactory, QSystemTrayIcon, QAction) from PyQt5.QtGui import QIcon from PyQt5.QtCore import (QLocale, QLibraryInfo, QTranslator, pyqtSlot, qInstallMessageHandler) from radioweb import chargeradios, RadioWeb ############################################################################# def lancefenetre(fichieradios): """charge les radios et lance la fenêtre sans l'afficher """ # charge les radios du fichier radioweb.txt try: listeradios = chargeradios(fichieradios) except ValueError: QMessageBox.information(None, "Chargement des radios", "Le fichier 'radioweb.txt' n'est pas trouvé") app.quit() sys.exit() if len(listeradios)==0: QMessageBox.information(None, "Chargement des radios", "Le fichier 'radioweb.txt' est vide") app.quit() sys.exit() # retourne la variable d'instance de la fenêtre à lancer return RadioWeb(listeradios) ############################################################################# def affichefenetre(fen): """complément d'affichage de la fenêtre du programme """ fen.tw.setFocus() fen.tw.setCurrentCell(0, 0) ############################################################################# class SystemTrayIcon(QSystemTrayIcon): #======================================================================== def __init__(self, qicone, parent=None): super().__init__(qicone, parent) # ajoute une bulle d'information si on laisse la souris sur l'icône self.setToolTip("Radio Internet\n" + \ "Cliquez sur l'icone pour afficher") # un clic gauche sur l'icône affiche la fenêtre self.activated.connect(self.affichefen) # un clic gauche sur l'étiquette le lancement affiche la fenêtre self.messageClicked.connect(self.affichefen) # fichier des radios replancement = os.path.dirname(os.path.abspath(__file__)) fichieradios = os.path.join(replancement, "radioweb.txt") # lance la fenêtre sans affichage self.fen = lancefenetre(fichieradios) # initialise le menu self.initmenu() #======================================================================== def initmenu(self): """crée et affiche le menu du systemtray (clic droit sur l'icône) """ menu = QMenu() actionPause = QAction('Pause', self) actionPause.triggered.connect(self.fen.pause) menu.addAction(actionPause) actionReprendre = QAction('Reprendre', self) actionReprendre.triggered.connect(self.fen.reprendre) menu.addAction(actionReprendre) menu.addSeparator() actionQuitter = QAction('Quitter', self) actionQuitter.triggered.connect(self.quitter) menu.addAction(actionQuitter) self.setContextMenu(menu) #======================================================================== def affichefen(self, raison=None): """clic gauche sur l'icone ou sur l'étiquette du lancement => affiche la fenêtre au dessus des autres """ if raison==QSystemTrayIcon.Trigger or raison==None: self.fen.showNormal() # affiche en mode fenêtre self.fen.activateWindow() # affiche au dessus des autres fenêtres # pour d'éventuels compléments d'affichage (ex: setFocus) affichefenetre(self.fen) #======================================================================== @pyqtSlot() def quitter(self): """demande d'arrêt du programme tray """ reponse = QMessageBox.question(None, "Confirmez!", "Voulez-vous vraiment quitter?", QMessageBox.Yes, QMessageBox.No) if reponse == QMessageBox.Yes: # ferme la fenêtre de sélection des radios si encore ouverte try: self.fen.close() except Exception: pass # ferme l'application et arrête l'exécution app.quit() sys.exit() ############################################################################# if __name__ == '__main__': #======================================================================== # Répertoire d'exécution avec ou sans pyinstaller (onedir ou onefile) if getattr(sys, 'frozen', False): REPEXE = sys._MEIPASS # programme traité par pyinstaller else: REPEXE = os.path.dirname(os.path.abspath(__file__)) # prog. normal #======================================================================== # active la bibliothèque graphique app = QApplication(sys.argv) #======================================================================== # vérifie qu'il y a un systemtray disponible (sinon => arrêt) if not QSystemTrayIcon.isSystemTrayAvailable(): QMessageBox.critical(None, "Systray", "Le système de notification n'est pas disponible sur cet OS") app.quit() sys.exit(1) #======================================================================== # met la même icône pour toutes les fenêtres du programme qicone = QIcon(os.path.join(REPEXE, "icons8-tour-de-radio-50.png")) app.setWindowIcon(qicone) #======================================================================== # met des tooltips à fond jaune clair dans toute l'application app.setStyleSheet("QToolTip {background-color: #ffff99; border: 1px solid black}") #======================================================================== # style pour toute l'application selon l'OS if "Fusion" in [st for st in QStyleFactory.keys()]: app.setStyle(QStyleFactory.create("Fusion")) elif sys.platform=="win32": app.setStyle(QStyleFactory.create("WindowsVista")) elif sys.platform=="linux": app.setStyle(QStyleFactory.create("gtk")) elif sys.platform=="darwin": app.setStyle(QStyleFactory.create("macintosh")) app.setPalette(QApplication.style().standardPalette()) #======================================================================== # indispensable pour utiliser QSystemTrayIcon # sinon: arret complet après fermeture d'un simple messagebox app.setQuitOnLastWindowClosed(False) #======================================================================== # assure la traduction automatique du conversationnel à la locale locale = QLocale.system().name() translator = QTranslator() reptrad = QLibraryInfo.location(QLibraryInfo.TranslationsPath) translator.load("qtbase_" + locale, reptrad) # qtbase_fr.qm app.installTranslator(translator) #======================================================================== # lance le tray trayIcon = SystemTrayIcon(qicone) # bulle: variable globale trayIcon.show() #-------------------------------------------------------------------- # message d'information affiché 1 seconde si l'OS le supporte # sous Windows, l'activation nécessite dans le registre: # HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ # Explorer\Advanced\EnableBalloonTips => dword:0x00000001 # et sa désactivation: ..\EnableBalloonTips => dword:0x00000000 if trayIcon.supportsMessages(): trayIcon.showMessage("Radio Internet", "Cliquez sur l'icone pour afficher", QSystemTrayIcon.Information, 1000) # temps d'affichage ignoré sous Windows #======================================================================== # boucle de traitement des évènements sys.exit(app.exec_())
De même que dans le chapitre précédent, on peut convertir le programme pour avoir un exécutable autonome, avec tout ce qui est nécessaire à son bon fonctionnement (interpréteur Python et bibliothèques utilisées).
Sous Windows, voilà la ligne de commande qu'on peut utiliser sous forme d'un fichier de commande pour la console cmd.exe qui s'appelle chez moi: “radioweb_tray.bat” et qui fabrique un fichier exécutable unique “radioweb_tray.exe:
SET programme=radioweb_tray IF EXIST build_tray_onefile RMDIR /S /Q build_tray_onefile IF EXIST dist_tray_onefile RMDIR /S /Q dist_tray_onefile pyinstaller ^ --clean ^ --noconfirm ^ --noconsole ^ --onefile ^ --noupx ^ --distpath ".\dist_tray_onefile" ^ --workpath ".\build_tray_onefile" ^ --add-data ".\icons8-tour-de-radio-50.png;." ^ --add-data "E:\Programmes\Python35\Lib\site-packages\PyQt5\Qt\translations;PyQt5\Qt\translations" ^ --icon ".\icons8-tour-de-radio-50.ico" ^ %programme%.py PAUSE
Il faut, bien sûr, adapter les noms et adresses selon la configuration qu'on a, et ne pas oublier de copier à la main le fichier des radios “radioweb.txt” dans le même répertoire.
Comme pyinstaller est multiplateforme, on devrait pouvoir faire quelque chose de similaire sous Linux et sous Mac OS, mais je n'ai pas encore essayé.
Bonne écoute! Et amusez-vous bien!