优化交互
parent
6edcdb148f
commit
cd87a44513
64
main.py
64
main.py
|
@ -1,14 +1,15 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import gi, sys, os, threading
|
import gi, sys, os, threading, time
|
||||||
# import dbus
|
# import dbus
|
||||||
# import dbus.service, dbus.mainloop.glib
|
# import dbus.service, dbus.mainloop.glib
|
||||||
from pprint import pprint as print
|
from pprint import pprint as print
|
||||||
import musicbrainzngs as mus
|
import musicbrainzngs as mus
|
||||||
|
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
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
|
from window import SonistWindow
|
||||||
|
|
||||||
|
@ -28,19 +29,30 @@ def run_async(func):
|
||||||
|
|
||||||
|
|
||||||
class Application(Gtk.Application):
|
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):
|
def __init__(self):
|
||||||
Gtk.Application.__init__(self, application_id = app_id)
|
Gtk.Application.__init__(self, application_id = app_id)
|
||||||
|
|
||||||
self.mpd = MPDClient()
|
self.mpd_state = None
|
||||||
self.mpd.timeout = 10
|
self.mpd_curr_song = None
|
||||||
self.mpd.connect("localhost", 6600)
|
|
||||||
|
|
||||||
self.mpd.ping()
|
self.mpd = MPDClient()
|
||||||
|
|
||||||
|
self.mpd_is_online = self.mpd_connect()
|
||||||
|
|
||||||
self.connect('window-removed', self.on_window_removed)
|
self.connect('window-removed', self.on_window_removed)
|
||||||
|
|
||||||
mus.set_useragent('Sonist Gtk', '0.0.1', 'https://github.com/app-cat/sonist-gtk')
|
mus.set_useragent('Sonist Gtk', '0.0.1', 'https://github.com/app-cat/sonist-gtk')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@run_async
|
@run_async
|
||||||
def get_cover(self, song, filepath, callback):
|
def get_cover(self, song, filepath, callback):
|
||||||
try:
|
try:
|
||||||
|
@ -53,6 +65,45 @@ class Application(Gtk.Application):
|
||||||
callback(filepath)
|
callback(filepath)
|
||||||
except:
|
except:
|
||||||
pass
|
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):
|
def do_activate(self):
|
||||||
|
@ -64,6 +115,7 @@ class Application(Gtk.Application):
|
||||||
self.add_window(self.window)
|
self.add_window(self.window)
|
||||||
|
|
||||||
self.window.show_all()
|
self.window.show_all()
|
||||||
|
self.ping()
|
||||||
|
|
||||||
|
|
||||||
def on_window_removed(self, app, win):
|
def on_window_removed(self, app, win):
|
||||||
|
|
|
@ -5,22 +5,25 @@ gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk, GObject
|
from gi.repository import Gtk, GObject
|
||||||
|
|
||||||
from .image_button import ImageButton
|
from .image_button import ImageButton
|
||||||
|
from .toggle_button import ToggleButton
|
||||||
|
|
||||||
class CtrlBox(Gtk.Box):
|
class CtrlBox(Gtk.Box):
|
||||||
|
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
'clicked': (GObject.SIGNAL_RUN_FIRST, None, (str,))
|
'clicked': (GObject.SignalFlags.RUN_FIRST, None, (str,))
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, spacing = 6):
|
def __init__(self, spacing = 6):
|
||||||
Gtk.Box.__init__(self, spacing = spacing)
|
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.modes = ['./usr/share/sonist/all.png','./usr/share/sonist/rand.png','./usr/share/sonist/single.png']
|
||||||
self.curr_mode = 0
|
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.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.next_btn = ImageButton('./usr/share/sonist/next.png')
|
||||||
self.vol_btn = ImageButton('./usr/share/sonist/volume.png')
|
self.vol_btn = ImageButton('./usr/share/sonist/volume.png')
|
||||||
|
|
||||||
|
@ -33,12 +36,16 @@ class CtrlBox(Gtk.Box):
|
||||||
|
|
||||||
self.mode_btn.connect('clicked', self.on_btn_clicked)
|
self.mode_btn.connect('clicked', self.on_btn_clicked)
|
||||||
self.prev_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.play_btn.connect('clicked', self.on_btn_clicked)
|
||||||
self.next_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)
|
self.vol_btn.connect('clicked', self.on_btn_clicked)
|
||||||
|
|
||||||
|
|
||||||
def on_btn_clicked(self, btn):
|
def on_btn_clicked(self, btn):
|
||||||
|
|
||||||
|
if self.disabled:
|
||||||
|
return
|
||||||
|
|
||||||
if btn == self.play_btn:
|
if btn == self.play_btn:
|
||||||
self.emit('clicked', 'play_btn')
|
self.emit('clicked', 'play_btn')
|
||||||
|
@ -55,19 +62,22 @@ class CtrlBox(Gtk.Box):
|
||||||
|
|
||||||
elif btn == self.next_btn:
|
elif btn == self.next_btn:
|
||||||
self.emit('clicked', 'next_btn')
|
self.emit('clicked', 'next_btn')
|
||||||
|
|
||||||
elif btn == self.vol_btn:
|
elif btn == self.vol_btn:
|
||||||
self.emit('clicked','vol_btn')
|
self.emit('clicked','vol_btn')
|
||||||
|
|
||||||
|
|
||||||
def toggle_play_btn(self, on = True):
|
def toggle_play_btn(self, on = True):
|
||||||
if on:
|
if self.disabled:
|
||||||
self.play_btn.set_image('./usr/share/sonist/play_a.png')
|
return
|
||||||
else:
|
|
||||||
self.play_btn.set_image('./usr/share/sonist/pause.png')
|
self.play_btn.toggle(on)
|
||||||
|
|
||||||
|
|
||||||
def toggle_mode_btn(self, mode = 'single'):
|
def toggle_mode_btn(self, mode = 'single'):
|
||||||
|
if self.disabled:
|
||||||
|
return
|
||||||
|
|
||||||
if mode == 'single':
|
if mode == 'single':
|
||||||
self.curr_mode = 2
|
self.curr_mode = 2
|
||||||
elif mode == 'random':
|
elif mode == 'random':
|
||||||
|
|
|
@ -40,9 +40,9 @@ class ImageButton(Gtk.Button):
|
||||||
css_provider.load_from_data(style.encode('UTF-8'))
|
css_provider.load_from_data(style.encode('UTF-8'))
|
||||||
|
|
||||||
context = self.get_style_context()
|
context = self.get_style_context()
|
||||||
path = context.get_path()
|
|
||||||
context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||||
|
|
||||||
|
|
||||||
def set_image(self, filepath):
|
def set_image(self, filepath):
|
||||||
if self._image_path == filepath:
|
if self._image_path == filepath:
|
||||||
return
|
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
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
|
||||||
import gi
|
import gi, io, base64
|
||||||
from gi.repository import Gdk, GLib, GdkPixbuf
|
from gi.repository import Gdk, GLib, GdkPixbuf
|
||||||
from PIL import Image, ImageFilter
|
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)
|
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):
|
def blur_image(pixbuf):
|
||||||
# 加载图片并确认该图片为RGBA模式,保证透明度
|
# 加载图片并确认该图片为RGBA模式,保证透明度
|
||||||
img = pixbuf_to_pil(pixbuf).convert('RGBA')
|
img = pixbuf_to_pil(pixbuf).convert('RGBA')
|
||||||
mask = Image.new('RGBA', img.size, (32, 32, 32,160))
|
mask = Image.new('RGBA', img.size, (64, 64, 64, 160))
|
||||||
img = img.filter(ImageFilter.GaussianBlur(radius = 16))
|
img = img.filter(ImageFilter.GaussianBlur(radius = 32))
|
||||||
img.alpha_composite(mask)
|
img.alpha_composite(mask)
|
||||||
|
|
||||||
return pil_to_pixbuf(img)
|
return pil_to_pixbuf(img)
|
31
window.py
31
window.py
|
@ -1,13 +1,13 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import gi, sys, os
|
import gi, sys, os, mutagen
|
||||||
from pprint import pprint as print
|
from pprint import pprint as print
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
|
|
||||||
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
|
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.image import ScaleImage
|
||||||
from ui.slider import Slider
|
from ui.slider import Slider
|
||||||
from ui.image_button import ImageButton
|
from ui.image_button import ImageButton
|
||||||
|
@ -26,6 +26,9 @@ class SonistWindow(Gtk.Window):
|
||||||
|
|
||||||
self.connect("destroy", self.quit)
|
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_name('SonistWindow')
|
||||||
self.set_default_size(320, 384)
|
self.set_default_size(320, 384)
|
||||||
self.set_resizable(False)
|
self.set_resizable(False)
|
||||||
|
@ -70,7 +73,7 @@ class SonistWindow(Gtk.Window):
|
||||||
|
|
||||||
# self.title_box = TextBox(256, 20)
|
# self.title_box = TextBox(256, 20)
|
||||||
self.title_box = Gtk.Label()
|
self.title_box = Gtk.Label()
|
||||||
self.title_box.set_text('孙晓 - 丹歌惊鸿')
|
self.title_box.set_text('mpd loading...')
|
||||||
|
|
||||||
layout.put(self.title_box, 32, 244)
|
layout.put(self.title_box, 32, 244)
|
||||||
|
|
||||||
|
@ -84,6 +87,8 @@ class SonistWindow(Gtk.Window):
|
||||||
self.ctrl_box = CtrlBox()
|
self.ctrl_box = CtrlBox()
|
||||||
self.ctrl_box.connect('clicked', self.ctrl_clicked)
|
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)
|
layout.put(self.ctrl_box, 48, 300)
|
||||||
|
|
||||||
self.add(layout)
|
self.add(layout)
|
||||||
|
@ -93,7 +98,11 @@ class SonistWindow(Gtk.Window):
|
||||||
|
|
||||||
|
|
||||||
def set_background_image(self, filepath):
|
def set_background_image(self, filepath):
|
||||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filepath)
|
if type(filepath) == str:
|
||||||
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filepath)
|
||||||
|
else:
|
||||||
|
pixbuf = filepath
|
||||||
|
|
||||||
pixbuf = blur_image(pixbuf)
|
pixbuf = blur_image(pixbuf)
|
||||||
pixbuf.savev(f"/tmp/sonist_album_cache", 'png', [], [])
|
pixbuf.savev(f"/tmp/sonist_album_cache", 'png', [], [])
|
||||||
|
|
||||||
|
@ -155,7 +164,7 @@ class SonistWindow(Gtk.Window):
|
||||||
self.update_play_stat(self.stat.get('state') == 'play')
|
self.update_play_stat(self.stat.get('state') == 'play')
|
||||||
|
|
||||||
|
|
||||||
def prev_song(self):
|
def prev_song(self):
|
||||||
self.app.mpd.previous()
|
self.app.mpd.previous()
|
||||||
self.sync_state()
|
self.sync_state()
|
||||||
|
|
||||||
|
@ -165,6 +174,9 @@ class SonistWindow(Gtk.Window):
|
||||||
|
|
||||||
|
|
||||||
def update_play_stat(self, played = True):
|
def update_play_stat(self, played = True):
|
||||||
|
if not self.app.mpd_is_online:
|
||||||
|
return
|
||||||
|
|
||||||
if played:
|
if played:
|
||||||
self.handler.reset('./usr/share/sonist/handler_a.png')
|
self.handler.reset('./usr/share/sonist/handler_a.png')
|
||||||
else:
|
else:
|
||||||
|
@ -175,6 +187,9 @@ class SonistWindow(Gtk.Window):
|
||||||
|
|
||||||
|
|
||||||
def sync_state(self, first = False):
|
def sync_state(self, first = False):
|
||||||
|
if not self.app.mpd_is_online:
|
||||||
|
return
|
||||||
|
|
||||||
self.stat = self.app.mpd.status()
|
self.stat = self.app.mpd.status()
|
||||||
|
|
||||||
played = self.stat.get('state')
|
played = self.stat.get('state')
|
||||||
|
@ -194,13 +209,15 @@ class SonistWindow(Gtk.Window):
|
||||||
filepath = f"./album/{song['title']}.png"
|
filepath = f"./album/{song['title']}.png"
|
||||||
|
|
||||||
# print(self.stat)
|
# print(self.stat)
|
||||||
print(song)
|
# print(song)
|
||||||
|
|
||||||
|
|
||||||
if os.path.isfile(filepath):
|
if os.path.isfile(filepath):
|
||||||
self.update_album(filepath)
|
self.update_album(filepath)
|
||||||
else:
|
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)
|
# print(buff)
|
||||||
# with open(filepath, 'wb') as file:
|
# with open(filepath, 'wb') as file:
|
||||||
# output = file.write(buff)
|
# output = file.write(buff)
|
||||||
|
|
Loading…
Reference in New Issue