sonist-gtk/window.py

288 lines
6.7 KiB
Python
Raw Normal View History

2023-08-17 00:06:21 +08:00
#!/usr/bin/env python3
import gi, sys, os, mutagen, threading
2023-08-23 20:36:52 +08:00
# from pprint import pprint as print
2023-08-17 00:06:21 +08:00
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf, GObject
2023-08-17 00:06:21 +08:00
from utils import blur_image, pic_to_pixbuf, base64_to_pixbuf
2023-08-17 20:51:59 +08:00
from ui.image import ScaleImage
from ui.slider import Slider
from ui.image_button import ImageButton
from ui.text import TextBox
2023-08-18 19:04:51 +08:00
from ui.ctrl_box import CtrlBox
from ui.timebar import Timebar
2023-08-22 18:52:18 +08:00
from ui.option_menu import OptionMenu
2023-08-17 20:51:59 +08:00
# 定义一个修饰器, 用于将当前方法转到主线程中运行 (子线程中调用方法时)
def idle(func):
def wrapper(*args):
GObject.idle_add(func, *args)
return wrapper
2023-08-17 20:51:59 +08:00
2023-08-17 00:06:21 +08:00
class SonistWindow(Gtk.Window):
2023-08-23 20:36:52 +08:00
app = None
mpd = None
stat = {}
2023-08-18 19:04:51 +08:00
def __init__(self, app):
2023-08-17 00:06:21 +08:00
Gtk.Window.__init__(self)
2023-08-18 19:04:51 +08:00
self.app = app
2023-08-23 20:36:52 +08:00
self.mpd = app.mpd
2023-08-18 19:04:51 +08:00
2023-08-23 20:36:52 +08:00
self.connect("destroy", self.quited)
2023-08-18 19:04:51 +08:00
self.mpd.connect('offline', lambda o: self.reset_player())
self.mpd.connect('online', lambda o: self.sync_state(None, None, True))
self.mpd.connect('state_changed', lambda o, stat: self.update_play_stat(stat == 'play'))
self.mpd.connect('song_changed', lambda o, stat, song: self.sync_state(stat, song, False))
self.mpd.connect('playing', lambda o, stat, song: self.update_playtime(stat))
2023-08-21 19:06:22 +08:00
2023-08-17 20:51:59 +08:00
self.set_name('SonistWindow')
self.set_default_size(320, 384)
2023-08-18 19:04:51 +08:00
self.set_resizable(False)
2023-08-17 20:51:59 +08:00
self.set_wmclass('Sonist', 'Sonist')
self.set_opacity(0.9)
# self.set_keep_above(True)
# album_img = './usr/share/sonist/album.png'
album_img = './usr/share/sonist/avatar.jpg'
self.set_background_image(album_img)
layout = Gtk.Layout()
2023-08-18 00:09:43 +08:00
# 菜单按钮
2023-08-22 18:52:18 +08:00
menu_btn = ImageButton('./usr/share/sonist/menu.png')
2023-08-24 15:25:03 +08:00
popup_menu = OptionMenu(app)
2023-08-22 18:52:18 +08:00
menu_btn.connect('clicked', lambda w: popup_menu.show(w))
layout.put(menu_btn, 276, 6)
2023-08-17 20:51:59 +08:00
# 唱片
disk = ScaleImage('./usr/share/sonist/disk.png')
2023-08-18 19:04:51 +08:00
handler = ScaleImage('./usr/share/sonist/handler.png')
2023-08-17 20:51:59 +08:00
album = ScaleImage(album_img)
disk.resize(192, 192)
2023-08-18 19:04:51 +08:00
album.resize(128, 128).set_radius(64)
handler.resize(48, 96)
self.handler = handler
self.album = album
2023-08-17 20:51:59 +08:00
box = Gtk.Fixed()
2023-08-18 19:04:51 +08:00
box.put(disk, 16, 16)
box.put(album, 48, 48)
box.put(handler, 0, 16)
2023-08-17 20:51:59 +08:00
2023-08-18 19:04:51 +08:00
layout.put(box, 48, 16)
2023-08-17 20:51:59 +08:00
# title
2023-08-18 19:04:51 +08:00
# self.title_box = TextBox(256, 20)
self.title_box = Gtk.Label()
2023-08-22 20:43:40 +08:00
self.title_box.set_name('text')
2023-08-21 19:06:22 +08:00
self.title_box.set_text('mpd loading...')
2023-08-17 20:51:59 +08:00
layout.put(self.title_box, 27, 244)
2023-08-17 20:51:59 +08:00
# 播放进度
self.timebar = Timebar()
2023-08-23 20:36:52 +08:00
self.timebar.connect('seeked', lambda a,v: self.mpd.seekcur(v))
layout.put(self.timebar, 24, 270)
2023-08-17 20:51:59 +08:00
# 控制条
2023-08-18 19:04:51 +08:00
self.ctrl_box = CtrlBox()
self.ctrl_box.connect('clicked', self.ctrl_clicked)
2023-08-17 20:51:59 +08:00
2023-08-21 19:06:22 +08:00
layout.put(self.ctrl_box, 48, 312)
2023-08-17 20:51:59 +08:00
self.add(layout)
2023-08-17 20:51:59 +08:00
def get_mpd_stat(self):
try:
2023-08-23 20:36:52 +08:00
self.stat = self.mpd.status()
except:
2023-08-23 20:36:52 +08:00
self.stat = {}
return self.stat
2023-08-17 20:51:59 +08:00
def set_background_image(self, filepath):
2023-08-21 19:06:22 +08:00
if type(filepath) == str:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filepath)
else:
pixbuf = filepath
2023-08-17 20:51:59 +08:00
pixbuf = blur_image(pixbuf)
pixbuf.savev(f"/tmp/sonist_album_cache", 'png', [], [])
css = f"""
#SonistWindow {{
background-image: url('/tmp/sonist_album_cache');
background-size: 100% 100%;
background-position: center;
}}
2023-08-22 20:43:40 +08:00
#text {{
color: #f2f5fc;
}}
2023-08-17 20:51:59 +08:00
"""
# 加载CSS样式
css_provider = Gtk.CssProvider()
css_provider.load_from_data(css.encode('UTF-8'))
context = Gtk.StyleContext()
screen = Gdk.Screen.get_default()
context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
2023-08-17 00:06:21 +08:00
2023-08-18 19:04:51 +08:00
def ctrl_clicked(self, box, btn):
match(btn):
case 'play_btn':
self.toggle_play()
case 'mode_btn':
# repeat all
if self.ctrl_box.curr_mode == 0:
2023-08-23 20:36:52 +08:00
self.mpd.repeat(1)
self.mpd.random(0)
self.mpd.single(0)
2023-08-18 19:04:51 +08:00
# random
elif self.ctrl_box.curr_mode == 1:
2023-08-23 20:36:52 +08:00
self.mpd.repeat(0)
self.mpd.random(1)
self.mpd.single(0)
2023-08-18 19:04:51 +08:00
# single
else:
2023-08-23 20:36:52 +08:00
self.mpd.repeat(0)
self.mpd.random(0)
self.mpd.single(1)
2023-08-18 19:04:51 +08:00
case 'prev_btn':
self.prev_song()
case 'next_btn':
self.next_song()
case 'vol_btn':
self.toggle_play()
def toggle_play(self):
try:
if self.stat.get('state') == 'stop':
2023-08-23 20:36:52 +08:00
self.mpd.play()
else:
2023-08-23 20:36:52 +08:00
self.mpd.pause()
except:
2023-08-23 20:36:52 +08:00
pass
2023-08-18 19:04:51 +08:00
self.update_play_stat(self.stat.get('state') == 'play')
2023-08-21 19:06:22 +08:00
def prev_song(self):
try:
2023-08-23 20:36:52 +08:00
self.mpd.previous()
except:
2023-08-23 20:36:52 +08:00
pass
2023-08-18 19:04:51 +08:00
def next_song(self):
try:
2023-08-23 20:36:52 +08:00
self.mpd.next()
except:
2023-08-23 20:36:52 +08:00
pass
2023-08-18 19:04:51 +08:00
@idle
2023-08-18 19:04:51 +08:00
def update_play_stat(self, played = True):
2023-08-23 20:36:52 +08:00
if not self.mpd.connected:
2023-08-21 19:06:22 +08:00
return
2023-08-18 19:04:51 +08:00
if played:
self.handler.reset('./usr/share/sonist/handler_a.png')
else:
self.handler.reset('./usr/share/sonist/handler.png')
# 切换播放按钮状态
self.ctrl_box.toggle_play_btn(played)
@idle
2023-08-23 20:36:52 +08:00
def update_playtime(self, stat = {}):
times = stat['time'].split(':')
self.timebar.update_time(int(times[0]), int(times[1]))
@idle
2023-08-23 20:36:52 +08:00
def sync_state(self, stat = None, song = None, first = False):
self.ctrl_box.disabled = False
2023-08-21 19:06:22 +08:00
2023-08-23 20:36:52 +08:00
self.stat = stat or self.get_mpd_stat()
2023-08-18 19:04:51 +08:00
played = self.stat.get('state')
if first:
self.update_play_stat(played == 'play')
if self.stat.get('single') == '1':
self.ctrl_box.toggle_mode_btn(mode = 'single')
elif self.stat.get('random') == '1':
self.ctrl_box.toggle_mode_btn(mode = 'random')
2023-08-23 20:36:52 +08:00
2023-08-22 20:43:40 +08:00
if played != 'stop':
2023-08-23 20:36:52 +08:00
song = song or self.mpd.currentsong()
2023-08-22 20:43:40 +08:00
# 更新歌曲信息
2023-08-23 20:36:52 +08:00
self.title_box.set_text(f"{song.get('artist')} - {song.get('title')}")
self.update_playtime(self.stat)
2023-08-18 19:04:51 +08:00
2023-08-22 20:43:40 +08:00
filepath = f"./album/{song['title']}.png"
songpath = f"{self.app.music_dir}/{song['file']}"
2023-08-18 19:04:51 +08:00
2023-08-22 20:43:40 +08:00
if os.path.isfile(filepath):
self.update_album(filepath)
else:
id3 = mutagen.File(songpath)
try:
if id3.tags.get('APIC:'):
pic = id3.tags['APIC:']
elif len(id3.pictures) > 0:
pic = id3.pictures[0]
if pic is not None:
album = pic_to_pixbuf(pic)
self.update_album(album)
except:
pass
2023-08-23 20:36:52 +08:00
@idle
2023-08-23 20:36:52 +08:00
def reset_player(self):
self.ctrl_box.disabled = True
self.title_box.set_text('mpd is offline...')
self.timebar.update_time()
self.update_album('./usr/share/sonist/avatar.jpg')
2023-08-18 19:04:51 +08:00
@idle
2023-08-18 19:04:51 +08:00
def update_album(self, filepath):
self.set_background_image(filepath)
self.album.reset(filepath).set_radius(64)
2023-08-23 20:36:52 +08:00
def quited(self, win):
2023-08-18 19:04:51 +08:00
self.app.remove_window(self)
2023-08-17 00:06:21 +08:00