7 changed files with 8405 additions and 0 deletions
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
[submodule "deps/libtorrent"] |
||||
path = deps/libtorrent |
||||
url = https://github.com/arvidn/libtorrent |
||||
branch = master |
@ -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 |
@ -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. |
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
Subproject commit 57e284118014b8ec67dfa5bd25aa5f9fa4174b70 |
@ -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 |
@ -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; |
||||
} |
Loading…
Reference in new issue