Created
December 24, 2024 17:51
-
-
Save Axel-Erfurt/67ffc276aeb3e133d8675f4f93bae8f3 to your computer and use it in GitHub Desktop.
Python Gtk4 AudioPlayer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
import sys | |
import gi | |
import time | |
gi.require_versions({"Gtk": "4.0", "Gdk": "4.0","Adw": "1"}) | |
from gi.repository import Gtk, Gdk, GObject, Gio, Adw, GLib | |
from pathlib import Path | |
from mutagen.id3 import ID3, TIT2 | |
from mutagen.flac import FLAC | |
from os import walk, path | |
import warnings | |
warnings.filterwarnings("ignore") | |
class MainWindow(Gtk.ApplicationWindow): | |
def __init__(self, *args, **kwargs): | |
super().__init__(title="Audio Player", *args, **kwargs) | |
self.set_default_size(600, 300) | |
self.set_size_request(600, 400) | |
self.song_list = [] | |
self.song_index = 0 | |
self.vol = 1.0 | |
self.selected_song = "" | |
self.media_stream = None | |
self.ext = [".mp3", ".wav", ".ogg", ".m4a", ".aiff", ".flac"] | |
self.current_folder = Gio.File.new_for_path( | |
GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_MUSIC) | |
) | |
self.file_filter_audio = Gtk.FileFilter() | |
self.file_filter_audio.set_name("Audio Files") | |
mime_types = [ | |
"audio/mpeg", | |
"audio/mp4", | |
"audio/x-aiff", | |
"audio/ogg", | |
"audio/vorbis", | |
"audio/wav" | |
] | |
for audio in mime_types: | |
self.file_filter_audio.add_mime_type(audio) | |
self.header_bar = Gtk.HeaderBar.new() | |
self.set_titlebar(titlebar=self.header_bar) | |
btn_clear = Gtk.Button(icon_name="edit-clear-symbolic", css_classes=["image-button","flat"]) | |
btn_clear.set_tooltip_text("Clear List") | |
btn_clear.connect("clicked", self.clear_list) | |
self.header_bar.pack_end(btn_clear) | |
btn_open = Gtk.Button(icon_name="document-open-symbolic", css_classes=["image-button","flat"]) | |
btn_open.set_tooltip_text("Open File(s)") | |
btn_open.connect("clicked", self.open_file) | |
self.header_bar.pack_end(btn_open) | |
self.btn_mute = Gtk.Button.new_from_icon_name("audio-volume-high-symbolic") | |
self.btn_mute.set_tooltip_text("Mute Audio") | |
self.btn_mute.connect("clicked", self.mute_audio) | |
self.header_bar.pack_end(self.btn_mute) | |
btn_next = Gtk.Button.new_from_icon_name("go-next-symbolic") | |
btn_next.set_tooltip_text("next song") | |
btn_next.connect("clicked", self.on_song_next) | |
self.header_bar.pack_end(btn_next) | |
btn_previous = Gtk.Button.new_from_icon_name("go-previous-symbolic") | |
btn_previous.set_tooltip_text("previous song") | |
btn_previous.connect("clicked", self.on_song_previous) | |
self.header_bar.pack_end(btn_previous) | |
self.m_controls = Gtk.MediaControls() | |
vbox = Gtk.Box(orientation=1) | |
self.listbox = Gtk.ListBox() | |
self.listbox.connect("row-selected", self.on_row_activated) | |
scrwin = Gtk.ScrolledWindow(vexpand = True) | |
scrwin.set_child(self.listbox) | |
vbox.append(scrwin) | |
vbox.append(self.m_controls) | |
self.set_child(vbox) | |
drop_controller = Gtk.DropTarget.new( | |
type=GObject.TYPE_NONE, actions=Gdk.DragAction.COPY | |
) | |
drop_controller.set_gtypes([self.listbox, Gdk.FileList, str]) | |
drop_controller.connect('drop', self.on_drop) | |
self.add_controller(drop_controller) | |
def clear_list(self, *args): | |
self.song_list = [] | |
self.listbox.bind_model(None, self.create_widget_func) | |
print("Playlist cleared") | |
def on_drop(self, _ctrl, value, _x, _y): | |
if isinstance(value, Gdk.FileList): | |
files = value.get_files() | |
if Path(files[0].get_path()).is_dir(): | |
self.dir_to_list(files) | |
else: | |
for file in files: | |
extension = f'.{file.get_path().rsplit(".")[-1]}' | |
if extension in self.ext: | |
name = self.read_tag(Path(file.get_path())) | |
lbl = Gtk.Label(label=name) | |
self.listbox.append(lbl) | |
self.song_list.append(file.get_path()) | |
def read_tag(self, path, *args): | |
if str(path).endswith(".mp3"): | |
audio = ID3(path) | |
try: | |
title = audio["TIT2"].text[0] | |
except: | |
title = Path(path).stem | |
elif str(path).endswith(".flac"): | |
audio = FLAC(path) | |
try: | |
title = audio["TITLE"][0] | |
except: | |
title = Path(path).stem | |
return title | |
def dir_to_list(self, files, *args): | |
dir_path = files[0].get_path() | |
for root, dirs, files in walk(files[0].get_path(), topdown = False): | |
for file in files: | |
extension = file.rsplit(".")[-1] | |
if extension in f".{self.ext}": | |
filepath = path.join(root, file) | |
name = self.read_tag(filepath) | |
lbl = Gtk.Label(label=name) | |
self.listbox.append(lbl) | |
self.song_list.append(filepath) | |
def files_to_list(self, files, *args): | |
### audio files to list | |
for file in files: | |
extension = f'.{file.get_path().rsplit(".")[-1]}' | |
if extension in self.ext: | |
name = self.read_tag(file) | |
lbl = Gtk.Label(label=name) | |
self.listbox.append(lbl) | |
self.song_list.append(file.get_path()) | |
def volume_changed(self, wdg, *args): | |
if wdg.get_volume() != 0: | |
self.vol = wdg.get_volume() | |
print(f"Volume: {self.vol:.2}") | |
def on_row_activated(self, listbox, listboxrow): | |
if listboxrow: | |
self.song_index = listboxrow.get_index() | |
song = listboxrow.get_child().get_text() | |
self.selected_song = song | |
self.play_audio(self.song_list[self.song_index]) | |
def mute_audio(self, wdg, *args): | |
if self.media_stream.get_volume() == 0: | |
self.media_stream.set_volume(self.vol) | |
self.btn_mute.set_icon_name("audio-volume-high-symbolic") | |
self.btn_mute.set_tooltip_text("mute Audio") | |
else: | |
self.media_stream.set_volume(0) | |
self.btn_mute.set_icon_name("audio-volume-muted-symbolic") | |
self.btn_mute.set_tooltip_text("unmute Audio") | |
def open_file(self, *args): | |
self.show_open_dialog() | |
def show_open_dialog(self): | |
self.dialog = Gtk.FileChooserNative.new("Open", self, Gtk.FileChooserAction.OPEN, "Open", "Cancel") | |
self.dialog.set_current_folder(self.current_folder) | |
self.dialog.add_filter(self.file_filter_audio) | |
self.dialog.set_transient_for(self) | |
self.dialog.set_select_multiple(True) | |
self.dialog.connect("response", self.on_open_dialog_response) | |
self.dialog.show() | |
def on_open_dialog_response(self, dialog, response_id): | |
if response_id == Gtk.ResponseType.ACCEPT: | |
self.song_list = [] | |
filename = dialog.get_files() | |
self.listbox.bind_model(filename, self.create_widget_func) | |
dialog.destroy() | |
def create_widget_func(self,item): | |
path = item.get_path() | |
name = path.split("/")[-1].split(".")[-2] | |
self.song_list.append(path) | |
label=Gtk.Label(label=name) | |
return label | |
def play_audio(self, file, *args): | |
if self.media_stream != None: | |
self.media_stream.pause() | |
self.media_stream = Gtk.MediaFile.new_for_filename(file) | |
self.m_controls.set_media_stream(self.media_stream) | |
self.media_stream.connect("notify::ended", self.on_song_ended) | |
self.media_stream.connect("notify::volume", self.volume_changed) | |
self.set_title(self.selected_song) | |
self.media_stream.play() | |
time.sleep(1) | |
self.media_stream.set_volume(self.vol) | |
print(f"playing {self.selected_song}") | |
def on_prepare(self, file, *args): | |
print(file.is_prepared()) | |
print(file.get_duration()) | |
def on_song_ended(self, *args): | |
row_index = self.song_index | |
row = self.listbox.get_selected_row() | |
next_row = self.listbox.get_row_at_index(row_index + 1) | |
if row.get_index() < len(self.song_list) - 1: | |
self.listbox.select_row(next_row) | |
def on_song_next(self, *args): | |
row_index = self.song_index | |
row = self.listbox.get_selected_row() | |
next_row = self.listbox.get_row_at_index(row_index + 1) | |
if row.get_index() < len(self.song_list) - 1: | |
self.listbox.select_row(next_row) | |
def on_song_previous(self, *args): | |
row_index = self.song_index | |
row = self.listbox.get_selected_row() | |
next_row = self.listbox.get_row_at_index(row_index - 1) | |
if row.get_index() > 0: | |
self.listbox.select_row(next_row) | |
class MyApp(Adw.Application): | |
def __init__(self, **kwargs): | |
super().__init__(**kwargs) | |
self.connect("activate", self.on_activate) | |
self.connect("open", self.on_activate) | |
self.set_flags(Gio.ApplicationFlags.HANDLES_OPEN) | |
self.win = None | |
def on_activate(self, app, *args, **kwargs): | |
self.win = MainWindow(application=app) | |
self.win.present() | |
app = MyApp() | |
sm = app.get_style_manager() | |
sm.set_color_scheme(Adw.ColorScheme.FORCE_DARK) | |
app.run(sys.argv) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Drag & Drop Audio files or folder with audio files