From 58951d776ab26767c4e4e3394bc789e7bdf6a71c Mon Sep 17 00:00:00 2001 From: Salastil <46979341+Salastil@users.noreply.github.com> Date: Tue, 25 Nov 2025 02:43:30 -0500 Subject: [PATCH 1/8] Replace Boost.Process youtube-dl call with yt-dlp popen --- src/actions.cpp | 232 +++++++++++++++++++++++++++---------------- src/screens/help.cpp | 8 +- 2 files changed, 148 insertions(+), 92 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index ed5afd414..432fb7f0a 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -21,20 +21,21 @@ #include #include #include +#include #include #include #include #include -#include -#include -#include -#include #include #include #include #include #include #include +#include +#include +#include +#include #include #include "actions.h" @@ -2764,90 +2765,145 @@ bool AddYoutubeDLItem::canBeRun() void AddYoutubeDLItem::run() { - using Global::wFooter; - namespace bp = boost::process; - namespace pt = boost::property_tree; - - std::string url; - { - Statusbar::ScopedLock slock; - Statusbar::put() << "Add via youtube-dl: "; - url = wFooter->prompt(); - } - - // do nothing if no url is given - if (url.empty()) - return; - - // search the youtube-dl executable in the PATH - auto ydl_path = bp::search_path("youtube-dl"); - if (ydl_path.empty()) { - Statusbar::print("youtube-dl was not found in PATH"); - return; - } - - Statusbar::printf("Calling youtube-dl with '%1%' ...", url); - - // start youtube-dl in a child process - // -j: output as JSON, each playlist item on a separate line - // -f bestaudio/best: selects the best available audio-only stream, or - // alternatively the best audio+video stream - bp::ipstream output; - bp::child child_process(ydl_path, url, "-j", "-f", "bestaudio/best", "--playlist-end", "100", bp::std_out > output, - bp::std_err > bp::null); - - // extract the URL and metadata from a ptree object and add - auto add_song = [] (const pt::ptree& ptree) { - auto download_url = ptree.get("url"); - auto title = ptree.get_optional("title"); - auto artist = ptree.get_optional("creator"); - if (!artist.has_value()) { - artist = ptree.get_optional("uploader"); - } - auto album = ptree.get_optional("album"); - auto id = Mpd.AddSong(download_url); - if (id == -1) { - return 0; - } - if (title.has_value()) { - Mpd.AddTag(id, MPD_TAG_TITLE, *title); - } - if (artist.has_value()) { - Mpd.AddTag(id, MPD_TAG_ARTIST, *artist); - } - if (album.has_value()) { - Mpd.AddTag(id, MPD_TAG_ALBUM, *album); - } - return 1; - }; - - std::string line; - pt::ptree ptree; - unsigned num_songs_added = 0; - - while (std::getline(output, line)) { - try { - std::istringstream line_stream(line); - pt::read_json(line_stream, ptree); - num_songs_added += add_song(ptree); - } catch (pt::ptree_error &e) { - Statusbar::print("An error occurred while parsing the output of youtube-dl"); - continue; - } - Statusbar::printf("Added %1% item(s) to playlist", num_songs_added); - } - - if (child_process.running()) { - child_process.terminate(); - } - child_process.wait(); - - auto ec = child_process.exit_code(); - if (ec == 0) { - Statusbar::printf("Added %1% item(s) to playlist", num_songs_added); - } else { - Statusbar::printf("Added %1% item(s) to playlist (youtube-dl exited with exit code %2%)", num_songs_added, ec); - } + using Global::wFooter; + namespace pt = boost::property_tree; + + auto find_executable = [] (const std::vector &names) -> std::string { + const char *path_env = std::getenv("PATH"); + if (!path_env) + return {}; + + std::string path(path_env); + std::stringstream ss(path); + std::string dir; + + std::vector dirs; + while (std::getline(ss, dir, ':')) + { + if (dir.empty()) + dir = "."; + dirs.push_back(dir); + } + + for (const auto &name : names) + { + for (const auto &d : dirs) + { + std::string candidate = d + '/' + name; + if (access(candidate.c_str(), X_OK) == 0) + return candidate; + } + } + + return {}; + }; + + std::string url; + { + Statusbar::ScopedLock slock; + Statusbar::put() << "Add via yt-dlp: "; + url = wFooter->prompt(); + } + + // do nothing if no url is given + if (url.empty()) + return; + + // search the yt-dlp or youtube-dl executable in the PATH + auto ydl_path = find_executable({"yt-dlp", "youtube-dl"}); + if (ydl_path.empty()) { + Statusbar::print("yt-dlp / youtube-dl was not found in PATH"); + return; + } + + Statusbar::printf("Calling %1% with '%2%' ...", ydl_path, url); + + // start yt-dlp in a child process + // -j: output as JSON, each playlist item on a separate line + // --flat-playlist: quickly list items without resolving URLs up front + // -f bestaudio/best: selects the best available audio-only stream, or + // alternatively the best audio+video stream + std::string escaped_url = url; + escapeSingleQuotes(escaped_url); + std::string command = ydl_path + " -j --flat-playlist -f bestaudio/best --playlist-end 100 '" + escaped_url + "' 2>/dev/null"; + + FILE *pipe = popen(command.c_str(), "r"); + if (!pipe) { + Statusbar::print("Failed to start yt-dlp"); + return; + } + + // extract the URL and metadata from a ptree object and add + auto add_song = [] (const pt::ptree& ptree) { + auto download_url = ptree.get_optional("url"); + if (!download_url.has_value()) { + download_url = ptree.get_optional("webpage_url"); + } + if (!download_url.has_value()) { + return 0; + } + + auto title = ptree.get_optional("title"); + auto artist = ptree.get_optional("creator"); + if (!artist.has_value()) { + artist = ptree.get_optional("uploader"); + } + auto album = ptree.get_optional("album"); + auto id = Mpd.AddSong(*download_url); + if (id == -1) { + return 0; + } + if (title.has_value()) { + Mpd.AddTag(id, MPD_TAG_TITLE, *title); + } + if (artist.has_value()) { + Mpd.AddTag(id, MPD_TAG_ARTIST, *artist); + } + if (album.has_value()) { + Mpd.AddTag(id, MPD_TAG_ALBUM, *album); + } + return 1; + }; + + std::string line; + pt::ptree ptree; + unsigned num_songs_added = 0; + + auto process_line = [&] (const std::string &json_line) { + try { + std::istringstream line_stream(json_line); + pt::read_json(line_stream, ptree); + num_songs_added += add_song(ptree); + Statusbar::printf("Added %1% item(s) to playlist", num_songs_added); + } catch (pt::ptree_error &) { + Statusbar::print("An error occurred while parsing the output of yt-dlp"); + } + }; + + char buffer[4096]; + while (fgets(buffer, sizeof(buffer), pipe)) + { + line.append(buffer); + if (!line.empty() && line.back() == '\n') + { + line.pop_back(); + process_line(line); + line.clear(); + } + } + if (!line.empty()) + process_line(line); + + int raw_status = pclose(pipe); + int ec = raw_status; + if (raw_status >= 0 && WIFEXITED(raw_status)) + ec = WEXITSTATUS(raw_status); + + if (ec == 0) { + Statusbar::printf("Added %1% item(s) to playlist", num_songs_added); + } else { + Statusbar::printf("Added %1% item(s) to playlist (yt-dlp exited with exit code %2%)", num_songs_added, ec); + } } } diff --git a/src/screens/help.cpp b/src/screens/help.cpp index c7de48e2a..d8d931e06 100644 --- a/src/screens/help.cpp +++ b/src/screens/help.cpp @@ -255,10 +255,10 @@ void write_bindings(NC::Scrollpad &w) key(w, Type::SavePlaylist, "Save playlist"); key(w, Type::Shuffle, "Shuffle range"); key(w, Type::SortPlaylist, "Sort range"); - key(w, Type::ReversePlaylist, "Reverse range"); - key(w, Type::JumpToPlayingSong, "Jump to current song"); - key(w, Type::TogglePlayingSongCentering, "Toggle playing song centering"); - key(w, Type::AddYoutubeDLItem, "Add items via youtube-dl"); + key(w, Type::ReversePlaylist, "Reverse range"); + key(w, Type::JumpToPlayingSong, "Jump to current song"); + key(w, Type::TogglePlayingSongCentering, "Toggle playing song centering"); + key(w, Type::AddYoutubeDLItem, "Add items via yt-dlp / youtube-dl"); key_section(w, "Browser"); key(w, Type::EnterDirectory, "Enter directory"); From 3af0ece4a32a542fe543a41fad06eae196819349 Mon Sep 17 00:00:00 2001 From: Salastil <46979341+Salastil@users.noreply.github.com> Date: Tue, 25 Nov 2025 02:53:19 -0500 Subject: [PATCH 2/8] Resolve playable URLs when importing via yt-dlp --- src/actions.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 432fb7f0a..d8cd96964 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -2833,15 +2834,54 @@ void AddYoutubeDLItem::run() return; } + auto resolve_stream_url = [&] (const std::string &source_url) -> boost::optional { + std::string escaped_source = source_url; + escapeSingleQuotes(escaped_source); + + std::string resolve_command = ydl_path + " -j -f bestaudio/best --no-playlist '" + escaped_source + "' 2>/dev/null"; + FILE *resolve_pipe = popen(resolve_command.c_str(), "r"); + if (!resolve_pipe) { + return boost::none; + } + + std::string resolved_json; + char resolve_buffer[4096]; + while (fgets(resolve_buffer, sizeof(resolve_buffer), resolve_pipe)) + resolved_json.append(resolve_buffer); + + int resolve_status = pclose(resolve_pipe); + if (resolve_status >= 0 && WIFEXITED(resolve_status) && WEXITSTATUS(resolve_status) != 0) + return boost::none; + + pt::ptree resolved_ptree; + try { + std::istringstream resolved_stream(resolved_json); + pt::read_json(resolved_stream, resolved_ptree); + } catch (pt::ptree_error &) { + return boost::none; + } + + auto resolved_url = resolved_ptree.get_optional("url"); + if (!resolved_url.has_value()) + resolved_url = resolved_ptree.get_optional("webpage_url"); + + if (!resolved_url.has_value()) + return boost::none; + + return resolved_url; + }; + // extract the URL and metadata from a ptree object and add - auto add_song = [] (const pt::ptree& ptree) { + auto add_song = [&] (const pt::ptree& ptree) { auto download_url = ptree.get_optional("url"); - if (!download_url.has_value()) { + if (!download_url.has_value()) download_url = ptree.get_optional("webpage_url"); - } - if (!download_url.has_value()) { + + if (download_url.has_value() && download_url->find("://") == std::string::npos) + download_url = resolve_stream_url(*download_url); + + if (!download_url.has_value() || download_url->find("://") == std::string::npos) return 0; - } auto title = ptree.get_optional("title"); auto artist = ptree.get_optional("creator"); From 517345312a80a1480e7155478613a92ad494c498 Mon Sep 17 00:00:00 2001 From: Salastil <46979341+Salastil@users.noreply.github.com> Date: Tue, 25 Nov 2025 03:04:59 -0500 Subject: [PATCH 3/8] Resolve yt-dlp playlist entries before adding to MPD --- src/actions.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index d8cd96964..0c45e2e37 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -2874,11 +2874,20 @@ void AddYoutubeDLItem::run() // extract the URL and metadata from a ptree object and add auto add_song = [&] (const pt::ptree& ptree) { auto download_url = ptree.get_optional("url"); - if (!download_url.has_value()) - download_url = ptree.get_optional("webpage_url"); + auto webpage_url = ptree.get_optional("webpage_url"); + + const bool has_protocol = ptree.get_optional("protocol").has_value(); + + if (!has_protocol) { + if (!download_url.has_value() && webpage_url.has_value()) + download_url = webpage_url; - if (download_url.has_value() && download_url->find("://") == std::string::npos) - download_url = resolve_stream_url(*download_url); + if (download_url.has_value()) + download_url = resolve_stream_url(*download_url); + } + + if (!download_url.has_value()) + download_url = webpage_url; if (!download_url.has_value() || download_url->find("://") == std::string::npos) return 0; From 49059972dead3a84b5992739208e64a0f4756490 Mon Sep 17 00:00:00 2001 From: Salastil <46979341+Salastil@users.noreply.github.com> Date: Tue, 25 Nov 2025 03:12:04 -0500 Subject: [PATCH 4/8] Restore original playlist handling with popen --- src/actions.cpp | 65 +++---------------------------------------------- 1 file changed, 4 insertions(+), 61 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 0c45e2e37..4ec89cb4e 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -2821,12 +2820,11 @@ void AddYoutubeDLItem::run() // start yt-dlp in a child process // -j: output as JSON, each playlist item on a separate line - // --flat-playlist: quickly list items without resolving URLs up front // -f bestaudio/best: selects the best available audio-only stream, or // alternatively the best audio+video stream std::string escaped_url = url; escapeSingleQuotes(escaped_url); - std::string command = ydl_path + " -j --flat-playlist -f bestaudio/best --playlist-end 100 '" + escaped_url + "' 2>/dev/null"; + std::string command = ydl_path + " -j -f bestaudio/best --playlist-end 100 '" + escaped_url + "' 2>/dev/null"; FILE *pipe = popen(command.c_str(), "r"); if (!pipe) { @@ -2834,71 +2832,16 @@ void AddYoutubeDLItem::run() return; } - auto resolve_stream_url = [&] (const std::string &source_url) -> boost::optional { - std::string escaped_source = source_url; - escapeSingleQuotes(escaped_source); - - std::string resolve_command = ydl_path + " -j -f bestaudio/best --no-playlist '" + escaped_source + "' 2>/dev/null"; - FILE *resolve_pipe = popen(resolve_command.c_str(), "r"); - if (!resolve_pipe) { - return boost::none; - } - - std::string resolved_json; - char resolve_buffer[4096]; - while (fgets(resolve_buffer, sizeof(resolve_buffer), resolve_pipe)) - resolved_json.append(resolve_buffer); - - int resolve_status = pclose(resolve_pipe); - if (resolve_status >= 0 && WIFEXITED(resolve_status) && WEXITSTATUS(resolve_status) != 0) - return boost::none; - - pt::ptree resolved_ptree; - try { - std::istringstream resolved_stream(resolved_json); - pt::read_json(resolved_stream, resolved_ptree); - } catch (pt::ptree_error &) { - return boost::none; - } - - auto resolved_url = resolved_ptree.get_optional("url"); - if (!resolved_url.has_value()) - resolved_url = resolved_ptree.get_optional("webpage_url"); - - if (!resolved_url.has_value()) - return boost::none; - - return resolved_url; - }; - // extract the URL and metadata from a ptree object and add - auto add_song = [&] (const pt::ptree& ptree) { - auto download_url = ptree.get_optional("url"); - auto webpage_url = ptree.get_optional("webpage_url"); - - const bool has_protocol = ptree.get_optional("protocol").has_value(); - - if (!has_protocol) { - if (!download_url.has_value() && webpage_url.has_value()) - download_url = webpage_url; - - if (download_url.has_value()) - download_url = resolve_stream_url(*download_url); - } - - if (!download_url.has_value()) - download_url = webpage_url; - - if (!download_url.has_value() || download_url->find("://") == std::string::npos) - return 0; - + auto add_song = [] (const pt::ptree& ptree) { + auto download_url = ptree.get("url"); auto title = ptree.get_optional("title"); auto artist = ptree.get_optional("creator"); if (!artist.has_value()) { artist = ptree.get_optional("uploader"); } auto album = ptree.get_optional("album"); - auto id = Mpd.AddSong(*download_url); + auto id = Mpd.AddSong(download_url); if (id == -1) { return 0; } From f37c3d94b45fb36aad9bfd0da99964b24ec577b4 Mon Sep 17 00:00:00 2001 From: Salastil <46979341+Salastil@users.noreply.github.com> Date: Tue, 25 Nov 2025 03:22:29 -0500 Subject: [PATCH 5/8] Restore youtube-dl playlist handling with popen --- src/actions.cpp | 36 +++++++++++++++++------------------- src/screens/help.cpp | 2 +- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 4ec89cb4e..40bfe81a3 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -2768,7 +2768,7 @@ void AddYoutubeDLItem::run() using Global::wFooter; namespace pt = boost::property_tree; - auto find_executable = [] (const std::vector &names) -> std::string { + auto find_executable = [] (const std::string &name) -> std::string { const char *path_env = std::getenv("PATH"); if (!path_env) return {}; @@ -2785,14 +2785,11 @@ void AddYoutubeDLItem::run() dirs.push_back(dir); } - for (const auto &name : names) + for (const auto &d : dirs) { - for (const auto &d : dirs) - { - std::string candidate = d + '/' + name; - if (access(candidate.c_str(), X_OK) == 0) - return candidate; - } + std::string candidate = d + '/' + name; + if (access(candidate.c_str(), X_OK) == 0) + return candidate; } return {}; @@ -2801,7 +2798,7 @@ void AddYoutubeDLItem::run() std::string url; { Statusbar::ScopedLock slock; - Statusbar::put() << "Add via yt-dlp: "; + Statusbar::put() << "Add via youtube-dl: "; url = wFooter->prompt(); } @@ -2809,26 +2806,26 @@ void AddYoutubeDLItem::run() if (url.empty()) return; - // search the yt-dlp or youtube-dl executable in the PATH - auto ydl_path = find_executable({"yt-dlp", "youtube-dl"}); + // search the youtube-dl executable in the PATH + auto ydl_path = find_executable("youtube-dl"); if (ydl_path.empty()) { - Statusbar::print("yt-dlp / youtube-dl was not found in PATH"); + Statusbar::print("youtube-dl was not found in PATH"); return; } - Statusbar::printf("Calling %1% with '%2%' ...", ydl_path, url); + Statusbar::printf("Calling youtube-dl with '%1%' ...", url); - // start yt-dlp in a child process + // start youtube-dl in a child process // -j: output as JSON, each playlist item on a separate line // -f bestaudio/best: selects the best available audio-only stream, or // alternatively the best audio+video stream std::string escaped_url = url; escapeSingleQuotes(escaped_url); - std::string command = ydl_path + " -j -f bestaudio/best --playlist-end 100 '" + escaped_url + "' 2>/dev/null"; + std::string command = ydl_path + " '" + escaped_url + "' -j -f bestaudio/best --playlist-end 100 2>/dev/null"; FILE *pipe = popen(command.c_str(), "r"); if (!pipe) { - Statusbar::print("Failed to start yt-dlp"); + Statusbar::print("Failed to start youtube-dl"); return; } @@ -2866,10 +2863,11 @@ void AddYoutubeDLItem::run() std::istringstream line_stream(json_line); pt::read_json(line_stream, ptree); num_songs_added += add_song(ptree); - Statusbar::printf("Added %1% item(s) to playlist", num_songs_added); } catch (pt::ptree_error &) { - Statusbar::print("An error occurred while parsing the output of yt-dlp"); + Statusbar::print("An error occurred while parsing the output of youtube-dl"); + return; } + Statusbar::printf("Added %1% item(s) to playlist", num_songs_added); }; char buffer[4096]; @@ -2894,7 +2892,7 @@ void AddYoutubeDLItem::run() if (ec == 0) { Statusbar::printf("Added %1% item(s) to playlist", num_songs_added); } else { - Statusbar::printf("Added %1% item(s) to playlist (yt-dlp exited with exit code %2%)", num_songs_added, ec); + Statusbar::printf("Added %1% item(s) to playlist (youtube-dl exited with exit code %2%)", num_songs_added, ec); } } diff --git a/src/screens/help.cpp b/src/screens/help.cpp index d8d931e06..328672838 100644 --- a/src/screens/help.cpp +++ b/src/screens/help.cpp @@ -258,7 +258,7 @@ void write_bindings(NC::Scrollpad &w) key(w, Type::ReversePlaylist, "Reverse range"); key(w, Type::JumpToPlayingSong, "Jump to current song"); key(w, Type::TogglePlayingSongCentering, "Toggle playing song centering"); - key(w, Type::AddYoutubeDLItem, "Add items via yt-dlp / youtube-dl"); + key(w, Type::AddYoutubeDLItem, "Add items via youtube-dl"); key_section(w, "Browser"); key(w, Type::EnterDirectory, "Enter directory"); From 3077441c78804e0a2c6061a696547bfb0f811c31 Mon Sep 17 00:00:00 2001 From: Salastil <46979341+Salastil@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:50:45 -0500 Subject: [PATCH 6/8] Prefer requested_formats URLs for youtube-dl entries --- src/actions.cpp | 54 +++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 40bfe81a3..070a453dc 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -2829,30 +2829,36 @@ void AddYoutubeDLItem::run() return; } - // extract the URL and metadata from a ptree object and add - auto add_song = [] (const pt::ptree& ptree) { - auto download_url = ptree.get("url"); - auto title = ptree.get_optional("title"); - auto artist = ptree.get_optional("creator"); - if (!artist.has_value()) { - artist = ptree.get_optional("uploader"); - } - auto album = ptree.get_optional("album"); - auto id = Mpd.AddSong(download_url); - if (id == -1) { - return 0; - } - if (title.has_value()) { - Mpd.AddTag(id, MPD_TAG_TITLE, *title); - } - if (artist.has_value()) { - Mpd.AddTag(id, MPD_TAG_ARTIST, *artist); - } - if (album.has_value()) { - Mpd.AddTag(id, MPD_TAG_ALBUM, *album); - } - return 1; - }; + // extract the URL and metadata from a ptree object and add + auto add_song = [] (const pt::ptree& ptree) { + auto download_url = ptree.get_optional("requested_formats.0.url"); + if (!download_url) { + download_url = ptree.get_optional("url"); + } + if (!download_url) { + return 0; + } + auto title = ptree.get_optional("title"); + auto artist = ptree.get_optional("creator"); + if (!artist.has_value()) { + artist = ptree.get_optional("uploader"); + } + auto album = ptree.get_optional("album"); + auto id = Mpd.AddSong(*download_url); + if (id == -1) { + return 0; + } + if (title.has_value()) { + Mpd.AddTag(id, MPD_TAG_TITLE, *title); + } + if (artist.has_value()) { + Mpd.AddTag(id, MPD_TAG_ARTIST, *artist); + } + if (album.has_value()) { + Mpd.AddTag(id, MPD_TAG_ALBUM, *album); + } + return 1; + }; std::string line; pt::ptree ptree; From 02a78ef41a01ffce71ba31ec10efb61dce59e8e5 Mon Sep 17 00:00:00 2001 From: Salastil <46979341+Salastil@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:18:40 -0500 Subject: [PATCH 7/8] Use yt-dlp non-flat playlist output --- src/actions.cpp | 16 ++++++++++------ src/screens/help.cpp | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 070a453dc..28d1d68da 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -2806,22 +2806,26 @@ void AddYoutubeDLItem::run() if (url.empty()) return; - // search the youtube-dl executable in the PATH - auto ydl_path = find_executable("youtube-dl"); + // search the yt-dlp/youtube-dl executable in the PATH (prefer yt-dlp) + auto ydl_path = find_executable("yt-dlp"); + if (ydl_path.empty()) + ydl_path = find_executable("youtube-dl"); + if (ydl_path.empty()) { - Statusbar::print("youtube-dl was not found in PATH"); + Statusbar::print("yt-dlp (or youtube-dl) was not found in PATH"); return; } - Statusbar::printf("Calling youtube-dl with '%1%' ...", url); + Statusbar::printf("Calling yt-dlp with '%1%' ...", url); - // start youtube-dl in a child process + // start yt-dlp in a child process // -j: output as JSON, each playlist item on a separate line + // --no-flat-playlist: ensure streaming URLs are included per entry // -f bestaudio/best: selects the best available audio-only stream, or // alternatively the best audio+video stream std::string escaped_url = url; escapeSingleQuotes(escaped_url); - std::string command = ydl_path + " '" + escaped_url + "' -j -f bestaudio/best --playlist-end 100 2>/dev/null"; + std::string command = ydl_path + " '" + escaped_url + "' -j --no-flat-playlist -f bestaudio/best 2>/dev/null"; FILE *pipe = popen(command.c_str(), "r"); if (!pipe) { diff --git a/src/screens/help.cpp b/src/screens/help.cpp index 328672838..981f95326 100644 --- a/src/screens/help.cpp +++ b/src/screens/help.cpp @@ -258,7 +258,7 @@ void write_bindings(NC::Scrollpad &w) key(w, Type::ReversePlaylist, "Reverse range"); key(w, Type::JumpToPlayingSong, "Jump to current song"); key(w, Type::TogglePlayingSongCentering, "Toggle playing song centering"); - key(w, Type::AddYoutubeDLItem, "Add items via youtube-dl"); + key(w, Type::AddYoutubeDLItem, "Add items via yt-dlp/youtube-dl"); key_section(w, "Browser"); key(w, Type::EnterDirectory, "Enter directory"); From d71c5dd71f1120da445210633b38d8454dcd489b Mon Sep 17 00:00:00 2001 From: Salastil <46979341+Salastil@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:26:29 -0500 Subject: [PATCH 8/8] Handle audio URLs from yt-dlp formats --- src/actions.cpp | 82 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 28d1d68da..c15df609c 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -2834,35 +2834,59 @@ void AddYoutubeDLItem::run() } // extract the URL and metadata from a ptree object and add - auto add_song = [] (const pt::ptree& ptree) { - auto download_url = ptree.get_optional("requested_formats.0.url"); - if (!download_url) { - download_url = ptree.get_optional("url"); - } - if (!download_url) { - return 0; - } - auto title = ptree.get_optional("title"); - auto artist = ptree.get_optional("creator"); - if (!artist.has_value()) { - artist = ptree.get_optional("uploader"); - } - auto album = ptree.get_optional("album"); - auto id = Mpd.AddSong(*download_url); - if (id == -1) { - return 0; - } - if (title.has_value()) { - Mpd.AddTag(id, MPD_TAG_TITLE, *title); - } - if (artist.has_value()) { - Mpd.AddTag(id, MPD_TAG_ARTIST, *artist); - } - if (album.has_value()) { - Mpd.AddTag(id, MPD_TAG_ALBUM, *album); - } - return 1; - }; + auto add_song = [] (const pt::ptree& ptree) { + std::string final_url; + + if (auto requested_formats = ptree.get_child_optional("requested_formats")) { + for (auto &format : *requested_formats) { + if (auto url = format.second.get_optional("url")) { + final_url = *url; + break; + } + } + } + + if (final_url.empty()) { + if (auto formats = ptree.get_child_optional("formats")) { + for (auto &format : *formats) { + auto acodec = format.second.get("acodec", ""); + auto vcodec = format.second.get("vcodec", ""); + + if (vcodec == "none" && !acodec.empty()) { + if (auto url = format.second.get_optional("url")) { + final_url = *url; + break; + } + } + } + } + } + + if (final_url.empty()) { + return 0; + } + + auto title = ptree.get_optional("title"); + auto artist = ptree.get_optional("creator"); + if (!artist.has_value()) { + artist = ptree.get_optional("uploader"); + } + auto album = ptree.get_optional("album"); + auto id = Mpd.AddSong(final_url); + if (id == -1) { + return 0; + } + if (title.has_value()) { + Mpd.AddTag(id, MPD_TAG_TITLE, *title); + } + if (artist.has_value()) { + Mpd.AddTag(id, MPD_TAG_ARTIST, *artist); + } + if (album.has_value()) { + Mpd.AddTag(id, MPD_TAG_ALBUM, *album); + } + return 1; + }; std::string line; pt::ptree ptree;