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) { }