youtube search working, still need improvementsd

Co-authored-by: Jason Wen <the-jasoney@users.noreply.github.com>
This commit is contained in:
liyaowhen
2024-08-03 22:35:55 -07:00
parent 0b9c61144b
commit 8db7622148
3641 changed files with 312032 additions and 61 deletions
+37 -5
View File
@@ -6,9 +6,9 @@ namespace Song {
public MainViewContent main_content;
public PlaylistObject current_playlist;
private Gtk.Revealer add_button_revealer = new Gtk.Revealer();
private Gtk.Revealer controls_revealer = new Gtk.Revealer();
public signal void enter_yt_search_mode();
public signal void exit_yt_search_mode();
public MainView(Window _window) {
this.window = _window;
@@ -80,10 +80,13 @@ namespace Song {
var controls = new SongControls();
controls_revealer.set_child(controls);
controls_revealer.set_reveal_child(true);
controls_revealer.transition_type = Gtk.RevealerTransitionType.SLIDE_UP;
toolbar_view.add_top_bar (action_bar);
toolbar_view.set_content(scrollable);
toolbar_view.add_bottom_bar (controls);
toolbar_view.add_bottom_bar (controls_revealer);
this.child = toolbar_view;
this.title = "main_view";
@@ -93,12 +96,15 @@ namespace Song {
}
private void instanciate_signals() {
enter_yt_search_mode.connect(() => {
var signal_hub = SignalHub.get_instance();
signal_hub.enter_yt_search_mode.connect(() => {
add_button_revealer.set_reveal_child(false);
controls_revealer.set_reveal_child(false);
});
exit_yt_search_mode.connect(() => {
signal_hub.exit_yt_search_mode.connect(() => {
add_button_revealer.set_reveal_child(true);
controls_revealer.set_reveal_child(true);
});
}
@@ -137,6 +143,8 @@ namespace Song {
private Adw.NavigationView navigation_view;
public PlaylistObject current_playlist;
private Adw.NavigationPage? yt_search_page = null;
public MainViewContent() {
vexpand = true;
hexpand = true;
@@ -162,6 +170,18 @@ namespace Song {
}
}
});
var signal_hub = SignalHub.get_instance();
signal_hub.enter_yt_search_mode.connect(() => {
search_ui();
});
signal_hub.exit_yt_search_mode.connect(() => {
if (yt_search_page != null) {
print("removing page");
navigation_view.pop();
}
});
}
public void change_page(PlaylistObject playlist) {
@@ -187,6 +207,7 @@ namespace Song {
empty_page.set_title("Empty Playlist " + playlist.name);
empty_page.set_icon_name("edit-find-symbolic");
empty_page.set_description("Click the button on the top left to add songs in this playlist.");
navigation_view.push(parent_page);
}
@@ -198,9 +219,20 @@ namespace Song {
load_music(playlist_page, playlist);
var parent_page = new Adw.NavigationPage(playlist_page, playlist.name);
navigation_view.push(parent_page);
}
private void search_ui() {
var yt_search_list = new YtSearchView();
var yt_search_list_container = new Gtk.ScrolledWindow();
yt_search_list_container.vexpand = true;
yt_search_list_container.set_child(yt_search_list);
yt_search_page = new Adw.NavigationPage(yt_search_list_container, "Searching...");
navigation_view.push(yt_search_page);
}
private void load_music(Adw.StatusPage page, PlaylistObject playlist) {
Adw.Clamp clamp = new Adw.Clamp();
Gtk.ListBox content = new Gtk.ListBox();
+386 -16
View File
@@ -5,9 +5,14 @@ namespace Song {
NONE,
}
public enum YtSearchViewStates {
SEARCHING,
IDLE,
}
public class YtSearchBar : Gtk.Box {
private Gtk.SearchEntry search_entry = new Gtk.SearchEntry ();
public Gtk.SearchEntry search_entry = new Gtk.SearchEntry ();
private MainViewActionBar container_parent;
private Adw.Clamp search_clamp = new Adw.Clamp();
@@ -17,7 +22,7 @@ namespace Song {
private YtSearchBarStates state = YtSearchBarStates.NONE;
private bool isEnlarged = false;
private bool isMouseInside = false;
public bool isMouseInside = false;
private YtSearchBarStates? requesting_state = null;
private Adw.TimedAnimation? enlarge_animation = null;
@@ -54,15 +59,8 @@ namespace Song {
instanciate_signals();
/*focus_event.enter.connect(() => {
enlarge();
SongController.main_view.enter_yt_search_mode();
});
focus_event.leave.connect(() => {
shrink();
SongController.main_view.exit_yt_search_mode();
});*/
var signal_hub = SignalHub.get_instance();
signal_hub.yt_search_bar = this;
}
@@ -70,18 +68,18 @@ namespace Song {
var signal_hub = SignalHub.get_instance();
signal_hub.mouse_clicked.connect(() => {
if (!isMouseInside && isEnlarged) {
shrink();
SongController.main_view.exit_yt_search_mode();
//shrink();
//signal_hub.exit_yt_search_mode();
} else if (!isEnlarged && isMouseInside) {
enlarge();
SongController.main_view.enter_yt_search_mode();
signal_hub.enter_yt_search_mode();
} else if (!isEnlarged && !isMouseInside && state == YtSearchBarStates.ENLARGING) {
requesting_state = YtSearchBarStates.SHRINKING;
Timeout.add(1, () => {
if (requesting_state != YtSearchBarStates.SHRINKING) return false;
if (isEnlarged) {
shrink();
SongController.main_view.exit_yt_search_mode();
signal_hub.exit_yt_search_mode();
requesting_state = null;
return false;
}
@@ -93,7 +91,7 @@ namespace Song {
if (requesting_state != YtSearchBarStates.ENLARGING) return false;
if (!isEnlarged) {
enlarge();
SongController.main_view.enter_yt_search_mode();
signal_hub.enter_yt_search_mode();
requesting_state = null;
return false;
}
@@ -101,6 +99,10 @@ namespace Song {
});
}
});
search_entry.changed.connect((i) => {
signal_hub.request_search(i.get_text());
});
}
private void enlarge() {
@@ -131,4 +133,372 @@ namespace Song {
}
}
public class YtSearchView : Gtk.Box {
private Gtk.Label label = new Gtk.Label("");
private YtSearchViewStates state = YtSearchViewStates.IDLE;
private Cancellable active_search_query;
private Gee.HashSet<YtSearchItem> results = new Gee.HashSet<YtSearchItem>();
private Gee.HashSet<Gtk.Widget> result_widgets = new Gee.HashSet<Gtk.Widget>();
private Subprocess? search_process = null;
construct {
orientation = Gtk.Orientation.VERTICAL;
append(label);
instanciate_signals();
var yt_search_tmp = File.new_build_filename(Environment.get_tmp_dir(), "yt_search_tmp.json");
if (!yt_search_tmp.query_exists(null)) {
yt_search_tmp.create(FileCreateFlags.NONE, null);
print(yt_search_tmp.get_path());
}
print(yt_search_tmp.get_path());
print(Environment.get_tmp_dir());
var motion_sensor = new Gtk.EventControllerMotion();
get_parent().add_controller(motion_sensor);
var signal_hub = SignalHub.get_instance();
motion_sensor.leave.connect(() => {
signal_hub.yt_search_bar.isMouseInside = false;
});
motion_sensor.enter.connect(() => {
signal_hub.yt_search_bar.isMouseInside = true;
});
}
private void instanciate_signals() {
var signal_hub = SignalHub.get_instance();
signal_hub.request_search.connect((query) => {
search.begin(query);
state = YtSearchViewStates.SEARCHING;
});
label.label = signal_hub.yt_search_bar.search_entry.text;
}
private async void search(string query) {
label.label = query;
string temp_file = Path.build_path("/", Environment.get_tmp_dir(), "yt_search_tmp.json");
string[] command = {
"sh",
"-c",
"./song-search " +
query,
null
};
print("\n");
foreach(string s in command) {
print(s + " ");
}
MainLoop loop = new MainLoop ();
try {
GLib.Pid child_pid;
int std_out;
int std_input;
int std_error;
//TODO: remove ghost processes
/*Process.spawn_async_with_pipes(
Environment.get_tmp_dir(),
command,
null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null,
out child_pid,
out std_input,
out std_out,
out std_error
);*/
/*Process.spawn_async_with_pipes(
Environment.get_user_config_dir() + "/com.liyaowhen.Song",
command,
null,
GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
null,
out child_pid,
out std_input,
out std_out,
out std_error
);*/
if (search_process != null && !search_process.get_if_exited()) {
search_process.force_exit();
}
try {
search_process = new Subprocess(GLib.SubprocessFlags.STDIN_PIPE | GLib.SubprocessFlags.STDOUT_PIPE | GLib.SubprocessFlags.STDERR_PIPE,
Environment.get_user_config_dir() + "/com.liyaowhen.Song/search-utility", query);
} catch (GLib.Error e) {
print(e.message);
}
try {
search_process.communicate_utf8_async.begin(null, null, (obj, res) => {
try {
string? stdout_buff;
string? stderr_buff;
search_process.communicate_utf8_async.end (res, out stdout_buff, out stderr_buff);
if (stdout_buff != null) {
print ("STDOUT: %s\n", stdout_buff);
read_json(stdout_buff);
}
if (stderr_buff != null) {
print ("STDERR: %s\n", stderr_buff);
}
} catch (Error e) {
print ("Error: %s\n", e.message);
}
});
} catch (GLib.Error e) {
print(e.message);
}
//GLib.IOChannel stdout_channel = new IOChannel.unix_new(std_out);
StringBuilder output_buffer = new StringBuilder();
/*stdout_channel.add_watch(IOCondition.IN | IOCondition.HUP, (i, c) => {
if (c == IOCondition.HUP) {
if (cancellable.is_cancelled()) {
//Process.close_pid(child_pid);
loop.quit();
return false;
}
read_json(cancellable, output_buffer.str);
return false;
}
try {
string line;
i.read_line(out line, null, null);
output_buffer.append(line);
} catch (GLib.ConvertError e) {
print("IOChannelError: %s\n", e.message);
}
return true;
});*/
//IOChannel error = new IOChannel.unix_new (std_error);
/*error.add_watch(IOCondition.IN | IOCondition.HUP, (i, c) => {
if (c == IOCondition.HUP) {
return false;
}
try {
string line;
i.read_line(out line, null, null);
} catch (GLib.ConvertError e) {
print("IOChannelError: %s\n", e.message);
}
return true;
}); */
/*ChildWatch.add(child_pid, (pid, status) => {
Process.close_pid (pid);
loop.quit ();
print("process ended");
}); */
loop.run();
} catch (GLib.SpawnError e) {
print(e.message);
}
}
private void read_json(string json) {
results.clear();
try {
/*
Json.Parser parser = new Json.Parser();
parser.load_from_data(json, -1);
Json.Node root = parser.get_root();
Json.Object root_object = root.get_object();
if (root_object.has_member("entries")) {
var entries = root_object.get_array_member("entries");
var entries_members = entries.get_elements();
entries_members.foreach((i) => {
var entry = i.get_object();
YtSearchItem search_item = new YtSearchItem();
if (entry.has_member("requested_downloads")) {
//requested formats has 2 parts, the video and audio parts
var requested_downloads = entry.get_array_member("requested_downloads");
var requested_downloads_iterable = requested_downloads.get_elements();
//has only one array item
requested_downloads_iterable.foreach((e) => {
var requested_downloads_object = e.get_object();
if (requested_downloads_object.has_member("requested_formats")) {
var requested_formats = requested_downloads_object.get_array_member("requested_formats");
var requested_formats_iterable = requested_formats.get_elements();
//requested formats iterable hosts 2 objects, a video stream and audio stream object
requested_formats_iterable.foreach((j) => {
var video_or_audio_object = j.get_object();
if (video_or_audio_object.has_member("audio_ext")) {
var audio_ext = video_or_audio_object.get_string_member("audio_ext");
if (audio_ext != "none") {
// if object is audio
search_item.audio_url = video_or_audio_object.get_string_member("url");
} else {
// if object is video
search_item.url = video_or_audio_object.get_string_member("url");
}
}
});
}
});
}
if (entry.has_member("fulltitle")) {
search_item.title = entry.get_string_member("fulltitle");
}
results.add(search_item);
print("\n " + search_item.title);
});
}
print("idle");
state = YtSearchViewStates.IDLE;
load_contents();
*/
Json.Parser parser = new Json.Parser();
parser.load_from_data(json, -1);
Json.Node root = parser.get_root();
Json.Array root_object = root.get_array();
var root_object_iterable = root_object.get_elements();
results.clear();
root_object_iterable.foreach((i) => {
var item = i.get_object();
YtSearchItem search_item = new YtSearchItem();
if (item.has_member("title")) {
search_item.title = item.get_string_member("title");
}
if (item.has_member("url")) {
search_item.url = item.get_string_member("url");
}
if (item.has_member("image")) {
search_item.thumbnail_url = item.get_string_member("image");
}
results.add(search_item);
});
/*
if (root_object.has_member("entries")) {
var entries = root_object.get_array_member("entries");
var entries_members = entries.get_elements();
entries_members.foreach((i) => {
var entry = i.get_object();
YtSearchItem search_item = new YtSearchItem();
if (entry.has_member("requested_downloads")) {
//requested formats has 2 parts, the video and audio parts
var requested_downloads = entry.get_array_member("requested_downloads");
var requested_downloads_iterable = requested_downloads.get_elements();
//has only one array item
requested_downloads_iterable.foreach((e) => {
var requested_downloads_object = e.get_object();
if (requested_downloads_object.has_member("requested_formats")) {
var requested_formats = requested_downloads_object.get_array_member("requested_formats");
var requested_formats_iterable = requested_formats.get_elements();
//requested formats iterable hosts 2 objects, a video stream and audio stream object
requested_formats_iterable.foreach((j) => {
var video_or_audio_object = j.get_object();
if (video_or_audio_object.has_member("audio_ext")) {
var audio_ext = video_or_audio_object.get_string_member("audio_ext");
if (audio_ext != "none") {
// if object is audio
search_item.audio_url = video_or_audio_object.get_string_member("url");
} else {
// if object is video
search_item.url = video_or_audio_object.get_string_member("url");
}
}
});
}
});
}
if (entry.has_member("fulltitle")) {
search_item.title = entry.get_string_member("fulltitle");
}
results.add(search_item);
print("\n " + search_item.title);
});
} */
print("idle");
state = YtSearchViewStates.IDLE;
load_contents();
} catch (Error e) {
print("Failed to parse JSON: %s\n", e.message);
}
}
private void load_contents() {
foreach (Gtk.Widget i in result_widgets) {
remove(i);
print("destroying widgets");
}
result_widgets.clear();
results.foreach((result) => {
var label = new Gtk.Label(result.title);
var link = new Gtk.LinkButton(result.url);
var thumbnail = new Gtk.Image();
thumbnail.hexpand = true;
thumbnail.hexpand_set = true;
thumbnail.vexpand = true;
thumbnail.vexpand_set = true;
thumbnail.icon_size = Gtk.IconSize.LARGE;
load_url_to_image(thumbnail, result.thumbnail_url);
var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 5);
var options_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
options_box.append(label);
box.append(thumbnail);
box.append(options_box);
append(box);
result_widgets.add(box);
return true;
});
}
private async void load_url_to_image (Gtk.Image? image, string url) {
Soup.Session session = new Soup.Session ();
Soup.Message message = new Soup.Message ("GET", url);
GLib.Bytes bytes = session.send_and_read(message, null);
var stream = new GLib.MemoryInputStream();
stream.add_bytes(bytes);
var pixbuf = new Gdk.Pixbuf.from_stream(stream, null);
if (image != null) {
image.set_from_pixbuf(pixbuf);
}
}
}
public class YtSearchItem : GLib.Object {
public string title = "";
public string url = "";
public string thumbnail_url = "";
}
}