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

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.service, dbus.mainloop.glib
from pprint import pprint as print
import musicbrainzngs as mus
gi.require_version('Gtk', '3.0')
@ -13,7 +12,6 @@ from gi.repository import Gtk, Gdk, GLib, GdkPixbuf, GObject
from window import SonistWindow
# from mpd.asyncio import MPDClient
from mpd.base import MPDClient
app_id = 'fun.wkit.sonist'
@ -45,27 +43,11 @@ class Application(Gtk.Application):
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:
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
def ping(self):
if self.mpd_is_online:
@ -87,14 +69,11 @@ class Application(Gtk.Application):
self.emit('state_changed', state)
self.mpd_state = state
time.sleep(1)
time.sleep(0.5)
except Exception as e:
print('<><><><><><><><>')
print(e)
print('<><><><><><><><>')
self.mpd.kill()
time.sleep(1)
time.sleep(2)
self.mpd_is_online = self.mpd_connect()
@ -111,10 +90,12 @@ class Application(Gtk.Application):
self.set_app_menu(None)
self.set_menubar(None)
self.mpd_is_online = self.mpd_connect()
self.window = SonistWindow(self)
self.add_window(self.window)
self.window.show_all()
self.ping()

View File

@ -14,7 +14,10 @@ class ScaleImage(Gtk.Image):
def reset(self, filepath):
if type(filepath) == str:
self.origin = GdkPixbuf.Pixbuf.new_from_file(filepath)
else:
self.origin = filepath
if self.width is None:
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
from gi.repository import Gdk, GLib, GdkPixbuf
from gi.repository import Gdk, GLib, GdkPixbuf, Gio
from PIL import Image, ImageFilter
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)
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):
# 解码base64
decoded = base64.b64decode(base64_str)
# 创建输入流
input_stream = io.BytesIO(decoded)
# 从输入流创建pixbuf
pixbuf = GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
return pixbuf
data = base64.b64decode(base64_str)
input_stream = Gio.MemoryInputStream.new_from_data(data, None)
return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
def blur_image(pixbuf):
# 加载图片并确认该图片为RGBA模式保证透明度

View File

@ -2,17 +2,20 @@
import gi, sys, os, mutagen
from pprint import pprint as print
from mutagen.flac import FLAC
gi.require_version('Gtk', '3.0')
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.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
@ -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('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)
@ -75,12 +80,13 @@ class SonistWindow(Gtk.Window):
self.title_box = Gtk.Label()
self.title_box.set_text('mpd loading...')
layout.put(self.title_box, 32, 244)
layout.put(self.title_box, 27, 244)
# 播放进度
slider = Slider(272)
layout.put(slider, 24, 270)
self.timebar = Timebar()
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
layout.put(self.ctrl_box, 48, 300)
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:
@ -155,22 +170,37 @@ class SonistWindow(Gtk.Window):
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.sync_state()
self.update_play_stat(self.stat.get('state') == 'play')
def prev_song(self):
try:
self.app.mpd.previous()
self.sync_state()
except:
self.app.ping()
self.prev_song()
return
# self.sync_state()
def next_song(self):
try:
self.app.mpd.next()
self.sync_state()
except:
self.app.ping()
self.next_song()
return
# self.sync_state()
def update_play_stat(self, played = True):
@ -186,11 +216,16 @@ class SonistWindow(Gtk.Window):
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.app.mpd.status()
self.stat = self.get_mpd_stat()
played = self.stat.get('state')
song = self.app.mpd.currentsong()
@ -203,30 +238,33 @@ class SonistWindow(Gtk.Window):
elif self.stat.get('random') == '1':
self.ctrl_box.toggle_mode_btn(mode = 'random')
# 更新歌曲信息
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)
# print(song)
filepath = f"./album/{song['title']}.png"
songpath = f"/data/music/{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
# 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)
# print(self.app.mpd.readpicture(self.stat.get('songid')))
def update_album(self, filepath):