优化交互, 增加读取歌曲内置封面的功能
parent
cd87a44513
commit
4e85a32b15
29
main.py
29
main.py
|
@ -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,26 +43,10 @@ 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):
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
17
utils.py
17
utils.py
|
@ -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模式,保证透明度
|
||||||
|
|
94
window.py
94
window.py
|
@ -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')))
|
||||||
|
|
||||||
|
self.update_playtime()
|
||||||
|
|
||||||
filepath = f"./album/{song['title']}.png"
|
filepath = f"./album/{song['title']}.png"
|
||||||
|
songpath = f"/data/music/{song['file']}"
|
||||||
# print(self.stat)
|
|
||||||
# print(song)
|
|
||||||
|
|
||||||
|
|
||||||
if os.path.isfile(filepath):
|
if os.path.isfile(filepath):
|
||||||
self.update_album(filepath)
|
self.update_album(filepath)
|
||||||
else:
|
else:
|
||||||
pass
|
|
||||||
# audio = mutagen.File()
|
id3 = mutagen.File(songpath)
|
||||||
# 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)
|
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)
|
||||||
# print(self.app.mpd.readpicture(self.stat.get('songid')))
|
self.update_album(album)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def update_album(self, filepath):
|
def update_album(self, filepath):
|
||||||
|
|
Loading…
Reference in New Issue