Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

通过 Mpris 接口 Metadata::xesam:asText 提供歌词 #268

Open
tuberry opened this issue Jan 2, 2024 · 12 comments
Open

通过 Mpris 接口 Metadata::xesam:asText 提供歌词 #268

tuberry opened this issue Jan 2, 2024 · 12 comments

Comments

@tuberry
Copy link

tuberry commented Jan 2, 2024

这个 Feature Request 算是此评论的后续。

这样做可以每首歌少读一次文件,而且文件名格式更自由有利于解决 #264 这类问题,也可以加songId之类的精确匹配。

我不会 rust,弄的这个 patch 可能有问题,但可以工作:

lyrics.patch
diff --git a/src/audio/mpris.rs b/src/audio/mpris.rs
index b4cce1a..48e1097 100644
--- a/src/audio/mpris.rs
+++ b/src/audio/mpris.rs
@@ -55,8 +55,9 @@ impl MprisController {
         Ok(Self { mpris_player })
     }
 
-    pub async fn update_metadata(&self, si: &SongInfo) -> Result<()> {
+    pub async fn update_metadata(&self, si: &SongInfo, lrc: String) -> Result<()> {
         let mut metadata = Metadata::new();
+        metadata.set_lyrics(Some(lrc.clone()));
         metadata.set_artist(Some(vec![si.singer.clone()]));
         metadata.set_title(Some(si.name.clone()));
         metadata.set_album(Some(si.album.clone()));
diff --git a/src/gui/player_controls.rs b/src/gui/player_controls.rs
index ba12410..741717b 100644
--- a/src/gui/player_controls.rs
+++ b/src/gui/player_controls.rs
@@ -19,7 +19,7 @@ use once_cell::sync::*;
 
 use crate::{application::Action, audio::*, model::ImageDownloadImpl, path::CACHE};
 use std::{
-    cell::Cell,
+    cell::{Cell, RefCell},
     fs, path,
     rc::Rc,
     sync::{Arc, Mutex},
@@ -177,11 +177,12 @@ impl PlayerControls {
         artist_label.set_label(&song_info.singer);
 
         let volume = self.property("volume");
+        let lyrics: String = self.property("lyrics");
         if let Some(mpris) = imp.mpris.get() {
             crate::MAINCONTEXT.spawn_local_with_priority(
                 Priority::LOW,
                 clone!(@weak mpris => async move {
-                    if let Err(err) = mpris.update_metadata(&song_info).await {
+                    if let Err(err) = mpris.update_metadata(&song_info, lyrics).await {
                         warn!("设置 MPRIS metadata 失败: {err:?}");
                     }
                     if let Err(err) = mpris.set_playback_status(PlaybackStatus::Playing).await {
@@ -324,13 +325,29 @@ impl PlayerControls {
 
         self.set_property("duration", sec);
 
+        let lyrics: String = self.property("lyrics");
         if let Some(mpris) = imp.mpris.get() {
             if let Some(mut si) = self.get_current_song() {
                 si.duration = msec / 1000;
                 crate::MAINCONTEXT.spawn_local_with_priority(
                     Priority::LOW,
                     clone!(@weak mpris => async move {
-                        mpris.update_metadata(&si).await.ok();
+                        mpris.update_metadata(&si, lyrics).await.ok();
+                    }),
+                );
+            }
+        }
+    }
+
+    pub fn metadata_lyrics_update(&self, lrc: String) {
+        self.set_property("lyrics", lrc.clone());
+        let imp = self.imp();
+        if let Some(mpris) = imp.mpris.get() {
+            if let Some(mut si) = self.get_current_song() {
+                crate::MAINCONTEXT.spawn_local_with_priority(
+                    Priority::LOW,
+                    clone!(@weak mpris => async move {
+                        mpris.update_metadata(&si, lrc).await.ok();
                     }),
                 );
             }
@@ -654,7 +671,7 @@ impl PlayerControls {
 
 mod imp {
 
-    use gst::glib::Propagation;
+    use gst::glib::{ParamSpecString, Propagation};
 
     use super::*;
 
@@ -713,6 +730,7 @@ mod imp {
 
         // 播放条拖动结束时的值
         scale_value: Cell<f64>,
+        lyrics: RefCell<String>,
     }
 
     #[glib::object_subclass]
@@ -934,6 +952,7 @@ mod imp {
                     ParamSpecUInt64::builder("duration").build(),
                     ParamSpecBoolean::builder("like").readwrite().build(),
                     ParamSpecDouble::builder("scale-value").readwrite().build(),
+                    ParamSpecString::builder("lyrics").build(),
                 ]
             });
             PROPERTIES.as_ref()
@@ -965,6 +984,10 @@ mod imp {
                     let scale_value = value.get().expect("The value needs to be of type `bool`.");
                     self.scale_value.replace(scale_value);
                 }
+                "lyrics" => {
+                    let val = value.get().unwrap();
+                    self.lyrics.replace(val);
+                }
                 n => unimplemented!("{}", n),
             }
         }
@@ -977,6 +1000,7 @@ mod imp {
                 "duration" => self.duration.get().to_value(),
                 "like" => self.like.get().to_value(),
                 "scale-value" => self.scale_value.get().to_value(),
+                "lyrics" => self.lyrics.borrow().to_value(),
                 n => unimplemented!("{}", n),
             }
         }
diff --git a/src/ncmapi.rs b/src/ncmapi.rs
index 16b9506..fcccaac 100644
--- a/src/ncmapi.rs
+++ b/src/ncmapi.rs
@@ -169,19 +169,16 @@ impl NcmClient {
     pub async fn get_lyrics(&self, si: SongInfo) -> Result<String> {
         let mut path = LYRICS.clone();
         path.push(format!("{}-{}-{}.lrc", si.name, si.singer, si.album));
-        let re = regex::Regex::new(r"\[\d+:\d+.\d+\]").unwrap();
         if !path.exists() {
             if let Ok(lyr) = self.client.song_lyric(si.id).await {
                 let lrc = lyr.into_iter().collect::<Vec<String>>().join("\n");
                 fs::write(&path, &lrc)?;
-                let lrc = re.replace_all(&lrc, "").to_string();
                 Ok(lrc)
             } else {
                 Ok(gettextrs::gettext("No lyrics found!".to_owned()))
             }
         } else {
             let lrc = fs::read_to_string(&path)?;
-            let lrc = re.replace_all(&lrc, "").to_string();
             Ok(lrc)
         }
     }
diff --git a/src/window.rs b/src/window.rs
index 58dcd6e..c348c42 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -674,9 +674,12 @@ impl NeteaseCloudMusicGtk4Window {
 
     pub fn update_lyrics(&self, lrc: String) {
         let imp = self.imp();
+        let re = regex::Regex::new(r"\[\d+:\d+.\d+\]").unwrap();
+        let txt = re.replace_all(&lrc, "").to_string();
         let page = imp.playlist_lyrics_page.get().unwrap();
+        imp.player_controls.get().metadata_lyrics_update(lrc);
         if self.page_cur_playlist_lyrics_page() {
-            page.update_lyrics(lrc);
+            page.update_lyrics(txt);
         }
     }

貌似 Github 不支持上传 patch 文件,那直接贴这儿了。

@gmg137
Copy link
Owner

gmg137 commented Jan 2, 2024

没有使用mpris的歌词接口是为了减少网络请求次数,将歌词缓存在本地,当再次需要时可以直接从本地读取。缓存lrc和使用asText功能上是重复的,如果只实现一个的话缓存lrc性价比会更高一些。

@tuberry
Copy link
Author

tuberry commented Jan 2, 2024

上面的 patch 只是在更新 PlayListLyricsPage 的歌词时顺便更一下 Mpris,获取歌词的方式根本没变。更新 Mpris 也需要网络请求吗?

@gmg137
Copy link
Owner

gmg137 commented Jan 2, 2024

我的意思是,更新Mpris歌词与缓存歌词文件在目的上是重复的,如果二选一选择mpris的asText传送歌词,会造成每次都要请求网络。

另外如果只在打开播放列表页面时才触发更新mpris歌词,这样该操作是没有意义的。

@tuberry
Copy link
Author

tuberry commented Jan 2, 2024

另外如果只在打开播放列表页面时才触发更新mpris歌词,这样该操作是没有意义的。

在打开判定之前更新的。

这里启用桌面歌词支持时每次都会下载/读取本地歌词,顺便更新一下 Mpris 不好吗?读了不能传,要传不能存(读),苦涩的选择。目前如果本地有文件,扩展那边要多读一次,否则就多一次注定失败的网络请求,也算重复吧。既然如此,你可能要按需读取。

谢谢回答。

@tuberry tuberry closed this as not planned Won't fix, can't repro, duplicate, stale Jan 2, 2024
@tuberry
Copy link
Author

tuberry commented Jan 4, 2024

另外发现在这每250ms更新进度条时 Mpris 会发Seeked信号,同时更新Mpris::Position。这似乎都没有必要

建议在自动更新进度条时不要发Seeked,至于Position之前就建议参考Lollypop按需直接Gst.Player读取不用更新。

谢谢。

@gmg137
Copy link
Owner

gmg137 commented Jan 4, 2024

如果不更新 position 你的歌词插件该如何确定当前播放位置? 特别是反复前后拖拉进度条后,会不会有影响。
你说的按需直接从Gst.Player读取我没太明白,这里是谁来读取?

更新 seeked 是为了让别的 mpris 播放插件同步播放条进度。

我上次测试gnome-music 在反复前后拖拉进度条时好像会卡住,具体情况忘了,反正就是做了取舍才弄成现在这样。

最近在忙装修,可以把你的建议先说下,我等闲了再研究研究看怎么弄。

@tuberry
Copy link
Author

tuberry commented Jan 4, 2024

如果不更新 position 你的歌词插件该如何确定当前播放位置? 特别是反复前后拖拉进度条后,会不会有影响。

客户端维护有 timer,获取当前 position 一般为初始化而非同步。拖动进度条结束发一次Seeked就行,客户端不关心怎么拖的。

你说的按需直接从Gst.Player读取我没太明白,这里是谁来读取?

服务端Position不像Volume之类的会同步到客户端,客户端通过 org.freedesktop.DBus.Properties.Get 接口来获取 Position,所以服务端在实现此接口时,对Position直接返回 Gst.Player 的位置即可,而非定期set_position缓存一个随时过期的值待用。

我上次测试gnome-music 在反复前后拖拉进度条时好像会卡住,具体情况忘了,反正就是做了取舍才弄成现在这样。

gnome-music 的实现有问题,表现为拖动过程中 PlaybackStatusPlayingPaused 间反复横跳,与客户端无关。

更新 seeked 是为了让别的 mpris 播放插件同步播放条进度。

这听起来有点像某软件每秒截30次屏来实现屏幕共享。尤其结合上文,为某进度条每250ms发一次信号,却不愿为歌词更一次元数据,就显得很有意思:) 总之,这信号据说 indicates that the track position has changed in a way that is inconsistant with the current playing state,用于同步不失为另辟蹊径吧。

最近在忙装修,可以把你的建议先说下,我等闲了再研究研究看怎么弄。

祝好。建议先前有一些了,刚打开播放列表没找到移除歌曲的办法,不确定这是 Feature 还是 Bug。

就这样吧。

@gmg137 gmg137 reopened this Apr 8, 2024
@gmg137
Copy link
Owner

gmg137 commented Apr 8, 2024

你好,我在尝试使用 asText 提供歌词时,desktop-lyrics 没有正常工作,问题如下:

1、播放歌曲时,在使用asText推送歌词的情况下,desktop-lyrics 依然会自动下载歌词,测试应该是没有使用我用asText提供的歌词;

2、当播放下面的歌曲时,没有任何反应,也没有自动下载歌词。

https://music.163.com/song?id=2021437775
歌曲:My Stupid Heart (Kids Version)
歌手:Walk off the Earth

歌词如下(已测试去掉换行符问题依旧):

"[00:00.000] 作曲 : Gianni Luminati Nicassio/Jake Torrey/Lostboy/Michael Matosic/Sarah Blackwood/Tokyo Speirs\n[00:00.112]Hey, everybody\n[00:00.514]My mom 'n dad made this new song called \"My Stupid Heart\"\n[00:03.547]I'm gonna play it with my brothers\n[00:05.187]Let's gooooooo!!\n[00:07.074]\n[00:07.337]My stupid heart\n[00:08.881]Don't know\n[00:09.983]I've tried to let you go\n[00:11.850]So many times before\n[00:13.938]Then wound up at your door\n[00:15.765]My stupid\n[00:16.149]\n[00:16.524]Can't believe that I haven't figured out by now\n[00:19.634]Every time I call you up\n[00:21.842]All you do is let me down (Let's Go!)\n[00:24.487]\n[00:24.833]Shoulda known there was nothing about us I could change\n[00:28.105]Everytime we try to be friends\n[00:30.112]It always ends the same\n[00:32.207]\n[00:32.368]But when I try to remember\n[00:36.376]All the pain that we've been through\n[00:40.596]Something in me says whatever\n[00:44.781]And it brings me back to you\n[00:48.172]\n[00:48.869]My stupid heart\n[00:50.638]Don't know\n[00:51.631]I've tried to let you go\n[00:53.583]So many times before\n[00:55.604]Then wound up at your door\n[00:57.482]My stupid heart\n[00:58.854]Too late\n[00:59.796]Already on my way\n[01:01.920]If we go down in flames\n[01:04.123]Again then you can blame my stupid heart\n[01:09.832](Okay!)\n[01:11.917](Okay!)\n[01:13.068]You can blame my stupid heart\n[01:18.386](Okay!)\n[01:20.658](Okay!)\n[01:21.279]You can blame my stupid heart\n[01:23.518]\n[01:23.737]Every now and then I get inside my head\n[01:26.555]Try to leave your text unread\n[01:28.357]But I wind up here instead\n[01:30.968]\n[01:31.676]I shoulda bit my tongue while we were still ahead\n[01:34.787]Yeah always had to be right\n[01:36.800]Til we had nothing left\n[01:39.079]\n[01:39.238]But when I try to remember\n[01:43.129]All the pain that we've been through\n[01:47.364]Something in me says whatever\n[01:51.615]And it brings me back to you\n[01:55.052]\n[01:55.613]My stupid heart\n[01:57.328]Don't know\n[01:58.440]I've tried to let you go\n[02:00.248]So many times before\n[02:02.418]Then wound up at your door\n[02:04.002]My stupid heart\n[02:05.665]Too late\n[02:06.822]Already on my way\n[02:08.590]If we go down in flames\n[02:10.830]Again then you can blame my stupid heart (Let's Go!)\n[02:16.703](Okay!)\n[02:18.945](Okay!)\n[02:19.437]\n[02:19.607]You can blame my stupid heart\n[02:23.868]I've tried to let you go\n[02:25.389]So many times before\n[02:29.064]You can blame my stupid heart\n[02:30.832]Too late\n[02:32.142]Already on my way\n[02:33.964]If we go down in flames\n[02:35.902]Again then you can blame my stupid heart\n[02:39.253]Too late\n[02:40.317]Already on my way\n[02:42.139]If we go down in flames\n[02:44.276]Again then you can blame my stupid heart"

@tuberry
Copy link
Author

tuberry commented Apr 8, 2024

GNOME45及以后的版本才支持,如果版本没问题看看Metadata是否提供了歌词:

gdbus call --session --dest org.mpris.MediaPlayer2.NeteaseCloudMusicGtk4 --object-path /org/mpris/MediaPlayer2 --method org.freedesktop.DBus.Properties.Get org.mpris.MediaPlayer2.Player Metadata

另外无asText字段时会尝试下载,所以初始化暂无歌词时需设置asText为空(字符串),有歌词再更新即可。

@gmg137
Copy link
Owner

gmg137 commented Apr 9, 2024

GNOME 版本是 45.3, Metadata 中可以正常获取歌词:
(<{'mpris:artUrl': <'file:///home/gmg/.cache/netease-cloud-music-gtk4/159843058-songlist.jpg'>, 'mpris:length': <int64 168112000>, 'mpris:trackid': <objectpath '/com/gitee/gmg137/NeteaseCloudMusicGtk4/2021437775'>, 'xesam:album': <'My Stupid Heart (Kids Version)'>, 'xesam:artist': <['Walk off the Earth']>, 'xesam:asText': <"[00:00.000] 作词 : Kim Taylor\n[00:00.000] 作曲 : Kim Taylor\n[00:00.000]La di da di da da\n[00:03.860]La di da di da da\n[00:08.110]La di da di da da\n[00:12.000]La da da\n[00:14.760]\n[00:16.510]I am tied by truth like an anchor\n[00:25.440]Anchored to a bottomless sea\n[00:32.340]I am floating freely in the heavens\n[00:41.440]Held in by your heart's gravity\n[00:46.160]\n[00:48.920]All because of love\n[00:53.040]All because of love\n[00:57.410]Even though sometimes\n[00:59.999]You don't know who I am\n[01:03.259]\n[01:05.239]I am you..\n[01:09.290]Everything you do\n[01:13.168]Anything you say,\n[01:17.319]You want me to be\n[01:21.328]You and me..\n[01:25.290]We're charms on a chain\n[01:29.790]Linked eternally one we can't undo\n[01:37.110]And I am you\n[01:40.790]\n[01:41.890]La di da di da da\n[01:45.840]La di da di da da\n[01:49.799]La da da\n[01:52.099]\n[01:53.920]All my senses awaken to the changes yeah\n[02:02.209]And I feel alive inside my own skin\n[02:10.159]All my reasons tell me just how strange it is\n[02:18.409]Coming home to a place I've always been\n[02:24.199]\n[02:26.439]And it's all for love\n[02:30.530]And it's all for love\n[02:34.739]Even though sometimes,\n[02:37.429]I don't know who I am\n[02:40.639]\n[02:42.629]I am you..\n[02:46.699]Everything you do\n[02:50.360]Anything you say,\n[02:54.549]You want me to be\n[02:58.810]You and me..\n[03:02.739]We're charms on a chain\n[03:07.109]Linked eternally one we can't undo, ohhh\n[03:14.829]I am you...\n[03:18.979]La di da di da da\n[03:22.869]La di da di da da (anything you say)\n[03:27.069]La da da (everything you do)\n[03:30.970]La di da di da da (I am you...)\n[03:35.129]La di da di da da\n[03:39.169]La di da di da da (anything you say)\n[03:43.160]La da da (everything you do)\n[03:47.209]I am you... (La di da di da da)\n[03:51.190]I am you... (La di da di da da)\n[03:55.119]I am you... (La di da di da da)\n[03:59.049]I am you... (La di da di da da)\n[04:01.639]\n[04:03.199]I am you... (La di da di da da)\n[04:07.139]I am you... (La di da di da da)\n[04:11.379]Everything you do....\n[04:18.069]\n[04:20.459]Fading away...\n[04:28.379]">, 'xesam:title': <'My Stupid Heart (Kids Version)'>}>,)

下面是我编译好的程序你可以测试下,如果不能运行我再把源代码打包了发你。
netease-cloud-music-gtk4.zip

@tuberry
Copy link
Author

tuberry commented Apr 9, 2024

此提交(对应扩展网站上的版本19)在GNOME46上测试发现你编译的 grsource 文件路径硬编码为本地,不过我用软连接解决了。
随机播放歌曲均显示为你上方提供的歌词,扩展本身并未下载/保存任何文件:

lrc-ncm-gtk

@gmg137
Copy link
Owner

gmg137 commented Apr 12, 2024

已经可以正常显示了,不过osdlyrics 貌似不支持 xesam:asText 接口,这样的话还是要保留从歌词文件读取歌词的功能。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants