Skip to content

Instantly share code, notes, and snippets.

@Axel-Erfurt
Created December 24, 2024 17:51
Show Gist options
  • Save Axel-Erfurt/67ffc276aeb3e133d8675f4f93bae8f3 to your computer and use it in GitHub Desktop.
Save Axel-Erfurt/67ffc276aeb3e133d8675f4f93bae8f3 to your computer and use it in GitHub Desktop.
Python Gtk4 AudioPlayer
#!/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)
@Axel-Erfurt
Copy link
Author

Drag & Drop Audio files or folder with audio files

24_12_24_18_46_18

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment