From 523b7d4135ec284e9467edff84e74c11e95116a5 Mon Sep 17 00:00:00 2001 From: Lennart Braun Date: Fri, 27 Mar 2020 00:04:14 +0100 Subject: [PATCH 1/3] Add action to add streams via youtube-dl youtube-dl [0] is a "command-line program to download videos from YouTube.com" and many other websites, e.g. media libraries of public broadcasters. This commit adds a new action to ncmpcpp's playlist view: Pressing 'D', prompts for the URL of a website. The input is passed to youtube-dl to retrieve the URL of the audio stream and available metadata. If successful, the URL is added to the MPD playlist. [0]: https://github.com/ytdl-org/youtube-dl --- src/actions.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++ src/actions.h | 10 +++++ src/bindings.cpp | 2 + src/mpdpp.cpp | 4 ++ src/mpdpp.h | 1 + src/screens/help.cpp | 1 + 6 files changed, 116 insertions(+) diff --git a/src/actions.cpp b/src/actions.cpp index 70fb97ff..d182366f 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -25,8 +25,17 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include "actions.h" #include "charset.h" @@ -2746,6 +2755,94 @@ void ShowServerInfo::run() myServerInfo->switchTo(); } +bool AddYoutubeDLItem::canBeRun() +{ + return myScreen == myPlaylist; +} + +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::ScopedLock slock; + Statusbar::put() << "youtube-dl was not found in PATH"; + return; + } + + { + Statusbar::ScopedLock slock; + Statusbar::put() << "Calling youtube-dl with '" << url << "' ..."; + wFooter->refresh(); + } + + // 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", 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; + } + 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); + } + }; + + std::string line; + pt::ptree ptree; + + while (std::getline(output, line)) { + try { + std::istringstream line_stream(line); + pt::read_json(line_stream, ptree); + add_song(ptree); + } catch (pt::ptree_error &e) { + Statusbar::ScopedLock slock; + Statusbar::put() << "An error occurred while calling youtube-dl or parsing its output"; + wFooter->refresh(); + } + } + + if (child_process.running()) { + child_process.terminate(); + } +} + } namespace { @@ -2884,6 +2981,7 @@ void populateActions() insert_action(new Actions::ShowVisualizer()); insert_action(new Actions::ShowClock()); insert_action(new Actions::ShowServerInfo()); + insert_action(new Actions::AddYoutubeDLItem()); for (size_t i = 0; i < AvailableActions.size(); ++i) { if (AvailableActions[i] == nullptr) diff --git a/src/actions.h b/src/actions.h index 8fba1890..efcecc42 100644 --- a/src/actions.h +++ b/src/actions.h @@ -164,6 +164,7 @@ enum class Type ShowVisualizer, ShowClock, ShowServerInfo, + AddYoutubeDLItem, _numberOfActions // needed to dynamically calculate size of action array }; @@ -1425,6 +1426,15 @@ private: virtual void run() override; }; +struct AddYoutubeDLItem: BaseAction +{ + AddYoutubeDLItem(): BaseAction(Type::AddYoutubeDLItem, "add_youtube-dl_item") { } + +private: + virtual bool canBeRun() override; + virtual void run() override; +}; + } #endif // NCMPCPP_ACTIONS_H diff --git a/src/bindings.cpp b/src/bindings.cpp index d4f28690..98a8624d 100644 --- a/src/bindings.cpp +++ b/src/bindings.cpp @@ -762,6 +762,8 @@ void BindingsConfiguration::generateDefaults() bind(k, Actions::Type::SetSelectedItemsPriority); if (notBound(k = stringToKey("q"))) bind(k, Actions::Type::Quit); + if (notBound(k = stringToKey("D"))) + bind(k, Actions::Type::AddYoutubeDLItem); } const Command *BindingsConfiguration::findCommand(const std::string &name) diff --git a/src/mpdpp.cpp b/src/mpdpp.cpp index ccdee31d..74fc890c 100644 --- a/src/mpdpp.cpp +++ b/src/mpdpp.cpp @@ -567,6 +567,10 @@ int Connection::AddSong(const Song &s, int pos) return AddSong((!s.isFromDatabase() ? "file://" : "") + s.getURI(), pos); } +void Connection::AddTag(int id, mpd_tag_type tag, const std::string &value) { + mpd_run_add_tag_id(m_connection.get(), id, tag, value.c_str()); +} + bool Connection::Add(const std::string &path) { bool result; diff --git a/src/mpdpp.h b/src/mpdpp.h index ce23c835..d7e7e38c 100644 --- a/src/mpdpp.h +++ b/src/mpdpp.h @@ -552,6 +552,7 @@ struct Connection int AddSong(const std::string &, int = -1); // returns id of added song int AddSong(const Song &, int = -1); // returns id of added song + void AddTag(int id, mpd_tag_type, const std::string &); bool AddRandomTag(mpd_tag_type, size_t, std::mt19937 &rng); bool AddRandomSongs(size_t number, const std::string &random_exclude_pattern, std::mt19937 &rng); bool Add(const std::string &path); diff --git a/src/screens/help.cpp b/src/screens/help.cpp index 574ac5fb..c7de48e2 100644 --- a/src/screens/help.cpp +++ b/src/screens/help.cpp @@ -258,6 +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_section(w, "Browser"); key(w, Type::EnterDirectory, "Enter directory"); From 0847e59a4c502012b9c366bf235a7f9b7e576a28 Mon Sep 17 00:00:00 2001 From: Lennart Braun Date: Sat, 26 Dec 2020 19:26:36 +0100 Subject: [PATCH 2/3] Update status messages of youtube-dl action - use `Statusbar::print{,f}` instead of `Statusbar::put` for non-blocking status messages - display number of items added - display of non-zero exit codes of youtube-dl --- src/actions.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index d182366f..3215a08b 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -2780,16 +2780,11 @@ void AddYoutubeDLItem::run() // search the youtube-dl executable in the PATH auto ydl_path = bp::search_path("youtube-dl"); if (ydl_path.empty()) { - Statusbar::ScopedLock slock; - Statusbar::put() << "youtube-dl was not found in PATH"; + Statusbar::print("youtube-dl was not found in PATH"); return; } - { - Statusbar::ScopedLock slock; - Statusbar::put() << "Calling youtube-dl with '" << url << "' ..."; - wFooter->refresh(); - } + 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 @@ -2810,7 +2805,7 @@ void AddYoutubeDLItem::run() auto album = ptree.get_optional("album"); auto id = Mpd.AddSong(download_url); if (id == -1) { - return; + return 0; } if (title.has_value()) { Mpd.AddTag(id, MPD_TAG_TITLE, *title); @@ -2821,26 +2816,36 @@ void AddYoutubeDLItem::run() 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); - add_song(ptree); + num_songs_added += add_song(ptree); } catch (pt::ptree_error &e) { - Statusbar::ScopedLock slock; - Statusbar::put() << "An error occurred while calling youtube-dl or parsing its output"; - wFooter->refresh(); + 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); + } } } From 9e6481ddd940c74c8371940515eefabc89109211 Mon Sep 17 00:00:00 2001 From: Salastil Date: Sun, 27 Aug 2023 03:24:01 -0400 Subject: [PATCH 3/3] Playlist size trim --- src/actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions.cpp b/src/actions.cpp index 3215a08b..bdfa2c6d 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -2791,7 +2791,7 @@ void AddYoutubeDLItem::run() // -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", bp::std_out > 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