Browse Source

first early version (#1)

* parses returned json from peertube instance
* can download and seed videos
master
scuti 3 weeks ago
parent
commit
6192fc493a
  1. 4
      .gitmodules
  2. 39
      Makefile
  3. 45
      README.md
  4. 1
      deps/libtorrent
  5. 473
      include/peertube.hpp
  6. 335
      main.cpp
  7. 7508
      peertube.yaml

4
.gitmodules vendored

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
[submodule "deps/libtorrent"]
path = deps/libtorrent
url = https://github.com/arvidn/libtorrent
branch = master

39
Makefile

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
# https://github.com/arvidn/libtorrent/pull/4123
# using submodule b/c it has to be the master branch
CFLAGS=-I"./include/" -I"./deps/libtorrent/include/" -I"./deps/"
# -I"./deps/libtorrent/include/libtorrent/"
LDFLAGS= -Dwebtorrent=ON -DTORRENT_USE_OPENSSL -lpthread -lcurl -lcurlpp
# -lcurses -lboost_system -ltorrent-rasterbar
# building libtorrent with webtorrent support
# git submodule init --recursive
# cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17 -Dwebtorrent=ON -G Ninja ..
LIBTORRENT= ./deps/libtorrent/build/libtorrent-rasterbar.so.2.0.5
# -Wl,-soname
LD_LIBRARY_PATH=./deps/libtorrent/build/
all: | main
# something to avoid prepending LD_LIBRARY_PATH=./deps/... every time.
# better if compilted to just search in relative path to begin with
patchelf --remove-needed libtorrent-rasterbar.so.2.0 cppia
patchelf --add-needed ./deps/libtorrent/build/libtorrent-rasterbar.so.2.0 cppia
main:
g++ main.cpp $(CFLAGS) $(LDFLAGS) $(LIBTORRENT) -std=c++17 -o cppia -g
ncurses:
g++ demo_window.cpp -lcurses -o window
# OPENAPI_GENERATOR=/home/user/Programs/openapi-generator-cli.jar
# PEERTUBE_API=./deps/peertube/
# openapi:
# java -jar $(OPENAPI_GENERATOR) generate -i peertube.yaml -g c -o $(PEERTUBE_API)
# video:
# g++ ./deps/peertube/model/video.c -std=c++17
.PHONY: clean
clean:
rm cppia

45
README.md

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
# cppia
A peertube client in C++.
**Why?**
I wanted to be punny.
**Does it work?**
Yes, technically. As an early version, the best it can do is download and seed videos.
## Dependencies
g++ make curlpp
A build of libtorrent with WebTorrent support is required. (libtorrent is a submodule of this project.)
gnutls-devel openssl-devel ninja boost
## Building
Build libtorrent.
git submodule init --recursive
cd ./deps/libtorrent
mkdir build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17 -Dwebtorrent=ON -G Ninja ..
ninja
Build cppia.
make
### Tested With
* boost-1.72.0_8
* boost-1.79.0.beta1
## Usage
cppia [video url]
cppia seed [list of torrent files in plain text]
The `cppia seed` will still download files or missing pieces.

1
deps/libtorrent vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
Subproject commit 57e284118014b8ec67dfa5bd25aa5f9fa4174b70

473
include/peertube.hpp

@ -0,0 +1,473 @@ @@ -0,0 +1,473 @@
#ifndef PEERTUBE_HTP
#define PEERTUBE_HPP
int interpret(const std::string &user_input, std::string &host, std::string &id) {
// what did the user link?
char *token = new char[user_input.length() + 1];
strcpy(token, user_input.c_str());
token = strtok(token, "/");
unsigned int state = 0;
bool is_playlist = false;
while (token != NULL) {
switch (state) {
case 0: // protocol
if (strcmp(token, "https:") != 0
&& strcmp(token, "http:") != 0) {
goto endwhile;
}
state++;
break;
case 1: // domain
host = std::string(token);
state++;
break;
case 2: // can be either api or 'w'
if (strcmp(token, "w") == 0) {
state++;
} else if (strcmp(token, "api") == 0) {
state = 'a';
} break;
case 3: // can either be playlist indicator or a video id
if (strcmp(token, "p") == 0) {
state++;
} else {
id = std::string(token);
} break;
case 4: // playlist id
id = std::string(token);
break;
case 'a': // api version indicator
state++;
break;
case 'b':
if (strcmp(token, "videos") == 0) {
state++;
} else if (strcmp(token,"video-playlists") == 0) {
is_playlist = true;
state++;
} break;
case 'c':
id = std::string(token);
break;
}
token = strtok(NULL, "/");
}
endwhile:
delete [] token;
if (is_playlist) {
state++; // so caller func knows api video vs api playlist
}
return state;
}
// https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo
// https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideoPlaylistVideos
std::string get_endpoint(const std::string &user_input, bool &is_playlist) {
std::string host;
std::string id;
int what = 0;
what = interpret(user_input, host, id);
std::string url = std::string("");
std::string api;
std::stringstream ss;
switch (what) {
default:
std::cout << what << std::endl;
case 0:
case 1:
std::cout << "URL not recognized." << std::endl;
break;
case 3:
std::cout << "video page" << std::endl;
api = "/api/v1/videos/"; //(host, id)
ss << "https://" << host << api << id;
url = ss.str();
break;
case 'c': // video api
std::cout << "video api endpoint" << std::endl;
url = user_input;
break;
case 'd':
std::cout << "playlist api endpoint" << std::endl;
url = user_input;
break;
case 4:
std::cout << "playlist page" << std::endl;
api = "/api/v1/video-playlists/";
ss << "https://" << host << api << id << "/videos";
url = ss.str();
break;
}
return url;
}
// #include "peertube/model/video.h" <- model does not compile
// https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo
// salvage the stuff that didn't compile.'
#define FILE_T_STR_COUNT 6
enum {
MAGNET_URI,
TORRENT_URL,
TORRENTDOWNLOAD_URL,
FILE_URL,
FILEDOWNNLOAD_URL,
METADATA_URL
} file_t_attribs;
struct file_t {
// char *magnetUri;
int resolution; // ignore label
int size;
// char *torrentUrl;
// char *torrentDownloadUrl;
// char *fileUrl;
// char *fileDownloadUrl;
int fps;
// char *metadataurl;
char *attribs[FILE_T_STR_COUNT]; // B)
};
struct file_t* file_init (int resolution,
int size,
int fps,
unsigned int len_data,
const char **data) {
struct file_t *f = (file_t*)malloc(sizeof(file_t));
f -> resolution = resolution;
f -> size = size;
f -> fps = fps;
if (len_data != FILE_T_STR_COUNT) {
return NULL;
}
// not going to take any cues
// on how to initialize a struct from openapigenerator
unsigned int len;
unsigned int i = 0;
for (i; i < FILE_T_STR_COUNT; ++i) {
if (data[i] == NULL) {
return NULL;
}
len = strlen(data[i]) * sizeof(char);
f -> attribs[i] = (char*)malloc(len);
strcpy(f -> attribs[i], data[i]);
}
return f;
}
void file_print(const struct file_t * const f) {
unsigned int i = 0;
for (i; i < FILE_T_STR_COUNT; ++i) {
fprintf(stdout, "%s\n", f -> attribs[i]);
}
}
void file_free(struct file_t* f) {
unsigned int i = 0;
for (i; i < FILE_T_STR_COUNT; ++i) {
free(f -> attribs[i]);
}
free(f);
}
struct video_t {
int id;
char *uuid;
char *short_uuid;
// int is_live;
// char *created_at; //date time
// char *published_at; //date time
// char *updated_at; //date time
// char *originally_published_at; //date time
// skip other models
char *description; // string
int duration; //numeric
// int is_local; //boolean
char *name; // string
int views; //numeric
int likes; //numeric
int dislikes; //numeric
int nsfw; //boolean
// skip other models again
int file_count;
struct file_t** files;
};
struct video_t *video_init(int id,
const char *uuid,
const char *short_uuid,
const char *description,
int duration,
const char *name,
int views,
int likes,
int dislikes,
int nsfw,
int file_count,
struct file_t** files) {
struct video_t *v = (video_t*)malloc(sizeof(video_t));
if (!v) {
return NULL;
}
v -> id = id;
v -> duration = duration;
v -> views = views;
v -> likes = likes;
v -> dislikes = dislikes;
v -> nsfw = nsfw;
unsigned int len;
if (uuid != NULL) {
len = strlen(uuid) * sizeof(char);
v -> uuid = (char *)malloc(len);
strcpy(v -> uuid, uuid);
}
if (short_uuid != NULL) {
len = strlen(short_uuid) * sizeof(char);
v -> short_uuid = (char*)malloc(len);
strcpy(v -> short_uuid, short_uuid);
}
if (description != NULL) {
len = strlen(description) * sizeof(char);
v -> description = (char*)malloc(len);
strcpy(v -> description, description);
}
if (name != NULL) {
len = strlen(name) * sizeof(char);
v -> name = (char*)malloc(len);
strcpy(v -> name, name);
}
v -> file_count = file_count;
if (files != NULL) {
v -> files = files;
}
return v;
}
void video_free(struct video_t *v) {
if (v == NULL) {
return;
}
if (v -> uuid != NULL) {
free(v -> uuid);
}
if (v -> short_uuid != NULL) {
free(v -> short_uuid);
}
if (v -> description != NULL) {
free(v -> description);
}
if (v -> name != NULL) {
free(v -> name);
}
unsigned int i;
for (i = 0; i < v-> file_count; ++i) {
file_free(v -> files[i]);
}
free(v -> files);
free(v);
}
void video_print(const struct video_t * const v) {
if (v == NULL) {
return;
}
fprintf(stdout, "%s\n", v -> name);
fprintf(stdout, "%s\n", v -> description);
fprintf(stdout, "%i views\n%i:%i (likes:dislikes)\n",
v -> views, v -> likes, v -> dislikes);
}
#include <cmath>
#include <climits>
// pick file download of a target resolution
struct file_t *video_pick_file(const struct video_t* const v, int target_res=0) {
if (v == NULL) {
return NULL;
}
if (v -> files == NULL) {
return NULL;
}
if (target_res == 0) {
// default behavior => pick best quality
return v-> files[0];
}
int lowest_delta = INT_MAX;
struct file_t *best_file = NULL;
unsigned int i;
for (i = 0; i < v -> file_count; ++i) {
int res = v -> files[i] -> resolution;
int delta = std::abs(target_res - res);
if (delta < lowest_delta) {
lowest_delta = delta;
best_file = v -> files[i];
}
}
return best_file;
}
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
rapidjson::Value& get_video_files(rapidjson::Document& root) {
rapidjson::Document *ptr = &root;
rapidjson::Value& out = root["files"];
assert(out.IsArray());
if (out.Size() > 0) {
return out;
}
// if the list is empty - HLS is enabled on the server
// therefore, look under streamingPlaylists
rapidjson::Value &playlists = root["streamingPlaylists"];
assert(playlists.IsArray());
for (rapidjson::SizeType i = 0; i < playlists.Size(); ++i) {
if (!playlists[i]["type"].IsInt()) {
continue;
}
// don't know what type other than 1 is.'
if (playlists[i]["type"].GetInt() == 1) {
out = playlists[i]["files"];
break;
} else {
std::cout << "Found streamingPlaylist type != 1" << std::endl;
}
}
return out;
}
struct file_t *init_from_json(const rapidjson::Value& file) {
const char *elem_ints[] = {
// "resolution", // id is nested inside resolution
"size",
"fps"
};
int ints[2];
// keep order -see enum above struct file_t definition
const char *elem_strs[FILE_T_STR_COUNT] = {
"magnetUri",
"torrentUrl",
"torrentDownloadUrl",
"fileUrl",
"fileDownloadUrl",
"metadataUrl"
};
const char *strs[FILE_T_STR_COUNT];
int resolution;
if (!file["resolution"]["id"].IsInt()) {
return NULL;
}
resolution = file["resolution"]["id"].GetInt();
unsigned int i = 0;
for (i; i < 2; ++i) {
const char *element = elem_ints[i];
const rapidjson::Value &v = file[element];
if (!v.IsInt()) {
return NULL;
}
ints[i] = v.GetInt();
}
i = 0;
for (i; i < FILE_T_STR_COUNT; ++i) {
const char *element = elem_strs[i];
const rapidjson::Value &v = file[element];
if (!v.IsString()) {
return NULL;
}
strs[i] = v.GetString();
}
struct file_t *foo = file_init(
resolution, ints[0], ints[1], FILE_T_STR_COUNT, strs);
return foo;
}
struct video_t *init_from_json(rapidjson::Document& root) {
#define INT_ATTRIB_COUNT 5
const char *elem_int[INT_ATTRIB_COUNT] = {
"id",
"duration",
"views",
"likes",
"dislikes",
};
int ints[INT_ATTRIB_COUNT];
#define STR_ATTRIB_COUNT 4
const char *elem_str[STR_ATTRIB_COUNT] = {
"uuid",
"shortUUID",
"description",
"name"
};
std::string strs[STR_ATTRIB_COUNT];
unsigned int i;
for (i = 0; i < INT_ATTRIB_COUNT; ++i) {
const char *element = elem_int[i];
rapidjson::Value& v = root[element];
if (!v.IsInt()) {
return NULL;
}
ints[i] = v.GetInt();
}
for (i = 0; i < STR_ATTRIB_COUNT; ++i) {
const char *element = elem_str[i];
rapidjson::Value& v = root[element];
if (!v.IsString()) {
return NULL;
}
strs[i] = v.GetString();
}
rapidjson::Value &v = root["nsfw"];
if (!v.IsBool()) {
return NULL;
}
bool is_nsfw = v.GetBool();
int nsfw = (is_nsfw) ? 1 : 0;
const rapidjson::Value &vf = get_video_files(root);
unsigned int file_count = vf.Size();
unsigned int size = file_count * sizeof(struct file_t**);
struct file_t ** f = (struct file_t**)malloc(size);
for (rapidjson::SizeType i = 0; i < vf.Size(); ++i) {
f[i] = init_from_json(vf[i]);
}
struct video_t *bar = video_init(
ints[0],
strs[0].c_str(),
strs[1].c_str(),
strs[2].c_str(),
ints[1],
strs[3].c_str(),
ints[2],
ints[3],
ints[4],
nsfw,
file_count,
f
);
return bar;
}
std::string get_magnet_link(const rapidjson::Value& files) {
unsigned int highest = 0;
std::string best_video;
// peertube tends to order highest to lowest, but just to make sure...
for (rapidjson::SizeType i = 0; i < files.Size(); ++i ) {
assert(files[i]["resolution"]["id"].IsInt());
unsigned int resolution = files[i]["resolution"]["id"].GetInt();
std::string magnet_link = files[i]["magnetUri"].GetString();
if (resolution > highest) {
highest = resolution;
best_video = magnet_link;
}
}
std::cout << "The best quality video is " << best_video << std::endl;
return best_video;
}
#endif

335
main.cpp

@ -0,0 +1,335 @@ @@ -0,0 +1,335 @@
#include "libtorrent/session.hpp"
#include "libtorrent/torrent_handle.hpp"
#include "libtorrent/magnet_uri.hpp"
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <vector>
#include <thread>
#include <chrono>
#include <memory>
void read_alerts(std::vector<lt::alert*> &alerts) {
for (lt::alert const* a : alerts) {
// alert examples on libtorrent do not work.
// https://www.libtorrent.org/tutorial-ref.html
// results in: compilation error - incomplete type
// type code referenced from:
// ./deps/libtorrent/include/libtorrent/alert_types.hpp
switch (a -> type()) {
default:
std::cout << a -> message() << ", " << a -> type() << std::endl;
// case 11: // tracker unreachable
// case 67: // torrent added
// case 23:
// case 26: // torrent finished alert
// case 64: // torrent error alert
}
}
return;
}
std::vector<std::string> readfile(std::string manifest) {
std::vector<std::string> out;
std::fstream fin;
fin.open(manifest);
std::string line;
if (!fin.is_open()) {
fprintf(stderr, "Error: could not open file.\n");
}
while (fin) {
std::getline(fin, line);
out.push_back(line);
}
return out;
}
void display(lt::torrent_status &ts) {
// <download_rate> downloading <name> from <num_peers> (<num seeds> seeds)
// <upload rate> uploading <name> to <num_peers> (<num seeds> seeds)
// fuck cout
static char last_state = 0;
// don't bother line breaking for checks and meta data'
if (last_state != ts.state) {
std::cout << std::endl;
}
// long names will spam console
#define MAX_LENGTH 40
std::string shortname;
if (ts.name.length() > MAX_LENGTH) {
shortname = ts.name.substr(0, MAX_LENGTH);
} else {
shortname = ts.name;
}
const char *tname = shortname.c_str();
float perc = ts.progress * 100;
float down = ts.download_rate/1000000;
float up = ts.upload_rate/1000000;
std::int64_t total_upload = ts.total_upload/1000000;
switch(ts.state) {
case 1: // checking files
fprintf(stdout, "checking files\r");
break;
case 2: // downloading meta data
fprintf(stdout, "downloading meta data\r");
break;
case 3: // downloading
fprintf(stdout, "[%2.2f%] Downloading %s from %i peers (%i seeds) @ %2.2f MB/s\r",
perc, tname, ts.num_peers, ts.num_seeds, down);
break;
case 4: // finished
break;
case 5: // seeding
fprintf(stdout, "Uploading %s to %i/%i peers (%i seeds) @ %2.2f MB/s [%i MB]\r",
tname, ts.num_peers, ts.list_peers, ts.list_seeds, up, total_upload);
break;
}
last_state = ts.state;
}
void get_handles_from_list(lt::session &s,
std::vector<lt::torrent_handle> &h,
std::string manifest) {
std::vector<std::string> torrents = readfile(manifest.c_str());
if (torrents.empty()) {
return;
}
std::string torrent;
for (unsigned int i = 0; i < torrents.size(); ++i) {
torrent = torrents[i];
if (torrent.length() < 2) { // less than 1 char and newline
continue;
}
lt::add_torrent_params p;
p.save_path = "./downloaded";
try {
p.ti = std::make_shared<lt::torrent_info>(torrent);
} catch (std::exception const&e) {
std::cerr << "Encountered error while reading torrent files ("
<< torrent
<< ").\n"
<< e.what() << std::endl;
return;
}
h.push_back(s.add_torrent(p));
}
}
// run torrents in the background
void seedbox (std::string &torrentfiles) {
#define MINUTE 60
lt::session s;
// magnet link version
// lt::add_torrent_params params = lt::parse_magnet_uri(magnet_link);
// params.save_path = "."; // save in current dir
// lt::torrent_handle h = s.add_torrent(params);
std::vector<lt::torrent_handle> handles;
get_handles_from_list(s, handles, torrentfiles);
// std::vector<std::string> maglinks = readfile(magnetlinks.c_str());
const int TORRENT_COUNT = handles.size();
unsigned int not_dl_count = 0;
unsigned int tid = 0;
unsigned int elapsed = 0;
while(true) {
std::vector<lt::alert*> alerts;
s.pop_alerts(&alerts);
read_alerts(alerts);
for (unsigned int i = 0; i < TORRENT_COUNT; ++i) {
lt::torrent_status ts = handles[i].status();
if (!ts.is_seeding && !ts.is_finished) {
not_dl_count++;
}
}
// if no torrents are downloading
if (!(TORRENT_COUNT > not_dl_count)) {
lt::torrent_status spotlight = handles[tid].status();
display(spotlight);
if (elapsed >= MINUTE) {
std::cout << std::endl;
tid = (tid + 1) % TORRENT_COUNT;
}
}
elapsed = (elapsed + 1) % (MINUTE + 1);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>
std::string request(const std::string& url) {
curlpp::options::Url source (url);
curlpp::Easy request;
request.setOpt(source);
std::ostringstream response;
try {
response << request;
} catch (curlpp::LibcurlRuntimeError e) {
std::cout << e.what() << std::endl;
} catch (std::exception const& e) {
std::cout << e.what() << std::endl;
}
return response.str();
}
#include "libtorrent/create_torrent.hpp"
#include "libtorrent/entry.hpp"
#include "libtorrent/bencode.hpp"
#include "peertube.hpp" // also includes rapidjson
void torrent_basic_loop(std::string torrent_file) {
lt::session s;
lt::add_torrent_params p;
p.save_path = "./downloaded";
p.ti = std::make_shared<lt::torrent_info>(torrent_file);
lt::torrent_handle h = s.add_torrent(p);
h.set_flags(lt::torrent_flags::sequential_download);
lt::torrent_status ts;
do {
std::vector<lt::alert*> alerts;
s.pop_alerts(&alerts);
read_alerts(alerts);
ts = h.status();
display(ts);
std::this_thread::sleep_for(std::chrono::seconds(1));
} while (!ts.is_seeding && !ts.is_finished);
}
// download torrent file from peertube server
std::string dl_torrentfile(struct file_t *video_file, const char * title = NULL) {
if (video_file == NULL) {
return std::string();
}
if (video_file -> attribs == NULL) {
return std::string();
}
std::string torrent_url (video_file -> attribs[TORRENTDOWNLOAD_URL]);
std::string buffer = request(torrent_url);
std::string filename;
{
std::stringstream ss;
if (title == NULL) { // use the file name from the url
unsigned int pos = torrent_url.rfind("/");
pos++; // +1 to omit '/'
ss << torrent_url.substr(pos, torrent_url.size());
} else {
// does not remove forbidden characters in file name yet
ss << title <<"-"<< video_file -> resolution << "p.torrent";
}
filename = ss.str();
std::cout << filename << std::endl;
}
std::fstream fout;
fout.open (filename, std::fstream::out | std::fstream::binary);
if (fout.good()) {
fout << buffer;
}
fout.close();
return filename;
}
// watch a video given a URL
bool watch(std::string video_url) {
//turn peertube video url into api request
bool is_playlist = false;
std::string api = get_endpoint(video_url, is_playlist);
if (api.length() == 0) {
std::cout << "Error: can't watch video." << std::endl;
return false;
}
// figure something out with playlists.
if (is_playlist) {
std::cout << "playlist support not implemented" << std::endl;
return false;
}
std::string got = request(api);
std::cout << api << std::endl;
rapidjson::Document root;
root.Parse(got.c_str());
// video downloads can either be in "files"
// or under "streamingPlaylists" if HLS is enabled on the server
struct video_t *video = init_from_json(root);
video_print(video);
// get torrent download url
struct file_t *best_file = video_pick_file(video);
// torrent file > magnet link if peertube instance's tracker is unreliable
std::string saved = dl_torrentfile(best_file);
if (!saved.empty()) {
std::cout << "acquired .torrent file! " << saved << std::endl;
torrent_basic_loop(saved);
}
std::cout << "\n\n\n" << std::endl;
video_free(video);
return true;
}
void test (std::string video_url) {
bool is_playlist;
std::string api = get_endpoint(video_url, is_playlist);
std::string got = request(api);
std::cout << got.length() << std::endl;
rapidjson::Document root;
root.Parse(got.c_str());
const rapidjson::Value& vf = get_video_files(root);
for (rapidjson::SizeType i = 0; i < vf.Size(); ++i) {
struct file_t *test = init_from_json(vf[i]);
if (test != NULL) {
file_print(test);
file_free(test);
}
}
}
void test2 (std::string video_url) {
bool is_playlist;
std::string api = get_endpoint(video_url, is_playlist);
std::string got = request(api);
// std::cout << api << std::endl;
rapidjson::Document root;
root.Parse(got.c_str());
struct video_t *video = init_from_json(root);
video_print(video);
// get torrent download url
struct file_t *best_file = video_pick_file(video);
std::string saved = dl_torrentfile(best_file);
if (!saved.empty()) {
std::cout << "acquired .torrent file! " << saved << std::endl;
torrent_basic_loop(saved);
}
std::cout << "\n\n\n" << std::endl;
video_free(video);
}
int main(int argc, char const* argv[]) {
std::cout << "This is cppia (unreleased)." << std::endl;
bool success;
switch (argc) {
case 1:
std::cout << "Usages: \n"
<< "\t cppia <video url>\n"
<< "\t cppia seed <list of torrent files>" << std::endl;
break;
case 2:
// if there is only one argument, assume it is a video url
success = watch(std::string(argv[1]));
if (!success) {
std::cout << "Usage: cppia <video url>" << std::endl;
}
break;
case 3:
std::string option (argv[1]);
std::string s(argv[2]);
if (option.compare("seed") == 0) {
seedbox(s);
} else if (option.compare("test") == 0) {
test2(s);
}
break;
}
return 0;
}

7508
peertube.yaml

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save