sonist-gtk/window.py

283 lines
6.5 KiB
Python

#!/usr/bin/env python3
import gi, sys, os, mutagen
from pprint import pprint as print
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
from utils import blur_image, pic_to_pixbuf, base64_to_pixbuf
from ui.image import ScaleImage
from ui.slider import Slider
from ui.image_button import ImageButton
from ui.text import TextBox
from ui.ctrl_box import CtrlBox
from ui.timebar import Timebar
from ui.option_menu import OptionMenu
class SonistWindow(Gtk.Window):
def __init__(self, app):
Gtk.Window.__init__(self)
self.app = app
self.connect("destroy", self.quit)
self.app.connect('state_changed', lambda a, x: self.update_play_stat(x == 'play'))
self.app.connect('song_changed', lambda a, id: self.sync_state(False))
self.app.connect('playing', lambda a, id: self.update_playtime())
self.set_name('SonistWindow')
self.set_default_size(320, 384)
self.set_resizable(False)
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()
# 菜单按钮
menu_btn = ImageButton('./usr/share/sonist/menu.png')
popup_menu = OptionMenu()
menu_btn.connect('clicked', lambda w: popup_menu.show(w))
layout.put(menu_btn, 276, 6)
# 唱片
disk = ScaleImage('./usr/share/sonist/disk.png')
handler = ScaleImage('./usr/share/sonist/handler.png')
album = ScaleImage(album_img)
disk.resize(192, 192)
album.resize(128, 128).set_radius(64)
handler.resize(48, 96)
self.handler = handler
self.album = album
box = Gtk.Fixed()
box.put(disk, 16, 16)
box.put(album, 48, 48)
box.put(handler, 0, 16)
layout.put(box, 48, 16)
# title
# self.title_box = TextBox(256, 20)
self.title_box = Gtk.Label()
self.title_box.set_name('text')
self.title_box.set_text('mpd loading...')
layout.put(self.title_box, 27, 244)
# 播放进度
self.timebar = Timebar()
self.timebar.connect('seeked', lambda a,v: self.app.mpd.seekcur(v))
layout.put(self.timebar, 24, 270)
# 控制条
self.ctrl_box = CtrlBox()
self.ctrl_box.connect('clicked', self.ctrl_clicked)
self.ctrl_box.disabled = not self.app.mpd_is_online
layout.put(self.ctrl_box, 48, 312)
self.add(layout)
self.sync_state(True)
def get_mpd_stat(self):
try:
self.stat = self.app.mpd.status()
except:
self.app.ping()
self.stat = self.app.mpd.status()
return self.stat
def set_background_image(self, filepath):
if type(filepath) == str:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filepath)
else:
pixbuf = filepath
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;
}}
#text {{
color: #f2f5fc;
}}
"""
# 加载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)
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:
self.app.mpd.repeat(1)
self.app.mpd.random(0)
self.app.mpd.single(0)
# random
elif self.ctrl_box.curr_mode == 1:
self.app.mpd.repeat(0)
self.app.mpd.random(1)
self.app.mpd.single(0)
# single
else:
self.app.mpd.repeat(0)
self.app.mpd.random(0)
self.app.mpd.single(1)
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':
self.app.mpd.play()
else:
self.app.mpd.pause()
except:
self.app.ping()
self.toggle_play()
return
# self.sync_state()
self.update_play_stat(self.stat.get('state') == 'play')
def prev_song(self):
try:
self.app.mpd.previous()
except:
self.app.ping()
self.prev_song()
return
# self.sync_state()
def next_song(self):
try:
self.app.mpd.next()
except:
self.app.ping()
self.next_song()
return
# self.sync_state()
def update_play_stat(self, played = True):
if not self.app.mpd_is_online:
return
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)
def update_playtime(self):
stat = self.get_mpd_stat()
times = stat['time'].split(':')
self.timebar.update_time(int(times[0]), int(times[1]))
def sync_state(self, first = False):
if not self.app.mpd_is_online:
return
self.stat = self.get_mpd_stat()
played = self.stat.get('state')
song = self.app.mpd.currentsong()
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')
if played != 'stop':
# 更新歌曲信息
self.title_box.set_text("%s - %s" % (song.get('artist'), song.get('title')))
self.update_playtime()
filepath = f"./album/{song['title']}.png"
songpath = f"{self.app.music_dir}/{song['file']}"
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
def update_album(self, filepath):
self.set_background_image(filepath)
self.album.reset(filepath).set_radius(64)
def quit(self, win):
self.app.remove_window(self)
python + gtk3开发的基于mpd后端的音乐播放器
Python 99.1%
Shell 0.9%