优化交互
parent
6edcdb148f
commit
cd87a44513
64
main.py
64
main.py
|
@ -1,14 +1,15 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gi, sys, os, threading
|
||||
import gi, sys, os, threading, time
|
||||
# import dbus
|
||||
# import dbus.service, dbus.mainloop.glib
|
||||
from pprint import pprint as print
|
||||
import musicbrainzngs as mus
|
||||
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
|
||||
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
|
||||
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf, GObject
|
||||
|
||||
from window import SonistWindow
|
||||
|
||||
|
@ -28,19 +29,30 @@ def run_async(func):
|
|||
|
||||
|
||||
class Application(Gtk.Application):
|
||||
|
||||
__gsignals__ = {
|
||||
'playing': (GObject.SignalFlags.RUN_FIRST, None, (bool,)),
|
||||
'song_changed': (GObject.SignalFlags.RUN_FIRST, None, (bool,)),
|
||||
'state_changed': (GObject.SignalFlags.RUN_FIRST, None, (str,))
|
||||
}
|
||||
|
||||
|
||||
def __init__(self):
|
||||
Gtk.Application.__init__(self, application_id = app_id)
|
||||
|
||||
self.mpd = MPDClient()
|
||||
self.mpd.timeout = 10
|
||||
self.mpd.connect("localhost", 6600)
|
||||
self.mpd_state = None
|
||||
self.mpd_curr_song = None
|
||||
|
||||
self.mpd.ping()
|
||||
self.mpd = MPDClient()
|
||||
|
||||
self.mpd_is_online = self.mpd_connect()
|
||||
|
||||
self.connect('window-removed', self.on_window_removed)
|
||||
|
||||
mus.set_useragent('Sonist Gtk', '0.0.1', 'https://github.com/app-cat/sonist-gtk')
|
||||
|
||||
|
||||
|
||||
@run_async
|
||||
def get_cover(self, song, filepath, callback):
|
||||
try:
|
||||
|
@ -54,6 +66,45 @@ class Application(Gtk.Application):
|
|||
except:
|
||||
pass
|
||||
|
||||
@run_async
|
||||
def ping(self):
|
||||
if self.mpd_is_online:
|
||||
while True:
|
||||
try:
|
||||
self.mpd.ping()
|
||||
stat = self.mpd.status()
|
||||
song = self.mpd.currentsong() or {}
|
||||
state = stat.get('state')
|
||||
|
||||
if self.mpd_curr_song != song.get('id'):
|
||||
self.mpd_curr_song = song.get('id')
|
||||
self.emit('song_changed', self.mpd_curr_song)
|
||||
|
||||
if state == 'play':
|
||||
self.emit('playing', False)
|
||||
|
||||
if self.mpd_state != state:
|
||||
self.emit('state_changed', state)
|
||||
|
||||
self.mpd_state = state
|
||||
time.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
print('<><><><><><><><>')
|
||||
print(e)
|
||||
print('<><><><><><><><>')
|
||||
self.mpd.kill()
|
||||
time.sleep(1)
|
||||
self.mpd_is_online = self.mpd_connect()
|
||||
|
||||
|
||||
def mpd_connect(self):
|
||||
try:
|
||||
self.mpd.connect("127.0.0.1", 6600)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def do_activate(self):
|
||||
print('hello mpc')
|
||||
|
@ -64,6 +115,7 @@ class Application(Gtk.Application):
|
|||
self.add_window(self.window)
|
||||
|
||||
self.window.show_all()
|
||||
self.ping()
|
||||
|
||||
|
||||
def on_window_removed(self, app, win):
|
||||
|
|
|
@ -5,22 +5,25 @@ gi.require_version('Gtk', '3.0')
|
|||
from gi.repository import Gtk, GObject
|
||||
|
||||
from .image_button import ImageButton
|
||||
from .toggle_button import ToggleButton
|
||||
|
||||
class CtrlBox(Gtk.Box):
|
||||
|
||||
__gsignals__ = {
|
||||
'clicked': (GObject.SIGNAL_RUN_FIRST, None, (str,))
|
||||
'clicked': (GObject.SignalFlags.RUN_FIRST, None, (str,))
|
||||
}
|
||||
|
||||
def __init__(self, spacing = 6):
|
||||
Gtk.Box.__init__(self, spacing = spacing)
|
||||
|
||||
self.disabled = False
|
||||
|
||||
self.modes = ['./usr/share/sonist/all.png','./usr/share/sonist/rand.png','./usr/share/sonist/single.png']
|
||||
self.curr_mode = 0
|
||||
|
||||
self.mode_btn = ImageButton('./usr/share/sonist/all.png')
|
||||
self.mode_btn = ImageButton(self.modes[0])
|
||||
self.prev_btn = ImageButton('./usr/share/sonist/prev.png')
|
||||
self.play_btn = ImageButton('./usr/share/sonist/pause.png', 48, 48)
|
||||
self.play_btn = ToggleButton(['./usr/share/sonist/pause.png', './usr/share/sonist/play.png', './usr/share/sonist/play_a.png'], 48, 48)
|
||||
self.next_btn = ImageButton('./usr/share/sonist/next.png')
|
||||
self.vol_btn = ImageButton('./usr/share/sonist/volume.png')
|
||||
|
||||
|
@ -33,6 +36,7 @@ class CtrlBox(Gtk.Box):
|
|||
|
||||
self.mode_btn.connect('clicked', self.on_btn_clicked)
|
||||
self.prev_btn.connect('clicked', self.on_btn_clicked)
|
||||
# self.play_btn.connect('toggled', self.on_btn_clicked)
|
||||
self.play_btn.connect('clicked', self.on_btn_clicked)
|
||||
self.next_btn.connect('clicked', self.on_btn_clicked)
|
||||
self.vol_btn.connect('clicked', self.on_btn_clicked)
|
||||
|
@ -40,6 +44,9 @@ class CtrlBox(Gtk.Box):
|
|||
|
||||
def on_btn_clicked(self, btn):
|
||||
|
||||
if self.disabled:
|
||||
return
|
||||
|
||||
if btn == self.play_btn:
|
||||
self.emit('clicked', 'play_btn')
|
||||
|
||||
|
@ -61,13 +68,16 @@ class CtrlBox(Gtk.Box):
|
|||
|
||||
|
||||
def toggle_play_btn(self, on = True):
|
||||
if on:
|
||||
self.play_btn.set_image('./usr/share/sonist/play_a.png')
|
||||
else:
|
||||
self.play_btn.set_image('./usr/share/sonist/pause.png')
|
||||
if self.disabled:
|
||||
return
|
||||
|
||||
self.play_btn.toggle(on)
|
||||
|
||||
|
||||
def toggle_mode_btn(self, mode = 'single'):
|
||||
if self.disabled:
|
||||
return
|
||||
|
||||
if mode == 'single':
|
||||
self.curr_mode = 2
|
||||
elif mode == 'random':
|
||||
|
|
|
@ -40,9 +40,9 @@ class ImageButton(Gtk.Button):
|
|||
css_provider.load_from_data(style.encode('UTF-8'))
|
||||
|
||||
context = self.get_style_context()
|
||||
path = context.get_path()
|
||||
context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
|
||||
def set_image(self, filepath):
|
||||
if self._image_path == filepath:
|
||||
return
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
from .image import ScaleImage
|
||||
|
||||
class ToggleButton(Gtk.Button):
|
||||
def __init__(self, files = [], width = 26, height = 26):
|
||||
Gtk.Button.__init__(self)
|
||||
|
||||
self.is_active = False
|
||||
|
||||
self.set_name('ToggleButton')
|
||||
self.set_size_request(width, height)
|
||||
|
||||
self.set_valign(Gtk.Align.CENTER)
|
||||
|
||||
# 针对macos的设置, 但只解决了普通状态下的边框问题, 鼠标经过的样式还在
|
||||
self.set_relief(Gtk.ReliefStyle.NONE)
|
||||
|
||||
css_provider = Gtk.CssProvider()
|
||||
br = '\n'
|
||||
style = f"""
|
||||
#ToggleButton {{
|
||||
padding: 4px;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background-image: url('{files[0]}');
|
||||
background-size: 100%;
|
||||
background-color: transparent;
|
||||
border-color:transparent;
|
||||
outline: transparent;
|
||||
}}
|
||||
|
||||
#ToggleButton.active {{
|
||||
background-image: url('{files[1]}');
|
||||
}}
|
||||
|
||||
#ToggleButton:hover {{
|
||||
background-color: rgba(255,255,255,.1);
|
||||
}}
|
||||
"""
|
||||
|
||||
css_provider.load_from_data(style.encode('UTF-8'))
|
||||
|
||||
self.style_ctx = self.get_style_context()
|
||||
self.style_ctx.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
|
||||
|
||||
def toggle(self, state = False):
|
||||
|
||||
if self.is_active == state:
|
||||
return
|
||||
|
||||
self.is_active = state
|
||||
|
||||
if self.is_active:
|
||||
self.style_ctx.add_class('active')
|
||||
else:
|
||||
self.style_ctx.remove_class('active')
|
15
utils.py
15
utils.py
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import gi
|
||||
import gi, io, base64
|
||||
from gi.repository import Gdk, GLib, GdkPixbuf
|
||||
from PIL import Image, ImageFilter
|
||||
|
||||
|
@ -26,11 +26,20 @@ def pil_to_pixbuf(img):
|
|||
return GdkPixbuf.Pixbuf.new_from_bytes(GLib.Bytes.new(data), GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, rowstride)
|
||||
|
||||
|
||||
def base64_to_pixbuf(base64_str):
|
||||
# 解码base64
|
||||
decoded = base64.b64decode(base64_str)
|
||||
# 创建输入流
|
||||
input_stream = io.BytesIO(decoded)
|
||||
# 从输入流创建pixbuf
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
|
||||
return pixbuf
|
||||
|
||||
def blur_image(pixbuf):
|
||||
# 加载图片并确认该图片为RGBA模式,保证透明度
|
||||
img = pixbuf_to_pil(pixbuf).convert('RGBA')
|
||||
mask = Image.new('RGBA', img.size, (32, 32, 32,160))
|
||||
img = img.filter(ImageFilter.GaussianBlur(radius = 16))
|
||||
mask = Image.new('RGBA', img.size, (64, 64, 64, 160))
|
||||
img = img.filter(ImageFilter.GaussianBlur(radius = 32))
|
||||
img.alpha_composite(mask)
|
||||
|
||||
return pil_to_pixbuf(img)
|
27
window.py
27
window.py
|
@ -1,13 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gi, sys, os
|
||||
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
|
||||
from utils import blur_image, base64_to_pixbuf
|
||||
from ui.image import ScaleImage
|
||||
from ui.slider import Slider
|
||||
from ui.image_button import ImageButton
|
||||
|
@ -26,6 +26,9 @@ class SonistWindow(Gtk.Window):
|
|||
|
||||
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.set_name('SonistWindow')
|
||||
self.set_default_size(320, 384)
|
||||
self.set_resizable(False)
|
||||
|
@ -70,7 +73,7 @@ class SonistWindow(Gtk.Window):
|
|||
|
||||
# self.title_box = TextBox(256, 20)
|
||||
self.title_box = Gtk.Label()
|
||||
self.title_box.set_text('孙晓 - 丹歌惊鸿')
|
||||
self.title_box.set_text('mpd loading...')
|
||||
|
||||
layout.put(self.title_box, 32, 244)
|
||||
|
||||
|
@ -84,6 +87,8 @@ class SonistWindow(Gtk.Window):
|
|||
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, 300)
|
||||
|
||||
self.add(layout)
|
||||
|
@ -93,7 +98,11 @@ class SonistWindow(Gtk.Window):
|
|||
|
||||
|
||||
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', [], [])
|
||||
|
||||
|
@ -165,6 +174,9 @@ class SonistWindow(Gtk.Window):
|
|||
|
||||
|
||||
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:
|
||||
|
@ -175,6 +187,9 @@ class SonistWindow(Gtk.Window):
|
|||
|
||||
|
||||
def sync_state(self, first = False):
|
||||
if not self.app.mpd_is_online:
|
||||
return
|
||||
|
||||
self.stat = self.app.mpd.status()
|
||||
|
||||
played = self.stat.get('state')
|
||||
|
@ -194,13 +209,15 @@ class SonistWindow(Gtk.Window):
|
|||
filepath = f"./album/{song['title']}.png"
|
||||
|
||||
# print(self.stat)
|
||||
print(song)
|
||||
# print(song)
|
||||
|
||||
|
||||
if os.path.isfile(filepath):
|
||||
self.update_album(filepath)
|
||||
else:
|
||||
buff = self.app.get_cover(song, filepath, self.update_album)
|
||||
pass
|
||||
# audio = mutagen.File()
|
||||
# buff = self.app.get_cover(song, filepath, self.update_album)
|
||||
# print(buff)
|
||||
# with open(filepath, 'wb') as file:
|
||||
# output = file.write(buff)
|
||||
|
|
Loading…
Reference in New Issue