diff options
author | Roland Reichwein <mail@reichwein.it> | 2022-11-05 13:49:53 +0100 |
---|---|---|
committer | Roland Reichwein <mail@reichwein.it> | 2022-11-05 13:49:53 +0100 |
commit | 4aeab7931182cb1c35bd5c52b58d71b30c32674d (patch) | |
tree | e9635c5b2c0827f16dc2021a6193139ef536793b /whiteboard.cpp |
Initial files, WIP
Diffstat (limited to 'whiteboard.cpp')
-rw-r--r-- | whiteboard.cpp | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/whiteboard.cpp b/whiteboard.cpp new file mode 100644 index 0000000..60cfcc2 --- /dev/null +++ b/whiteboard.cpp @@ -0,0 +1,215 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <dirent.h> +#include <sys/types.h> +#include <fcgiapp.h> + +#include <functional> +#include <filesystem> +#include <regex> +#include <string> +#include <unordered_map> + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/trim.hpp> +#include <boost/property_tree/xml_parser.hpp> + +#include "file.h" + +namespace pt = boost::property_tree; +using namespace std::string_literals; +namespace fs = std::filesystem; + +namespace { + + class TempDir + { + private: + fs::path m_path; + fs::path m_oldpath; + public: + TempDir() + { + char templ[] = "/tmp/downtubeXXXXXX"; + char *temp = mkdtemp(templ); + if (temp == nullptr) { + throw std::runtime_error("Can't create temporary directory."); + } + + m_path = temp; + m_oldpath = fs::current_path(); + fs::current_path(m_path); + } + + ~TempDir() + { + fs::current_path(m_oldpath); + fs::remove_all(m_path); + } + + const fs::path& getPath() const {return m_path;} + }; + + std::regex re{"https?://(www\\.youtube\\.com/watch\\?v=|youtu\\.be/)[[:alnum:]_&=-]+", std::regex::extended}; // www.youtube.com/watch?v=ItjMIxS3-rI or https://youtu.be/ItjMIxS3-rI + +} // anonymous namespace + +int main(void) +{ + int result = FCGX_Init(); + if (result != 0) { // error on init + fprintf(stderr, "Error: FCGX_Init()\n"); + return 1; + } + + result = FCGX_IsCGI(); + if (result) { + fprintf(stderr, "Error: No FCGI environment available.\n"); + return 1; + } + + FCGX_Request request; + result = FCGX_InitRequest(&request, 0, 0); + if (result != 0) { + fprintf(stderr, "Error: FCGX_InitRequest()\n"); + return 1; + } + + while (FCGX_Accept_r(&request) >= 0) { + try { + char* method = FCGX_GetParam("REQUEST_METHOD", request.envp); + + // POST for server actions, changes + if (!strcmp(method, "POST")) { + size_t contentLength { std::stoul(FCGX_GetParam("CONTENT_LENGTH", request.envp)) }; + std::string postData(contentLength, '\0'); // contentLength number of bytes, initialize with 0 + if (FCGX_GetStr(postData.data(), contentLength, request.in) != contentLength) { + throw std::runtime_error("Bad data read: Content length mismatch.\r\n"); + } + // postData contains POST data + std::string contentType(FCGX_GetParam("CONTENT_TYPE", request.envp)); + + std::string xmlData = postData; // default: interpret whole POST data as xml request + + pt::ptree xml; + std::istringstream ss{xmlData}; + pt::xml_parser::read_xml(ss, xml, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); + + std::string url {xml.get<std::string>("request.url")}; + std::string format {xml.get<std::string>("request.format")}; + std::string command {xml.get<std::string>("request.command")}; + + //FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); + //FCGX_FPrintF(request.out, "url: %s\r\n", url.c_str()); + //FCGX_FPrintF(request.out, "format: %s\r\n", format.c_str()); // mp3, mp4 + + if (format != "mp3" && format != "mp4") { + throw std::runtime_error("Bad format: "s + format); + } + + if (!std::regex_match(url, re)) { + throw std::runtime_error("Bad URL"); + } + + // remove trailing "&..." + size_t and_pos {url.find('&')}; + if (and_pos != std::string::npos) + url = url.substr(0, and_pos); + + //FCGX_FPrintF(request.out, "command: %s\r\n", command.c_str()); + + TempDir tempDir; + + //FCGX_FPrintF(request.out, "path: %s\r\n", tempDir.getPath().string().c_str()); + + if (command == "getfilename") { + //std::string cmd{"youtube-dl -o '%(title)s."s + format + "' --get-filename --no-call-home --restrict-filenames "s + url + " > filename.txt"}; + // Recoding to MP4 is too slow currently. So keep original format for now + std::string cmd{"youtube-dl -o '%(title)s.%(ext)s' --get-filename --no-call-home --restrict-filenames "s + url + " > filename.txt"}; + if (system(cmd.c_str())) + throw std::runtime_error("Can't guess filename"); + + std::string filename {File::getFile("filename.txt")}; + boost::algorithm::trim(filename); + + if (format == "mp3") + filename = fs::path{filename}.stem().string() + ".mp3"; + + FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); + FCGX_FPrintF(request.out, "%s", filename.c_str()); + } else if (command == "getfile") { + if (format == "mp3") { + std::string cmd{"youtube-dl --no-warnings --no-call-home --no-progress -x --audio-format mp3 -o 'audio.%(ext)s' --restrict-filenames "s + url}; + system(cmd.c_str()); // Ignore error - "ERROR: Stream #1:0 -> #0:1 (copy)" - seems to be ok + + std::string filedata {File::getFile("audio.mp3")}; // may throw + + if (filedata.size() > 30000000) + throw std::runtime_error("File too big"); + + FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out); + FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size()); + FCGX_PutStr(filedata.c_str(), filedata.size(), request.out); + } else if (format == "mp4") { + //std::string cmd{"youtube-dl --no-warnings --no-call-home --no-progress --recode-video mp4 -o video.mp4 --restrict-filenames "s + url}; + // Recoding to MP4 is too slow currently. So keep original format for now + std::string cmd{"youtube-dl --no-warnings --no-call-home --no-progress -o video.mp4 --restrict-filenames "s + url}; + system(cmd.c_str()); // Ignore error + + // youtube-dl gets it wrong and creates, e.g. video.mkv. + // So find it and load it + fs::directory_iterator di{fs::current_path()}; + fs::path filename; + for (const auto& i: di) { + if (boost::algorithm::starts_with(i.path().filename().string(), "video."s)) { + filename = i.path().filename().string(); + break; + } + } + + if (filename.empty()) { + throw std::runtime_error("No video file found."); + } + + std::string filedata {File::getFile(filename)}; // may throw + + if (filedata.size() > 300000000) + throw std::runtime_error("File too big"); + + FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out); + FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size()); + FCGX_PutStr(filedata.c_str(), filedata.size(), request.out); + } else { + throw std::runtime_error("Bad format for unknown reason: "s + format); // should have been caught above already! + } + } else { + throw std::runtime_error("Bad command: "s + command); + } + + // Name: + // youtube-dl -o "%(title)s.mp3" --get-filename --no-call-home --restrict-filenames $SOURCE > filename.txt + // + // MP3: + // youtube-dl --no-warnings --no-call-home --no-progress -x --audio-format mp3 --embed-thumbnail -o "%(title)s.(ext)s" --restrict-filenames $SOURCE + // + // MP4: + // youtube-dl --no-warnings --no-call-home --no-progress --recode-video mp4 -o "%(title)s.(ext)s" --restrict-filenames $SOURCE + } else { + throw std::runtime_error("Unsupported method.\r\n"); + } + } catch (const std::runtime_error& ex) { + FCGX_PutS("Status: 500 Internal Server Error\r\n", request.out); + FCGX_PutS("Content-Type: text/html\r\n\r\n", request.out); + FCGX_FPrintF(request.out, "Error: %s\r\n", ex.what()); + } catch (const std::exception& ex) { + FCGX_PutS("Status: 500 Internal Server Error\r\n", request.out); + FCGX_PutS("Content-Type: text/html\r\n\r\n", request.out); + FCGX_FPrintF(request.out, "Unknown exception: %s\r\n", ex.what()); + } + } + + return 0; +} + |