From 1ac3221a5c74fb20f71fb8c078bd9fa904b43a3d Mon Sep 17 00:00:00 2001
From: lainsce
Date: Mon, 5 Aug 2024 19:59:34 -0300
Subject: [PATCH] Initial work on Lyrics pane
---
data/style.css | 1 -
data/ui/window.blp | 39 ++++-
meson.build | 6 +
src/Utils/lyric.vala | 30 ++++
src/Utils/lyrics-catch.vala | 292 ++++++++++++++++++++++++++++++++++++
src/Widgets/lyric-page.vala | 144 ++++++++++++++++++
src/application.vala | 90 +++++------
src/window.vala | 142 ++++++++++--------
8 files changed, 634 insertions(+), 110 deletions(-)
create mode 100644 src/Utils/lyric.vala
create mode 100644 src/Utils/lyrics-catch.vala
create mode 100644 src/Widgets/lyric-page.vala
diff --git a/data/style.css b/data/style.css
index ddc436a..86b6735 100644
--- a/data/style.css
+++ b/data/style.css
@@ -43,7 +43,6 @@
.side-pane .bottom-bar {
border-radius: 0 0 12px 12px;
- margin: 0 1px 1px 1px;
}
.music-navrail {
diff --git a/data/ui/window.blp b/data/ui/window.blp
index e1622bb..0df9941 100644
--- a/data/ui/window.blp
+++ b/data/ui/window.blp
@@ -171,15 +171,42 @@ template VictrolaMainWindow : He.ApplicationWindow {
show-back: bind album.folded;
show-left-title-buttons: bind album.folded;
show-right-title-buttons: true;
+
+ [titlebar-toggle]
+ ToggleButton lyrics_btn {
+ icon-name: "edit-find-symbolic";
+ tooltip-text: _("See Lyrics");
+ }
}
- Box info_box {
- orientation: vertical;
- vexpand: true;
+ Stack infostack {
+ StackPage {
+ name: "info";
+ child:
+ Box info_box {
+ orientation: vertical;
+ vexpand: true;
- styles [
- "side-pane"
- ]
+ styles [
+ "side-pane"
+ ]
+ }
+ ;
+ }
+
+ StackPage {
+ name: "lyrics";
+ child:
+ Box lyrics_box {
+ orientation: vertical;
+ vexpand: true;
+
+ styles [
+ "side-pane"
+ ]
+ }
+ ;
+ }
}
}
;
diff --git a/meson.build b/meson.build
index 5c2085a..efa0c90 100644
--- a/meson.build
+++ b/meson.build
@@ -52,6 +52,9 @@ dependencies = [
dependency('gtk4'),
dependency('gee-0.8'),
dependency('libhelium-1'),
+ dependency('libsoup-2.4'),
+ dependency('json-glib-1.0'),
+ dependency('libxml-2.0'),
dependency('libbismuth-1', fallback: [ 'libbismuth', 'libbismuth_dep' ], version: '>=1.0.0'),
libm_dep,
posix_dep,
@@ -70,7 +73,10 @@ sources = [
'src/Utils/mpris.vala',
'src/Utils/song-store.vala',
'src/Utils/tag-parser.vala',
+ 'src/Utils/lyric.vala',
+ 'src/Utils/lyrics-catch.vala',
'src/Widgets/info-page.vala',
+ 'src/Widgets/lyric-page.vala',
'src/Widgets/play-bar.vala',
'src/Widgets/play-bar-mobile.vala',
'src/Widgets/song-entry.vala',
diff --git a/src/Utils/lyric.vala b/src/Utils/lyric.vala
new file mode 100644
index 0000000..377a061
--- /dev/null
+++ b/src/Utils/lyric.vala
@@ -0,0 +1,30 @@
+using Gee;
+
+namespace Victrola {
+ public class Lyric : Object {
+
+ /* Fields */
+ public string title { get; set; }
+ public string artist { get; set; }
+ public string lyric { get; set; }
+ public string lyric_sync { get; set; }
+ private string[] urls;
+ public string current_url { get; set; }
+ public string current_sync_url { get; set; }
+
+ public void add_url(string url) {
+ urls += url;
+ }
+
+ public int get_len_urls() {
+ return urls.length;
+ }
+
+ public string get_url_from_index(int index) {
+ if (index > get_len_urls()) {
+ return "";
+ }
+ return urls[index];
+ }
+ }
+}
diff --git a/src/Utils/lyrics-catch.vala b/src/Utils/lyrics-catch.vala
new file mode 100644
index 0000000..103b8e9
--- /dev/null
+++ b/src/Utils/lyrics-catch.vala
@@ -0,0 +1,292 @@
+/*
+* Copyright 2024 Fyra Labs
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public
+* License along with this program; if not, write to the
+* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+* Boston, MA 02110-1301 USA
+*
+*/
+
+namespace Victrola {
+
+ public class LyricsAPI : GLib.Object {
+ public LyricsAPI () {
+
+ }
+
+ }
+
+ public class LyricsFetcher : GLib.Object {
+
+ private string[] lyrics_apis = {};
+
+ public LyricsFetcher () {
+ lyrics_apis += "music_163";
+ lyrics_apis += "letras_mus";
+ lyrics_apis += "lyrics_wikia";
+ }
+
+ private Lyric? get_music_163(string title, string artist){
+ var 163_url = "http://music.163.com/api/search/pc?offset=0&limit=1&type=1&s=";
+ var session = new Soup.Session ();
+ session.timeout = 5;
+ var url = 163_url + title + "," + artist;
+ var message = new Soup.Message ("GET", url);
+ /* send a sync request */
+ session.send_message (message);
+ try {
+ var parser = new Json.Parser ();
+ parser.load_from_data ((string) message.response_body.flatten ().data, -1);
+
+ var root_object = parser.get_root ().get_object ();
+ if(root_object.get_int_member ("code") == 200){
+ var result = root_object.get_object_member ("result");
+ var songs = result.get_array_member ("songs");
+ if(songs.get_elements().length() > 0){
+ var song = songs.get_object_element(0);
+ var song_id = song.get_int_member("id");
+ 163_url = "https://music.163.com/api/song/lyric?os=pc&lv=-1&kv=-1&tv=-1&id=";
+ session = new Soup.Session ();
+ session.timeout = 5;
+ url = 163_url + song_id.to_string();
+ message = new Soup.Message ("GET", url);
+ /* send a sync request */
+ session.send_message (message);
+ try {
+ parser = new Json.Parser ();
+ parser.load_from_data ((string) message.response_body.flatten ().data, -1);
+
+ root_object = parser.get_root ().get_object ();
+ if(root_object.has_member ("lrc")){
+ var lyric = new Lyric();
+ var lrc = root_object.get_object_member ("lrc");
+ var string_lyric = lrc.get_string_member ("lyric");
+ var result_lyric_sync = "";
+ var result_lyric = "";
+ var split_lyric = string_lyric.split("\n");
+ GLib.Regex exp = /\[(.*?)\](.*)/;
+ for(var i = 0;i < split_lyric.length; i++){
+ GLib.MatchInfo mi;
+ exp.match (split_lyric[i], 0, out mi);
+ mi.matches();
+ var fetch_position = mi.fetch (1);
+ var position = "";
+ if(fetch_position != null){
+ var position_split = new string[0];
+ if(fetch_position.contains(":")) {
+ position_split = fetch_position.split(":");
+ } else if (fetch_position.contains(";")) {
+ position_split = fetch_position.split(";");
+ } else {
+ continue;
+ }
+ if(position_split.length > 0){
+ if(position_split[1] == "00.000" || position_split[1] == ""){
+ continue;
+ }
+ position = (int.parse(position_split[1].split(".")[0]) + (int.parse(position_split[0]) * 60)).to_string();
+ if(result_lyric_sync != ""){
+ result_lyric_sync += "|-|" + position + "\n";
+ }
+ }
+ }
+ result_lyric_sync += mi.fetch (2) + "|-|" + position;
+
+ result_lyric += mi.fetch (2) + "\n";
+ }
+ lyric.lyric = result_lyric;
+ lyric.current_url = "https://music.163.com";
+ lyric.lyric_sync = result_lyric_sync;
+ return lyric;
+ }
+
+ } catch (Error e) {
+ print ("I guess something is not working...\n");
+ return null;
+ }
+ }
+ }
+
+
+ } catch (Error e) {
+ stderr.printf ("I guess something is not working...\n");
+ return null;
+ }
+ return null;
+ }
+
+ private Lyric? get_lyrics_wikia(string title, string artist){
+ var seeds_url = "http://lyrics.wikia.com/wiki/";
+ var session = new Soup.Session ();
+ session.timeout = 5;
+ var url = seeds_url + artist.replace("'", "'").replace("&", "e") + ":" + title;
+ var message = new Soup.Message ("GET", url);
+
+ /* send a sync request */
+ session.send_message (message);
+
+ // parse html
+ var html_cntx = new Html.ParserCtxt();
+ html_cntx.use_options(Html.ParserOption.NOERROR + Html.ParserOption.NOWARNING);
+ var result_string = (string) message.response_body.flatten ().data;
+
+ var doc = html_cntx.read_doc(result_string.replace("
", "\n"), "");
+ var lyricbox = getValue(doc, "//div[contains(@class, 'lyricbox')]");
+
+ if(lyricbox == null){
+ return null;
+ }
+ if(lyricbox.contains("Unfortunately, we are not licensed to display the full lyrics for this song at the moment.")){
+ return null;
+ }
+ var lyric = new Lyric();
+ lyric.lyric = lyricbox;
+ lyric.current_url = url;
+ return lyric;
+ }
+
+ private Lyric? get_letras_mus(string title, string artist){
+ var letras_url = "https://m.letras.mus.br/";
+ var session = new Soup.Session ();
+ session.timeout = 5;
+ var url = letras_url + artist.replace(" ", "-").replace("'", "-").replace("&", "e") + "/" + title.replace(" ", "-").split("(")[0];
+ var message = new Soup.Message ("GET", url);
+
+ /* send a sync request */
+ session.send_message (message);
+
+ // parse html
+ var html_cntx = new Html.ParserCtxt();
+ html_cntx.use_options(Html.ParserOption.NOERROR + Html.ParserOption.NOWARNING);
+ var result_string = (string) message.response_body.flatten ().data;
+
+ var doc = html_cntx.read_doc(result_string.replace("
", "\n").replace("
", "\n\n").replace("
", "").replace("
", ""), "");
+
+ // check song
+ var check_song = getValue(doc, "//div[contains(@class, 'lyric-title')]//h1");
+
+ if(check_song == null || check_song.down().contains(title.down()) == false){
+ return null;
+ }
+
+ var lyricbox = getValue(doc, "//div[contains(@class, 'lyric-tra_l')]");
+ var remove_first_line = false;
+ if(lyricbox == null){
+ lyricbox = getValue(doc, "//div[contains(@class, 'lyric-cnt')]");
+
+ if(lyricbox == null){
+ return null;
+ }
+ }else{
+ remove_first_line = true;
+ }
+
+ if(lyricbox.contains("Essa música foi removida em razão de solicitação do(s) titular(es) da obra.")){
+ return null;
+ }
+ var array_subtitle = "";
+ var lyric = new Lyric();
+
+ if(remove_first_line == true){
+ Regex regex = new GLib.Regex ("^" + check_song);
+ lyricbox = regex.replace (lyricbox, lyricbox.length, 0, "");
+ lyricbox = lyricbox.strip();
+ }
+ lyric.lyric = lyricbox;
+ lyric.current_url = url;
+ lyric.lyric_sync = array_subtitle;
+ return lyric;
+ }
+
+ public Lyric? get_lyric(string title, string artist){
+ Lyric? r = null;
+ var n_title = remove_accents(title.replace("?", "").down());
+ var n_artist = remove_accents(artist.down());
+ foreach (var s_api in lyrics_apis) {
+ if(s_api == "music_163"){
+ r = get_music_163(n_title, n_artist);
+ }else if(s_api == "lyrics_wikia"){
+ r = get_lyrics_wikia(n_title, n_artist);
+ }else if(s_api == "letras_mus"){
+ r = get_letras_mus(n_title, n_artist);
+ }else{
+ return null;
+ }
+ if(r == null){
+ continue;
+ }
+ if(r.lyric != ""){
+
+ break;
+ }
+
+ }
+ if(r != null){
+ r.title = title;
+ r.artist = artist;
+ }
+ return r;
+ }
+
+ public static string? getValue(Html.Doc* doc, string xpath, bool remove = false){
+ Xml.XPath.Context cntx = new Xml.XPath.Context(doc);
+ Xml.XPath.Object* res = cntx.eval_expression(xpath);
+
+ if(res == null)
+ {
+ return null;
+ }
+ else if(res->type != Xml.XPath.ObjectType.NODESET || res->nodesetval == null)
+ {
+ delete res;
+ return null;
+ }
+
+ Xml.Node* node = res->nodesetval->item(0);
+ string result = cleanString(node->get_content());
+
+ if(remove)
+ {
+ node->unlink();
+ node->free_list();
+ }
+
+ delete res;
+ return result;
+ }
+
+ public static string cleanString(string? text){
+ if(text == null)
+ return "";
+ var tmpText = text;
+ var array = tmpText.split(" ");
+ tmpText = "";
+
+ foreach(string word in array)
+ {
+ if(word.chug() != "")
+ {
+ tmpText += word + " ";
+ }
+ }
+
+ return tmpText.chomp();
+ }
+
+ private string remove_accents(string input){
+ var new_string = input.replace("ê", "e").replace("á", "á").replace("à", "à").replace("ã", "a").replace("ó", "o").replace("ç", "c").replace("í", "i").replace("ú", "u").replace("å", "a").replace("ö", "o");
+ return new_string;
+ }
+ }
+}
diff --git a/src/Widgets/lyric-page.vala b/src/Widgets/lyric-page.vala
new file mode 100644
index 0000000..419323b
--- /dev/null
+++ b/src/Widgets/lyric-page.vala
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2022 Fyra Labs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Victrola {
+ public class LyricPage : He.Bin {
+ Application app = (Application) GLib.Application.get_default ();
+ public Gtk.Window window { get; construct; }
+ LyricsFetcher fetcher;
+ Lyric current_lyric;
+ Gtk.TextView view;
+ Gtk.ScrolledWindow scrolled;
+ public string last_title;
+ public string last_artist;
+ private int64 last_position;
+ private bool was_paused;
+
+ public LyricPage (Gtk.Window window) {
+ Object (
+ margin_start: 18,
+ margin_end: 18,
+ window: window
+ );
+ last_title = "";
+ last_artist = "";
+ last_position = 0;
+ was_paused = false;
+ }
+
+ construct {
+ var player = app.player;
+
+ fetcher = new LyricsFetcher ();
+
+ view = new Gtk.TextView ();
+ view.editable = false;
+ view.set_wrap_mode (Gtk.WrapMode.WORD);
+ view.vexpand = true;
+ view.add_css_class ("view-lyric");
+
+ scrolled = new Gtk.ScrolledWindow ();
+ scrolled.margin_bottom = 18;
+ scrolled.set_child (view);
+
+ var main_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 12);
+ main_box.vexpand = main_box.hexpand = true;
+ main_box.margin_top = 18;
+ main_box.append (scrolled);
+
+ this.child = main_box;
+ this.vexpand = this.hexpand = true;
+
+ // update_lyric.begin (app);
+ }
+
+ private async void update_lyric (Application app) {
+ ThreadFunc run = () => {
+ var sub = "";
+ var title = "";
+ var url = "";
+ var lyric = "";
+ try {
+ bool error = false;
+ var r = fetcher.get_lyric (app.current_song.title, app.current_song.artist);
+ if (r != null) {
+ lyric = r.lyric;
+ url = r.current_url;
+ title = r.title;
+ sub = r.lyric_sync;
+ if (title != last_title) {
+ return null;
+ }
+ Idle.add (() => {
+ clean_text_buffer ();
+ return false;
+ });
+
+ if (lyric == "" || lyric == null) {
+ error = true;
+ }
+ current_lyric = r;
+ } else {
+ error = true;
+ }
+
+ if (error == true) {
+ scrolled.hide ();
+ } else {
+ Idle.add (() => {
+ insert_text (lyric);
+ show_lyrics ();
+ return false;
+ });
+ }
+ } catch (Error e) {
+ warning ("Failed to get lyric: %s", e.message);
+ }
+ return null;
+ };
+
+ try {
+ new Thread.try (null, run);
+ } catch (Error e) {
+ warning (e.message);
+ }
+ }
+
+ private void insert_text (string text) {
+ Gtk.TextIter text_start;
+ Gtk.TextIter text_end;
+
+ view.buffer.get_start_iter (out text_start);
+ view.buffer.insert (ref text_start, text, text.length);
+ view.buffer.get_end_iter (out text_end);
+ view.buffer.apply_tag_by_name ("lyric", text_start, text_end);
+ }
+
+ private void clean_text_buffer () {
+ Gtk.TextIter start;
+ Gtk.TextIter end;
+ view.buffer.get_start_iter (out start);
+ view.buffer.get_end_iter (out end);
+ view.buffer.delete (ref start, ref end);
+ }
+
+ private void show_lyrics () {
+ scrolled.get_vadjustment ().set_value (0);
+ scrolled.show ();
+ }
+ }
+}
diff --git a/src/application.vala b/src/application.vala
index 29b03bf..144a37f 100644
--- a/src/application.vala
+++ b/src/application.vala
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright 2022 Fyra Labs
*
* This program is free software: you can redistribute it and/or modify
@@ -58,7 +58,7 @@ namespace Victrola {
}
public Application () {
- Object (application_id: "com.fyralabs.Victrola", flags: ApplicationFlags.HANDLES_OPEN);
+ Object (application_id : "com.fyralabs.Victrola", flags : ApplicationFlags.HANDLES_OPEN);
ActionEntry[] action_entries = {
{ ACTION_ABOUT, show_about },
@@ -89,11 +89,11 @@ namespace Victrola {
{ ACTION_QUIT, "q" }
};
foreach (var item in action_keys) {
- set_accels_for_action (ACTION_APP + item.name, {item.key});
+ set_accels_for_action (ACTION_APP + item.name, { item.key });
}
_song_list.model = _song_store.store;
- _song_store.sort_mode = (SortMode) (_settings?.get_uint ("sort-mode") ?? SortMode.TITLE);
+ _song_store.sort_mode = (SortMode) (_settings ? .get_uint ("sort-mode") ?? SortMode.TITLE);
_player.end_of_stream.connect (() => {
if (single_loop) {
@@ -107,10 +107,10 @@ namespace Victrola {
_player.tag_parsed.connect (on_tag_parsed);
var mpris_id = Bus.own_name (BusType.SESSION,
- "org.mpris.MediaPlayer2." + application_id,
- BusNameOwnerFlags.NONE,
- on_bus_acquired,
- null, null
+ "org.mpris.MediaPlayer2." + application_id,
+ BusNameOwnerFlags.NONE,
+ on_bus_acquired,
+ null, null
);
if (mpris_id == 0)
warning ("Initialize MPRIS session failed\n");
@@ -118,8 +118,8 @@ namespace Victrola {
protected override void startup () {
Gdk.RGBA accent_color = { 0 };
- accent_color.parse("#F7812B");
- default_accent_color = He.Color.from_gdk_rgba(accent_color);
+ accent_color.parse ("#F7812B");
+ default_accent_color = He.Color.from_gdk_rgba (accent_color);
override_accent_color = true;
scheme_factory = new He.ContentScheme ();
@@ -129,13 +129,13 @@ namespace Victrola {
Bis.init ();
- typeof(InfoPage).ensure ();
- typeof(PlayBar).ensure ();
- typeof(SongEntry).ensure ();
+ typeof (InfoPage).ensure ();
+ typeof (PlayBar).ensure ();
+ typeof (SongEntry).ensure ();
new MainWindow (this);
- // Must load tag cache after the app register (GLib init), to make sort works
+ // Must load tag cache after the app register (GLib init), to make sort works
_song_store.load_tag_cache_async.begin ((obj, res) => {
_song_store.load_tag_cache_async.end (res);
});
@@ -179,7 +179,7 @@ namespace Victrola {
public void on_open () {
var music_dir = get_music_folder ();
var chooser = new Gtk.FileChooserNative (null, active_window,
- Gtk.FileChooserAction.SELECT_FOLDER, null, null);
+ Gtk.FileChooserAction.SELECT_FOLDER, null, null);
try {
chooser.set_file (music_dir);
} catch (Error e) {
@@ -189,7 +189,7 @@ namespace Victrola {
if (id == Gtk.ResponseType.ACCEPT) {
var dir = chooser.get_file ();
if (dir != null && dir != music_dir) {
- settings.set_string ("music-dir", ((!)dir).get_uri ());
+ settings.set_string ("music-dir", ((!) dir).get_uri ());
reload_song_store ();
}
}
@@ -208,8 +208,8 @@ namespace Victrola {
var song = _song_list.get_item (value) as Song;
if (song != null && _current_song != song) {
_current_song = song;
- _player.uri = ((!)song).uri;
- song_changed ((!)song);
+ _player.uri = ((!) song).uri;
+ song_changed ((!) song);
}
if (_current_item != value) {
var old_item = _current_item;
@@ -262,11 +262,11 @@ namespace Victrola {
current_item = current_item + 1;
}
- public void play_pause() {
+ public void play_pause () {
_player.playing = !_player.playing;
}
- public void stop() {
+ public void stop () {
_player.playing = false;
}
@@ -275,8 +275,9 @@ namespace Victrola {
}
public void sort_by (SimpleAction action, Variant? parameter) {
- sort_mode = (SortMode) (parameter?.get_uint32 () ?? 2);
+ sort_mode = (SortMode) (parameter ? .get_uint32 () ?? 2);
_settings?.set_uint ("sort-mode", sort_mode);
+
find_current_item ();
}
@@ -292,14 +293,14 @@ namespace Victrola {
public void toggle_search () {
var win = active_window as MainWindow;
if (win != null)
- ((!)win).search_btn.active = ! ((!)win).search_btn.active;
+ ((!) win).search_btn.active = !((!) win).search_btn.active;
}
public bool find_current_item () {
if (_song_list.get_item (_current_item) == _current_song)
return false;
- // find current item
+ // find current item
var old_item = _current_item;
var count = _song_list.get_n_items ();
_current_item = -1;
@@ -344,7 +345,7 @@ namespace Victrola {
} else if (_current_song != null && _current_song == _song_list.get_item (_current_item)) {
play_item = _current_item;
} else {
- var uri = _current_song?.uri ?? _settings.get_string ("played-uri");
+ var uri = _current_song ? .uri ?? _settings.get_string ("played-uri");
if (uri.length > 0) {
var count = _song_list.get_n_items ();
for (var i = 0; i < count; i++) {
@@ -361,19 +362,19 @@ namespace Victrola {
public void show_about () {
var about = new He.AboutWindow (
- active_window,
- "Victrola" + Config.NAME_SUFFIX,
- Config.APP_ID,
- Config.VERSION,
- Config.APP_ID,
- "https://github.com/tau-OS/victrola/tree/main/po",
- "https://github.com/tau-OS/victrola/issues",
- "https://github.com/tau-OS/victrola/",
- {},
- {"Fyra Labs"},
- 2023,
- He.AboutWindow.Licenses.GPLV3,
- He.Colors.ORANGE
+ active_window,
+ "Victrola" + Config.NAME_SUFFIX,
+ Config.APP_ID,
+ Config.VERSION,
+ Config.APP_ID,
+ "https://github.com/tau-OS/victrola/tree/main/po",
+ "https://github.com/tau-OS/victrola/issues",
+ "https://github.com/tau-OS/victrola/",
+ {},
+ { "Fyra Labs" },
+ 2023,
+ He.AboutWindow.Licenses.GPLV3,
+ He.Colors.ORANGE
);
about.present ();
}
@@ -391,14 +392,15 @@ namespace Victrola {
private async void on_tag_parsed (string? album, string? artist, string? title, Gst.Sample? image) {
_cover_image = image;
if (_current_song != null) {
- var song = (!)current_song;
+ var song = (!) current_song;
song_tag_parsed (song, image);
string? cover_uri = null;
if (image != null) {
var file = File.new_build_filename (Environment.get_tmp_dir (), application_id + "_" + str_hash (song.cover_uri).to_string ("%x"));
- yield save_sample_to_file (file, (!)image);
+ yield save_sample_to_file (file, (!) image);
yield delete_cover_tmp_file_async ();
+
_cover_tmp_file = file;
cover_uri = file.get_uri ();
}
@@ -411,23 +413,27 @@ namespace Victrola {
}
}
}
+
public static async void save_sample_to_file (File file, Gst.Sample sample) {
try {
var buffer = sample.get_buffer ();
Gst.MapInfo? info = null;
- if (buffer?.map (out info, Gst.MapFlags.READ) ?? false) {
+ if (buffer ? .map (out info, Gst.MapFlags.READ) ?? false) {
var stream = yield file.create_async (FileCreateFlags.NONE);
+
yield stream.write_all_async (info?.data, Priority.DEFAULT, null, null);
- buffer?.unmap ((!)info);
+
+ buffer?.unmap ((!) info);
}
} catch (Error e) {
}
}
+
private File? _cover_tmp_file = null;
private async void delete_cover_tmp_file_async () {
try {
if (_cover_tmp_file != null) {
- yield ((!)_cover_tmp_file).delete_async ();
+ yield ((!) _cover_tmp_file).delete_async ();
_cover_tmp_file = null;
}
} catch (Error e) {
diff --git a/src/window.vala b/src/window.vala
index a86f526..743a10e 100644
--- a/src/window.vala
+++ b/src/window.vala
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright 2022 Fyra Labs
*
* This program is free software: you can redistribute it and/or modify
@@ -31,12 +31,16 @@ namespace Victrola {
[GtkChild]
private unowned Gtk.Box info_box;
[GtkChild]
+ private unowned Gtk.Box lyrics_box;
+ [GtkChild]
public unowned Gtk.Box infogrid;
[GtkChild]
private unowned He.SideBar listgrid;
[GtkChild]
private unowned Gtk.Stack stack;
[GtkChild]
+ private unowned Gtk.Stack infostack;
+ [GtkChild]
private unowned Gtk.GridView list_view1;
[GtkChild]
private unowned Gtk.GridView list_view2;
@@ -45,6 +49,8 @@ namespace Victrola {
[GtkChild]
public unowned Gtk.ToggleButton search_btn;
[GtkChild]
+ public unowned Gtk.ToggleButton lyrics_btn;
+ [GtkChild]
private unowned Gtk.MenuButton menu_btn;
[GtkChild]
private unowned Gtk.SearchEntry search_entry;
@@ -66,37 +72,37 @@ namespace Victrola {
public SortMode sort_mode {
set {
switch (value) {
- case SortMode.ALBUM:
- list_view1.set_visible (true);
- list_view2.set_visible (false);
- list_view3.set_visible (false);
- num1 = list_view1.get_model ().get_n_items ();
- search_entry.placeholder_text = num1.to_string () + " " + (_("songs"));
- break;
- case SortMode.ARTIST:
- list_view1.set_visible (false);
- list_view2.set_visible (true);
- list_view3.set_visible (false);
- num2 = list_view2.get_model ().get_n_items ();
- search_entry.placeholder_text = num2.to_string () + " " + (_("songs"));
- break;
- case SortMode.RECENT:
- case SortMode.SHUFFLE:
- break;
- default:
- list_view1.set_visible (false);
- list_view2.set_visible (false);
- list_view3.set_visible (true);
- num3 = list_view3.get_model ().get_n_items ();
- search_entry.placeholder_text = num3.to_string () + " " + (_("songs"));
- break;
+ case SortMode.ALBUM:
+ list_view1.set_visible (true);
+ list_view2.set_visible (false);
+ list_view3.set_visible (false);
+ num1 = list_view1.get_model ().get_n_items ();
+ search_entry.placeholder_text = num1.to_string () + " " + (_("songs"));
+ break;
+ case SortMode.ARTIST:
+ list_view1.set_visible (false);
+ list_view2.set_visible (true);
+ list_view3.set_visible (false);
+ num2 = list_view2.get_model ().get_n_items ();
+ search_entry.placeholder_text = num2.to_string () + " " + (_("songs"));
+ break;
+ case SortMode.RECENT:
+ case SortMode.SHUFFLE:
+ break;
+ default:
+ list_view1.set_visible (false);
+ list_view2.set_visible (false);
+ list_view3.set_visible (true);
+ num3 = list_view3.get_model ().get_n_items ();
+ search_entry.placeholder_text = num3.to_string () + " " + (_("songs"));
+ break;
}
}
}
public MainWindow (Application app) {
Object (
- application: app
+ application: app
);
this.icon_name = app.application_id;
@@ -120,6 +126,17 @@ namespace Victrola {
info_box_mobile.append (play_bar_mobile);
+ var lyric_page = new LyricPage (this);
+ lyrics_box.append (lyric_page);
+
+ lyrics_btn.toggled.connect (() => {
+ if (lyrics_btn.active) {
+ infostack.set_visible_child_name ("lyrics");
+ } else {
+ infostack.set_visible_child_name ("info");
+ }
+ });
+
action_set_enabled (ACTION_APP + ACTION_PREV, false);
action_set_enabled (ACTION_APP + ACTION_PLAY, false);
action_set_enabled (ACTION_APP + ACTION_NEXT, false);
@@ -129,7 +146,7 @@ namespace Victrola {
var factory = new Gtk.SignalListItemFactory ();
factory.setup.connect ((item) => {
- ((Gtk.ListItem)item).child = new SongEntry ();
+ ((Gtk.ListItem) item).child = new SongEntry ();
});
factory.bind.connect (on_bind_item);
list_view1.factory = factory;
@@ -141,7 +158,7 @@ namespace Victrola {
var factory2 = new Gtk.SignalListItemFactory ();
factory2.setup.connect ((item) => {
- ((Gtk.ListItem)item).child = new SongEntry ();
+ ((Gtk.ListItem) item).child = new SongEntry ();
});
factory2.bind.connect (on_bind_item);
list_view2.factory = factory2;
@@ -153,7 +170,7 @@ namespace Victrola {
var factory3 = new Gtk.SignalListItemFactory ();
factory3.setup.connect ((item) => {
- ((Gtk.ListItem)item).child = new SongEntry ();
+ ((Gtk.ListItem) item).child = new SongEntry ();
});
factory3.bind.connect (on_bind_item);
list_view3.factory = factory3;
@@ -213,12 +230,12 @@ namespace Victrola {
private async void on_bind_item (Gtk.SignalListItemFactory factory, Object item) {
var app = (Application) application;
- var entry = (SongEntry) ((Gtk.ListItem)item).child;
- var song = (Song) ((Gtk.ListItem)item).item;
- entry.playing = ((Gtk.ListItem)item).position == app.current_item;
+ var entry = (SongEntry) ((Gtk.ListItem) item).child;
+ var song = (Song) ((Gtk.ListItem) item).item;
+ entry.playing = ((Gtk.ListItem) item).position == app.current_item;
entry.update (song, app.sort_mode);
- var saved_pos = ((Gtk.ListItem)item).position;
- if (saved_pos != ((Gtk.ListItem)item).position) {
+ var saved_pos = ((Gtk.ListItem) item).position;
+ if (saved_pos != ((Gtk.ListItem) item).position) {
Idle.add (() => {
app.song_list.items_changed (saved_pos, 0, 0);
return false;
@@ -257,19 +274,19 @@ namespace Victrola {
update_song_info (song);
var app = (Application) application;
- var pixbufs = new Gdk.Pixbuf?[1] {null};
+ var pixbufs = new Gdk.Pixbuf ? [1] { null };
if (song == app.current_song) {
Gdk.Paintable? paintable = null;
if (image != null) {
- pixbufs[0] = load_clamp_pixbuf_from_sample ((!)image, 300);
+ pixbufs[0] = load_clamp_pixbuf_from_sample ((!) image, 300);
}
if (pixbufs[0] != null) {
- paintable = Gdk.Texture.for_pixbuf ((!)pixbufs[0]);
+ paintable = Gdk.Texture.for_pixbuf ((!) pixbufs[0]);
}
- accent_set.begin ((!)pixbufs[0]);
+ accent_set.begin ((!) pixbufs[0]);
var art = update_cover_paintable (song, info_page.cover_art, paintable);
info_page.cover_art.paintable = art;
@@ -285,11 +302,12 @@ namespace Victrola {
play_bar_mobile.cover_art.opacity = value;
});
fade_animation?.pause ();
+
fade_animation = new He.TimedAnimation (info_page.cover_art, 0.1,
- info_page.cover_art.opacity + 0.1,
- 900,
- target);
- ((!)fade_animation).done.connect (() => {
+ info_page.cover_art.opacity + 0.1,
+ 900,
+ target);
+ ((!) fade_animation).done.connect (() => {
fade_animation = null;
});
fade_animation?.play ();
@@ -298,27 +316,27 @@ namespace Victrola {
private static Gdk.Texture? update_cover_paintable (Song song, Gtk.Widget widget, Gdk.Paintable paintable) {
var snapshot = new Gtk.Snapshot ();
- var rect = (!)Graphene.Rect ().init (0, 0, 256, 256);
- var rounded = (!)Gsk.RoundedRect ().init_from_rect (rect, 12);
+ var rect = (!) Graphene.Rect ().init (0, 0, 256, 256);
+ var rounded = (!) Gsk.RoundedRect ().init_from_rect (rect, 12);
snapshot.push_rounded_clip (rounded);
paintable.snapshot (snapshot, 256, 256);
snapshot.pop ();
var node = snapshot.free_to_node ();
if (node is Gsk.RenderNode) {
- return widget.get_native ()?.get_renderer ()?.render_texture ((!)node, rect);
+ return widget.get_native () ? .get_renderer () ? .render_texture ((!) node, rect);
}
return null;
}
private static Gdk.Texture? update_blur_paintable (Song song, Gtk.Widget widget, Gdk.Paintable paintable) {
var snapshot = new Gtk.Snapshot ();
- var rect = (!)Graphene.Rect ().init (0, 0, 256, 256);
- var rounded = (!)Gsk.RoundedRect ().init_from_rect (rect, 12);
+ var rect = (!) Graphene.Rect ().init (0, 0, 256, 256);
+ var rounded = (!) Gsk.RoundedRect ().init_from_rect (rect, 12);
snapshot.push_rounded_clip (rounded);
paintable.snapshot (snapshot, 256, 256);
snapshot.pop ();
var node = snapshot.free_to_node ();
if (node is Gsk.RenderNode) {
- return widget.get_native ()?.get_renderer ()?.render_texture ((!)node, rect);
+ return widget.get_native () ? .get_renderer () ? .render_texture ((!) node, rect);
}
return null;
}
@@ -327,8 +345,8 @@ namespace Victrola {
var buffer = sample.get_buffer ();
Gst.MapInfo? info = null;
- if (buffer?.map (out info, Gst.MapFlags.READ) ?? false) {
- var bytes = new Bytes.static (info?.data);
+ if (buffer ? .map (out info, Gst.MapFlags.READ) ?? false) {
+ var bytes = new Bytes.static (info ? .data);
var stream = new MemoryInputStream.from_bytes (bytes);
try {
var pixbuf = new Gdk.Pixbuf.from_stream (stream);
@@ -338,7 +356,7 @@ namespace Victrola {
var dx = (int) (width * scale + 0.5); var dy = (int) (height * scale + 0.5);
var newbuf = pixbuf.scale_simple (dx, dy, Gdk.InterpType.TILES);
if (newbuf != null)
- return ((!)newbuf);
+ return ((!) newbuf);
buffer?.unmap ((!) info);
}
} catch (Error e) {
@@ -358,6 +376,7 @@ namespace Victrola {
info_page.update (song);
play_bar_mobile.update (song);
}
+
public async void accent_set (Gdk.Pixbuf? pixbuf) {
var app = (Application) application;
try {
@@ -366,20 +385,19 @@ namespace Victrola {
GLib.Array result = He.Ensor.accent_from_pixels_async.end (res);
int top = result.index (0);
print ("FIRST VICTROLA ARGB RESULT (should be the same as Ensor's): %d\n".printf (top));
-
+
if (top != 0) {
Gdk.RGBA accent_color = { 0 };
- accent_color.parse(He.Color.hexcode_argb (top));
- app.default_accent_color = He.Color.from_gdk_rgba(accent_color);
+ accent_color.parse (He.Color.hexcode_argb (top));
+ app.default_accent_color = He.Color.from_gdk_rgba (accent_color);
} else {
Gdk.RGBA accent_color = { 0 };
- accent_color.parse("#F7812B");
- app.default_accent_color = He.Color.from_gdk_rgba(accent_color);
+ accent_color.parse ("#F7812B");
+ app.default_accent_color = He.Color.from_gdk_rgba (accent_color);
}
loop.quit ();
});
loop.run ();
-
} catch (Error e) {
print (e.message);
}
@@ -391,13 +409,13 @@ namespace Victrola {
app.song_list.filter = new Gtk.CustomFilter ((obj) => {
var song = (Song) obj;
switch (_search_type) {
- case SearchType.ARTIST:
+ case SearchType.ARTIST :
return song.artist == _search_property;
- case SearchType.TITLE:
+ case SearchType.TITLE :
return song.title == _search_property;
- default:
+ default :
return _search_text.match_string (song.artist, false)
- || _search_text.match_string (song.title, false);
+ || _search_text.match_string (song.title, false);
}
});
} else {
@@ -412,7 +430,9 @@ namespace Victrola {
public static async void save_data_to_file (File file, Bytes data) {
try {
var stream = yield file.create_async (FileCreateFlags.NONE);
+
yield stream.write_bytes_async (data);
+
yield stream.close_async ();
} catch (Error e) {
}