summaryrefslogtreecommitdiffhomepage
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
Initial files, WIP
-rwxr-xr-xMakefile90
-rw-r--r--debian/README.Debian44
-rw-r--r--debian/changelog5
-rw-r--r--debian/compat1
-rw-r--r--debian/control16
-rw-r--r--debian/copyright4
-rwxr-xr-xdebian/rules4
-rw-r--r--debian/source/format1
-rw-r--r--debian/whiteboard.service13
-rw-r--r--file.cpp46
-rw-r--r--file.h15
-rw-r--r--html/index.html60
-rw-r--r--html/whiteboard.css69
-rw-r--r--html/whiteboard.js116
-rwxr-xr-xstart.sh5
-rw-r--r--webserver.conf.example8
-rw-r--r--whiteboard.cpp215
17 files changed, 712 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100755
index 0000000..034b024
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,90 @@
+#
+# Makefile
+#
+# Environment: Debian
+#
+
+DISTROS=debian10 debian11 ubuntu2004 ubuntu2204
+VERSION=$(shell dpkg-parsechangelog --show-field Version)
+
+CXX=clang++-10
+
+ifeq ($(shell which $(CXX)),)
+CXX=clang++
+endif
+
+ifeq ($(shell which $(CXX)),)
+CXX=g++-9
+endif
+
+ifeq ($(shell which $(CXX)),)
+CXX=g++
+endif
+
+LIBS=-lfcgi -lboost_filesystem
+INCLUDES=-I.
+CXXFLAGS=-Wall -g -O2 -fPIC -std=c++17 -Wpedantic
+HEADERS=file.h
+SOURCES=$(HEADERS:.h=.cpp)
+OBJECTS=$(HEADERS:.h=.o)
+TARGETS=whiteboard.fcgi
+
+ifeq ($(CXX),clang++-10)
+LIBS+= \
+-fuse-ld=lld-10 \
+-lstdc++
+#-lc++ \
+#-lc++abi
+#-lc++fs
+#-lstdc++fs
+else
+LIBS+= \
+-lstdc++ \
+-lstdc++fs
+endif
+
+build: $(TARGETS)
+
+all: build
+ ./start.sh
+
+install:
+ mkdir -p $(DESTDIR)/usr/lib/whiteboard
+ cp whiteboard.fcgi $(DESTDIR)/usr/lib/whiteboard/
+
+ mkdir -p $(DESTDIR)/usr/lib/whiteboard/html
+ cp -r html/* $(DESTDIR)/usr/lib/whiteboard/html/
+
+ uglifyjs html/whiteboard.js -m -c > $(DESTDIR)/usr/lib/whiteboard/html/whiteboard.js
+ htmlmin html/index.html $(DESTDIR)/usr/lib/whiteboard/html/index.html
+ cleancss -o $(DESTDIR)/usr/lib/whiteboard/html/whiteboard.css html/whiteboard.css
+
+
+whiteboard.fcgi: $(OBJECTS)
+
+# link
+%.fcgi: %.o
+ $(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LIBS) -o $@
+
+# .cpp -> .o
+%.o: %.cpp
+ $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
+
+clean:
+ -rm -f *.o *.fcgi
+
+deb:
+ dpkg-buildpackage
+
+deb-src: clean
+ dh_clean
+ dh_auto_clean
+ dpkg-source -b -I.git -Iresult .
+
+$(DISTROS): deb-src
+ sudo pbuilder build --basetgz /var/cache/pbuilder/$@.tgz --buildresult result/$@ ../whiteboard_$(VERSION).dsc
+ debsign result/$@/whiteboard_$(VERSION)_amd64.changes
+
+debs: $(DISTROS)
+
+.PHONY: clean
diff --git a/debian/README.Debian b/debian/README.Debian
new file mode 100644
index 0000000..f3f2434
--- /dev/null
+++ b/debian/README.Debian
@@ -0,0 +1,44 @@
+whiteboard for Debian
+=====================
+
+This package is the Debian version of whiteboard.
+
+
+Configuration
+-------------
+
+* You can add this to /etc/webserver.conf
+
+ <path requested="/whiteboard">
+ <plugin>static-files</plugin>
+ <target>/usr/lib/whiteboard/html</target>
+ </path>
+ <path requested="/whiteboard/whiteboard.fcgi">
+ <plugin>fcgi</plugin>
+ <target>127.0.0.1:9014</target>
+ </path>
+
+* Enable:
+
+ # systemctl enable whiteboard.service
+
+* Start:
+
+ # systemctl start whiteboard
+
+* Stop:
+
+ # systemctl stop whiteboard
+
+* Query Status:
+
+ # systemctl status whiteboard
+
+ and observe /var/log/syslog
+
+
+Contact
+-------
+
+Reichwein IT <mail@reichwein.it>
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..3db9796
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+whiteboard (1.0) unstable; urgency=medium
+
+ * Initial release
+
+ -- Roland Reichwein <mail@reichwein.it> Sat, 05 Nov 2022 13:34:57 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..48082f7
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+12
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..dc159fa
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,16 @@
+Source: whiteboard
+Section: web
+Priority: optional
+Maintainer: Roland Reichwein <mail@reichwein.it>
+Build-Depends: debhelper (>= 12), libboost-all-dev | libboost1.71-all-dev, clang | g++-9, node-uglify, python3-pkg-resources, htmlmin, cleancss, libfcgi-dev
+Standards-Version: 4.5.0
+Homepage: http://www.reichwein.it/whiteboard/
+
+Package: whiteboard
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, spawn-fcgi
+Recommends: webserver
+Homepage: http://www.reichwein.it/whiteboard/
+Description: Web application for an collaborative editor
+ Whiteboard is a text editor running on an HTML5 webpage (including a server
+ part) that enables collaborative editing and presenting.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..5007f59
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,4 @@
+Author: Roland Reichwein <mail@reichwein.it>, 2022
+
+Both upstream source code and Debian packaging is available
+under the conditions of CC0 1.0 Universal
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..2d33f6a
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,4 @@
+#!/usr/bin/make -f
+
+%:
+ dh $@
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/debian/whiteboard.service b/debian/whiteboard.service
new file mode 100644
index 0000000..c60f3f0
--- /dev/null
+++ b/debian/whiteboard.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Whiteboard
+After=network.target
+
+[Service]
+Type=simple
+# Restart=always
+ExecStart=spawn-fcgi -a 127.0.0.1 -p 9014 -n -- /usr/lib/whiteboard/whiteboard.fcgi
+
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
diff --git a/file.cpp b/file.cpp
new file mode 100644
index 0000000..47ab8be
--- /dev/null
+++ b/file.cpp
@@ -0,0 +1,46 @@
+#include "file.h"
+
+#include <fstream>
+
+namespace fs = std::filesystem;
+
+using namespace std::string_literals;
+
+std::string File::getFile(const fs::path& filename)
+{
+ std::ifstream file(filename.string(), std::ios::in | std::ios::binary | std::ios::ate);
+
+ if (file.is_open()) {
+ std::ifstream::pos_type fileSize = file.tellg();
+ file.seekg(0, std::ios::beg);
+
+ std::string bytes(fileSize, ' ');
+ file.read(reinterpret_cast<char*>(bytes.data()), fileSize);
+
+ return bytes;
+
+ } else {
+ throw std::runtime_error("Opening "s + filename.string() + " for reading");
+ }
+}
+
+void File::setFile(const fs::path& filename, const std::string& s)
+{
+ File::setFile(filename, s.data(), s.size());
+}
+
+void File::setFile(const fs::path& filename, const char* data, size_t size)
+{
+ std::ofstream file(filename.string(), std::ios::out | std::ios::binary);
+ if (file.is_open()) {
+ file.write(data, size);
+ } else {
+ throw std::runtime_error("Opening "s + filename.string() + " for writing");
+ }
+}
+
+void File::setFile(const fs::path& filename, const std::vector<uint8_t>& data)
+{
+ File::setFile(filename, reinterpret_cast<const char*>(data.data()), data.size());
+}
+
diff --git a/file.h b/file.h
new file mode 100644
index 0000000..e7e4cf6
--- /dev/null
+++ b/file.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <cstdint>
+#include <filesystem>
+#include <string>
+#include <vector>
+
+namespace File {
+
+std::string getFile(const std::filesystem::path& filename);
+void setFile(const std::filesystem::path& filename, const std::string& s);
+void setFile(const std::filesystem::path& filename, const char* data, size_t size);
+void setFile(const std::filesystem::path& filename, const std::vector<uint8_t>& data);
+
+}
diff --git a/html/index.html b/html/index.html
new file mode 100644
index 0000000..f97b295
--- /dev/null
+++ b/html/index.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="keywords" content="Reichwein, DownTube, YouTube, Download MP3">
+ <title>DownTube</title>
+ <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"/>
+ <link rel="stylesheet" type="text/css" href="downtube.css"/>
+ <script src="downtube.js"></script>
+ </head>
+ <body onload="init();">
+ <div class="page">
+ <h1><img src="Downtube256.png"></h1>
+
+ <p>
+ Download internet videos as MP3 (audio) or MP4 (video).
+ </p>
+
+ <p>
+ Video URL:<br/>
+ <input size="40" type="text" id="url" name="url"><br><br>
+ </p>
+
+ <p>
+ Transform to format:<br/>
+ <input type="radio" id="mp3" name="format" value="mp3" checked>
+ <label for="mp3">MP3 (Audio)</label><br>
+ <input type="radio" id="mp4" name="format" value="mp4">
+ <label for="mp4">MP4 (Video)</label><br>
+ </p>
+
+ <br/>
+ <div class="status" id="status">&nbsp;</div>
+ <p>
+ <button class="button" onclick="on_start();">Start</button>
+ </p>
+
+ <br/>
+ <br/>
+ <br/>
+ <br/>
+ <br/>
+ <br/>
+ <p>
+ Note: Audio download is currently limited to 30MB, Video download is limited to 300MB.
+ </p>
+ <br/>
+ <br/>
+ <h2>Contact</h2>
+ Roland Reichwein<br/>
+ Hauptstr. 101a<br/>
+ 82008 Unterhaching<br/>
+ <a href="mailto:mail@reichwein.it">mail@reichwein.it</a><br/>
+ <a href="https://www.reichwein.it">https://www.reichwein.it</a><br/>
+ </div>
+
+ <a id="download-a" hidden></a>
+ </body>
+</html>
diff --git a/html/whiteboard.css b/html/whiteboard.css
new file mode 100644
index 0000000..2f68794
--- /dev/null
+++ b/html/whiteboard.css
@@ -0,0 +1,69 @@
+body {
+ font-family: "sans-serif";
+}
+
+figcaption {
+ text-align: center;
+ font-size: 8px;
+ color: #808080;
+}
+
+figure {
+ display: inline-block;
+}
+
+p {
+ margin: 30px 0px 30px 0px;
+}
+
+div.status {
+ color: #FF0000;
+}
+
+.mobile {
+ width: 300px;
+ border-width: 80px 15px 80px 15px;
+ border-style: solid;
+ border-radius: 30px;
+ border-color: #000000;
+}
+
+.logo {
+ display: block;
+ margin: 0 auto;
+}
+
+.screenshot {
+ width: 400px;
+ border: 2px solid;
+ border-color: #8888AA;
+}
+
+img.banner {
+ vertical-align: -5px;
+}
+
+.button {
+ color:#FFFFFF;
+ background-color:#50B050;
+ text-decoration: none;
+ padding: 15px 20px;
+ font-size: 16px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+}
+
+@media only screen and (min-width: 1px) and (max-width: 630px) {
+}
+
+@media only screen and (min-width: 631px) and (max-width: 950px) {
+}
+
+@media only screen and (min-width: 951px) {
+ div.page {
+ max-width: 950px;
+ width: 100%;
+ margin: 0 auto;
+ }
+}
diff --git a/html/whiteboard.js b/html/whiteboard.js
new file mode 100644
index 0000000..f79443e
--- /dev/null
+++ b/html/whiteboard.js
@@ -0,0 +1,116 @@
+// started on main page load
+function init() {
+
+ // Connect "Enter" in text field with Button click
+ var url = document.getElementById("url");
+ url.addEventListener("keyup", function(event) {
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ on_start();
+ }
+ });
+}
+
+function set_status(message) {
+ if (message == "")
+ message = "&nbsp;";
+
+ document.getElementById("status").innerHTML = message;
+}
+
+// started on button click: get filename
+function on_start() {
+ var xhr = new XMLHttpRequest();
+
+ // run on data received back
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4) {
+ return;
+ }
+ if (this.status != 200) {
+ set_status("Server Error while retrieving filename, " + filename + ", status: " + this.status + " " + this.statusText);
+ return;
+ }
+
+ var filename = this.responseText;
+
+ get_file(filename);
+ }
+
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
+
+ var requestElement = xmlDocument.getElementsByTagName("request")[0];
+
+ var commandElement = xmlDocument.createElement("command");
+ commandElement.appendChild(document.createTextNode("getfilename"));
+ requestElement.appendChild(commandElement);
+
+ var urlElement = xmlDocument.createElement("url");
+ urlElement.appendChild(document.createTextNode(document.getElementById("url").value));
+ requestElement.appendChild(urlElement);
+
+ var formatElement = xmlDocument.createElement("format");
+ formatElement.appendChild(document.createTextNode(document.getElementById("mp3").checked ? "mp3" : "mp4"));
+ requestElement.appendChild(formatElement);
+
+ xhr.open("POST", "downtube.fcgi", true);
+ xhr.setRequestHeader("Content-type", "text/xml");
+ xhr.responseType = 'text';
+ xhr.send(xmlDocument);
+
+ set_status("Please wait while retrieving filename...");
+}
+
+// started on button click: get file
+function get_file(filename) {
+ var xhr = new XMLHttpRequest();
+
+ // run on data received back
+ xhr.onreadystatechange = function() {
+ if (this.readyState == 3) {
+ set_status("Please wait while downloading " + filename + " ...");
+ return;
+ }
+ if (this.readyState != 4) {
+ return;
+ }
+ if (this.status != 200) {
+ set_status("Server Error while retrieving " + filename + ", status: " + this.status + " " + this.statusText);
+ return;
+ }
+
+ var a = document.getElementById("download-a");
+ a.setAttribute("download", filename);
+ var file = new Blob([this.response]);
+ a.href = window.URL.createObjectURL(file);
+ a.click();
+
+ set_status(""); // OK
+ }
+
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString("<request></request>", "text/xml");
+
+ var requestElement = xmlDocument.getElementsByTagName("request")[0];
+
+ var commandElement = xmlDocument.createElement("command");
+ commandElement.appendChild(document.createTextNode("getfile"));
+ requestElement.appendChild(commandElement);
+
+ var urlElement = xmlDocument.createElement("url");
+ urlElement.appendChild(document.createTextNode(document.getElementById("url").value));
+ requestElement.appendChild(urlElement);
+
+ var formatElement = xmlDocument.createElement("format");
+ formatElement.appendChild(document.createTextNode(document.getElementById("mp3").checked ? "mp3" : "mp4"));
+ requestElement.appendChild(formatElement);
+
+ xhr.open("POST", "downtube.fcgi", true);
+ xhr.setRequestHeader("Content-type", "text/xml");
+ xhr.responseType = 'blob';
+ xhr.send(xmlDocument);
+
+ set_status("Please wait while server prepares " + filename + " ...");
+}
+
diff --git a/start.sh b/start.sh
new file mode 100755
index 0000000..b42c33e
--- /dev/null
+++ b/start.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+#
+# Test script for debugging
+#
+spawn-fcgi -a 127.0.0.1 -p 9014 -n -- ./whiteboard.fcgi
diff --git a/webserver.conf.example b/webserver.conf.example
new file mode 100644
index 0000000..eeb48a1
--- /dev/null
+++ b/webserver.conf.example
@@ -0,0 +1,8 @@
+ <path requested="/whiteboard">
+ <plugin>static-files</plugin>
+ <target>/usr/lib/whiteboard/html</target>
+ </path>
+ <path requested="/whiteboard/whiteboard.fcgi">
+ <plugin>fcgi</plugin>
+ <target>127.0.0.1:9014</target>
+ </path>
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;
+}
+