diff --git a/demo.png b/demo.png new file mode 100644 index 0000000..edc1987 Binary files /dev/null and b/demo.png differ diff --git a/main.py b/main.py index bafc999..3916cdd 100755 --- a/main.py +++ b/main.py @@ -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,26 +43,10 @@ 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): @@ -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() diff --git a/ui/image.py b/ui/image.py index b3b67f1..a1af905 100644 --- a/ui/image.py +++ b/ui/image.py @@ -14,7 +14,10 @@ class ScaleImage(Gtk.Image): 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: self.pixbuf = self.origin diff --git a/ui/timebar.py b/ui/timebar.py new file mode 100644 index 0000000..6ea4c60 --- /dev/null +++ b/ui/timebar.py @@ -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) \ No newline at end of file diff --git a/utils.py b/utils.py index c1cad16..db45f6f 100644 --- a/utils.py +++ b/utils.py @@ -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模式,保证透明度 diff --git a/window.py b/window.py index c32d1b8..ee526f0 100644 --- a/window.py +++ b/window.py @@ -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): - if self.stat.get('state') == 'stop': - self.app.mpd.play() - else: - self.app.mpd.pause() + 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): - self.app.mpd.previous() - self.sync_state() + try: + self.app.mpd.previous() + except: + self.app.ping() + self.prev_song() + return + # self.sync_state() def next_song(self): - self.app.mpd.next() - self.sync_state() + try: + self.app.mpd.next() + 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'))) + + self.update_playtime() filepath = f"./album/{song['title']}.png" - - # print(self.stat) - # print(song) + songpath = f"/data/music/{song['file']}" if os.path.isfile(filepath): self.update_album(filepath) 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) + + id3 = mutagen.File(songpath) - # self.set_background_image(filepath) + try: + if id3.tags.get('APIC:'): + pic = id3.tags['APIC:'] + elif len(id3.pictures) > 0: + pic = id3.pictures[0] - - - # print(self.app.mpd.readpicture(self.stat.get('songid'))) + if pic is not None: + album = pic_to_pixbuf(pic) + self.update_album(album) + except: + pass def update_album(self, filepath):