summaryrefslogtreecommitdiffhomepage
path: root/whiteboard.cpp
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2022-11-05 13:49:53 +0100
committerRoland Reichwein <mail@reichwein.it>2022-11-05 13:49:53 +0100
commit4aeab7931182cb1c35bd5c52b58d71b30c32674d (patch)
treee9635c5b2c0827f16dc2021a6193139ef536793b /whiteboard.cpp
Initial files, WIP
Diffstat (limited to 'whiteboard.cpp')
-rw-r--r--whiteboard.cpp215
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;
+}
+