优化交互, 增加读取歌曲内置封面的功能

master
yutent 2023-08-22 16:19:20 +08:00
parent cd87a44513
commit 4e85a32b15
6 changed files with 142 additions and 61 deletions

BIN
demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

29
main.py
View File

@ -4,7 +4,6 @@ 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
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
@ -13,7 +12,6 @@ from gi.repository import Gtk, Gdk, GLib, GdkPixbuf, GObject
from window import SonistWindow from window import SonistWindow
# from mpd.asyncio import MPDClient
from mpd.base import MPDClient from mpd.base import MPDClient
app_id = 'fun.wkit.sonist' app_id = 'fun.wkit.sonist'
@ -45,27 +43,11 @@ class Application(Gtk.Application):
self.mpd = MPDClient() 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')
@run_async
def get_cover(self, song, filepath, callback):
try:
data = mus.search_releases(song["artist"], song["title"], 1)
release_id = data["release-list"][0]["release-group"]["id"]
print(release_id)
buff = mus.get_release_group_image_front(release_id, size = 128)
with open(filepath, 'wb') as file:
output = file.write(buff)
callback(filepath)
except:
pass
@run_async @run_async
def ping(self): def ping(self):
if self.mpd_is_online: if self.mpd_is_online:
@ -87,14 +69,11 @@ class Application(Gtk.Application):
self.emit('state_changed', state) self.emit('state_changed', state)
self.mpd_state = state self.mpd_state = state
time.sleep(1) time.sleep(0.5)
except Exception as e: except Exception as e:
print('<><><><><><><><>')
print(e)
print('<><><><><><><><>')
self.mpd.kill() self.mpd.kill()
time.sleep(1) time.sleep(2)
self.mpd_is_online = self.mpd_connect() self.mpd_is_online = self.mpd_connect()
@ -111,10 +90,12 @@ class Application(Gtk.Application):
self.set_app_menu(None) self.set_app_menu(None)
self.set_menubar(None) self.set_menubar(None)
self.mpd_is_online = self.mpd_connect()
self.window = SonistWindow(self) self.window = SonistWindow(self)
self.add_window(self.window) self.add_window(self.window)
self.window.show_all() self.window.show_all()
self.ping() self.ping()

View File

@ -14,7 +14,10 @@ class ScaleImage(Gtk.Image):
def reset(self, filepath): def reset(self, filepath):
self.origin = GdkPixbuf.Pixbuf.new_from_file(filepath) if type(filepath) == str:
self.origin = GdkPixbuf.Pixbuf.new_from_file(filepath)
else:
self.origin = filepath
if self.width is None: if self.width is None:
self.pixbuf = self.origin self.pixbuf = self.origin

58
ui/timebar.py Normal file
View File

@ -0,0 +1,58 @@
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
from .slider import Slider
def time_to_str(stamp = 0):
m = stamp // 60
s = stamp % 60
if m < 10:
m = f"0{m}"
if s < 10:
s = f"0{s}"
return f"{m}:{s}"
class Timebar(Gtk.Fixed):
__gsignals__ = {
'seeked': (GObject.SignalFlags.RUN_FIRST, None, (float,))
}
def __init__(self):
Gtk.Fixed.__init__(self)
self._duration = 0
self._time = 0
self.slider = Slider(272)
self.curr = Gtk.Label()
self.duration = Gtk.Label()
self.duration.set_justify(Gtk.Justification.RIGHT)
self.duration.set_xalign(1)
self.slider.connect('change-value', self.on_timeupdate)
self.put(self.curr, 3, 0)
self.put(self.duration, 233, 0)
self.put(self.slider, 0, 12)
def update_time(self, curr = 0, duration = 0):
self._duration = duration
self._time = curr
progress = curr * 100 / duration
self.curr.set_text(time_to_str(curr))
self.duration.set_text(time_to_str(duration))
self.slider.set_value(progress)
def on_timeupdate(self, slider, a, b):
# print(slider.get_value(), a, b)
p = slider.get_value()
time = p * self._duration / 100
self.emit('seeked', time)

View File

@ -2,7 +2,7 @@
import gi, io, base64 import gi, io, base64
from gi.repository import Gdk, GLib, GdkPixbuf from gi.repository import Gdk, GLib, GdkPixbuf, Gio
from PIL import Image, ImageFilter from PIL import Image, ImageFilter
def pixbuf_to_pil(pixbuf): def pixbuf_to_pil(pixbuf):
@ -26,14 +26,15 @@ 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 pic_to_pixbuf(pic):
data = pic.data
input_stream = Gio.MemoryInputStream.new_from_data(data, None)
return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
def base64_to_pixbuf(base64_str): def base64_to_pixbuf(base64_str):
# 解码base64 data = base64.b64decode(base64_str)
decoded = base64.b64decode(base64_str) input_stream = Gio.MemoryInputStream.new_from_data(data, None)
# 创建输入流 return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
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模式保证透明度

View File

@ -2,17 +2,20 @@
import gi, sys, os, mutagen import gi, sys, os, mutagen
from pprint import pprint as print from pprint import pprint as print
from mutagen.flac import FLAC
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, base64_to_pixbuf from utils import blur_image, pic_to_pixbuf, 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
from ui.text import TextBox from ui.text import TextBox
from ui.ctrl_box import CtrlBox from ui.ctrl_box import CtrlBox
from ui.timebar import Timebar
@ -28,6 +31,8 @@ class SonistWindow(Gtk.Window):
self.app.connect('state_changed', lambda a, x: self.update_play_stat(x == 'play')) 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('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_name('SonistWindow')
self.set_default_size(320, 384) self.set_default_size(320, 384)
@ -75,12 +80,13 @@ class SonistWindow(Gtk.Window):
self.title_box = Gtk.Label() self.title_box = Gtk.Label()
self.title_box.set_text('mpd loading...') self.title_box.set_text('mpd loading...')
layout.put(self.title_box, 32, 244) layout.put(self.title_box, 27, 244)
# 播放进度 # 播放进度
slider = Slider(272) self.timebar = Timebar()
layout.put(slider, 24, 270) self.timebar.connect('seeked', lambda a,v: self.app.mpd.seekcur(v))
layout.put(self.timebar, 24, 270)
# 控制条 # 控制条
@ -89,13 +95,22 @@ class SonistWindow(Gtk.Window):
self.ctrl_box.disabled = not self.app.mpd_is_online self.ctrl_box.disabled = not self.app.mpd_is_online
layout.put(self.ctrl_box, 48, 300) layout.put(self.ctrl_box, 48, 312)
self.add(layout) self.add(layout)
self.sync_state(True) 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): def set_background_image(self, filepath):
if type(filepath) == str: if type(filepath) == str:
@ -155,22 +170,37 @@ class SonistWindow(Gtk.Window):
def toggle_play(self): def toggle_play(self):
if self.stat.get('state') == 'stop': try:
self.app.mpd.play() if self.stat.get('state') == 'stop':
else: self.app.mpd.play()
self.app.mpd.pause() else:
self.app.mpd.pause()
except:
self.app.ping()
self.toggle_play()
return
self.sync_state() # self.sync_state()
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() try:
self.sync_state() self.app.mpd.previous()
except:
self.app.ping()
self.prev_song()
return
# self.sync_state()
def next_song(self): def next_song(self):
self.app.mpd.next() try:
self.sync_state() self.app.mpd.next()
except:
self.app.ping()
self.next_song()
return
# self.sync_state()
def update_play_stat(self, played = True): def update_play_stat(self, played = True):
@ -186,11 +216,16 @@ class SonistWindow(Gtk.Window):
self.ctrl_box.toggle_play_btn(played) 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): def sync_state(self, first = False):
if not self.app.mpd_is_online: if not self.app.mpd_is_online:
return return
self.stat = self.app.mpd.status() self.stat = self.get_mpd_stat()
played = self.stat.get('state') played = self.stat.get('state')
song = self.app.mpd.currentsong() song = self.app.mpd.currentsong()
@ -203,30 +238,33 @@ class SonistWindow(Gtk.Window):
elif self.stat.get('random') == '1': elif self.stat.get('random') == '1':
self.ctrl_box.toggle_mode_btn(mode = 'random') self.ctrl_box.toggle_mode_btn(mode = 'random')
# 更新歌曲信息 # 更新歌曲信息
self.title_box.set_text("%s - %s" % (song.get('artist'), song.get('title'))) self.title_box.set_text("%s - %s" % (song.get('artist'), song.get('title')))
filepath = f"./album/{song['title']}.png" self.update_playtime()
# print(self.stat) filepath = f"./album/{song['title']}.png"
# print(song) songpath = f"/data/music/{song['file']}"
if os.path.isfile(filepath): if os.path.isfile(filepath):
self.update_album(filepath) self.update_album(filepath)
else: else:
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)
# self.set_background_image(filepath) 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:
# print(self.app.mpd.readpicture(self.stat.get('songid'))) album = pic_to_pixbuf(pic)
self.update_album(album)
except:
pass
def update_album(self, filepath): def update_album(self, filepath):