diff --git a/src/actions.cpp b/src/actions.cpp index ed5afd41..c15df609 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,169 @@ 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); + using Global::wFooter; + namespace pt = boost::property_tree; + + auto find_executable = [] (const std::string &name) -> 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 &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 youtube-dl: "; + url = wFooter->prompt(); + } + + // do nothing if no url is given + if (url.empty()) + return; + + // 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("yt-dlp (or youtube-dl) was not found in PATH"); + return; + } + + Statusbar::printf("Calling yt-dlp with '%1%' ...", url); + + // 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 --no-flat-playlist -f bestaudio/best 2>/dev/null"; + + FILE *pipe = popen(command.c_str(), "r"); + if (!pipe) { + Statusbar::print("Failed to start youtube-dl"); + 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; - }; - - 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); - } + 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; + 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); + } catch (pt::ptree_error &) { + 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]; + 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 (youtube-dl exited with exit code %2%)", num_songs_added, ec); + } } } diff --git a/src/screens/help.cpp b/src/screens/help.cpp index c7de48e2..981f9532 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");