Options: Added Season Option
This allows an entire season to be downloaded at once.
This commit is contained in:
parent
570793c973
commit
cd72083b69
|
@ -64,7 +64,6 @@ This needs to be redone every time the cookies expire (~30 minutes)
|
||||||
--verbose Display debug information while running
|
--verbose Display debug information while running
|
||||||
--force-cookies Interpret the next to arguments as authentication cookie and session cookie
|
--force-cookies Interpret the next to arguments as authentication cookie and session cookie
|
||||||
--series Interpret the url as a link to a series and download all episodes from all seasons
|
--series Interpret the url as a link to a series and download all episodes from all seasons
|
||||||
--episode Select an episode from the series to download
|
--season Interpret the url as a link to a season and download all episodes from all seasons
|
||||||
--season Select a season from the series to download
|
|
||||||
```
|
```
|
||||||
By default, dropout-dl will download the episode in the format `<series>/S<season-num>E<episode-num><name>.mp4`
|
By default, dropout-dl will download the episode in the format `<series>/S<season-num>E<episode-num><name>.mp4`
|
|
@ -77,7 +77,6 @@ namespace dropout_dl {
|
||||||
void download(const std::string& quality, const std::string& series_directory, std::string filename = "");
|
void download(const std::string& quality, const std::string& series_directory, std::string filename = "");
|
||||||
|
|
||||||
episode(const std::string& episode_url, std::vector<std::string> cookies, bool verbose = false) {
|
episode(const std::string& episode_url, std::vector<std::string> cookies, bool verbose = false) {
|
||||||
std::cout << episode_url << std::endl;
|
|
||||||
|
|
||||||
this->episode_url = episode_url;
|
this->episode_url = episode_url;
|
||||||
this->verbose = verbose;
|
this->verbose = verbose;
|
||||||
|
|
55
src/main.cpp
55
src/main.cpp
|
@ -292,26 +292,6 @@ std::vector<std::string> get_cookies(bool verbose = false) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* <select class="js-switch-season btn-dropdown-transparent margin-right-small" data-switch-season="">
|
|
||||||
<option value="https://www.dropout.tv/game-changer/season:1" selected="">
|
|
||||||
Season 1
|
|
||||||
</option>
|
|
||||||
<option value="https://www.dropout.tv/game-changer/season:2">
|
|
||||||
Season 2
|
|
||||||
</option>
|
|
||||||
<option value="https://www.dropout.tv/game-changer/season:3">
|
|
||||||
Season 3
|
|
||||||
</option>
|
|
||||||
<option value="https://www.dropout.tv/game-changer/season:4">
|
|
||||||
Season 4
|
|
||||||
</option>
|
|
||||||
<option value="https://www.dropout.tv/game-changer/season:7">
|
|
||||||
Bonus Content
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
*/
|
|
||||||
|
|
||||||
class options {
|
class options {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -319,11 +299,11 @@ public:
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
bool cookies_forced = false;
|
bool cookies_forced = false;
|
||||||
bool series = false;
|
bool series = false;
|
||||||
|
bool season = false;
|
||||||
std::string quality;
|
std::string quality;
|
||||||
std::string filename;
|
std::string filename;
|
||||||
std::string series_dir;
|
std::string series_dir;
|
||||||
std::string episode;
|
std::string episode;
|
||||||
std::string season;
|
|
||||||
std::vector<std::string> cookies;
|
std::vector<std::string> cookies;
|
||||||
|
|
||||||
static std::vector<std::string> convert_program_args(int argc, char** argv) {
|
static std::vector<std::string> convert_program_args(int argc, char** argv) {
|
||||||
|
@ -380,19 +360,8 @@ public:
|
||||||
else if (arg == "series") {
|
else if (arg == "series") {
|
||||||
series = true;
|
series = true;
|
||||||
}
|
}
|
||||||
else if (arg == "episode") {
|
|
||||||
if (i + 1 >= args.size()) {
|
|
||||||
std::cerr << "ARGUMENT PARSE ERROR: --episode used with too few following arguments\n";
|
|
||||||
exit(8);
|
|
||||||
}
|
|
||||||
episode = args[++i];
|
|
||||||
}
|
|
||||||
else if (arg == "season") {
|
else if (arg == "season") {
|
||||||
if (i + 1 >= args.size()) {
|
season = true;
|
||||||
std::cerr << "ARGUMENT PARSE ERROR: --season used with too few following arguments\n";
|
|
||||||
exit(8);
|
|
||||||
}
|
|
||||||
season = args[++i];
|
|
||||||
}
|
}
|
||||||
else if (arg == "help") {
|
else if (arg == "help") {
|
||||||
std::cout << "Usage: dropout-dl [OPTIONS] <url> [OPTIONS]\n"
|
std::cout << "Usage: dropout-dl [OPTIONS] <url> [OPTIONS]\n"
|
||||||
|
@ -406,14 +375,17 @@ public:
|
||||||
"\t--verbose Display debug information while running\n"
|
"\t--verbose Display debug information while running\n"
|
||||||
"\t--force-cookies Interpret the next to arguments as authentication cookie and session cookie\n"
|
"\t--force-cookies Interpret the next to arguments as authentication cookie and session cookie\n"
|
||||||
"\t--series Interpret the url as a link to a series and download all episodes from all seasons\n"
|
"\t--series Interpret the url as a link to a series and download all episodes from all seasons\n"
|
||||||
"\t--episode Select an episode from the series to download\n"
|
"\t--season Interpret the url as a link to a season and download all episodes from all seasons\n"
|
||||||
"\t--season Select a season from the series to download\n"
|
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (season && series) {
|
||||||
|
std::cerr << "ARGUMENT PARSE ERROR: Season and Series arguments used\n";
|
||||||
|
}
|
||||||
if (quality.empty()) {
|
if (quality.empty()) {
|
||||||
quality = "1080p";
|
quality = "1080p";
|
||||||
}
|
}
|
||||||
|
@ -458,6 +430,19 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
series.download(options.quality, options.series_dir);
|
series.download(options.quality, options.series_dir);
|
||||||
}
|
}
|
||||||
|
else if (options.season) {
|
||||||
|
dropout_dl::season season = dropout_dl::series::get_season(options.url, options.cookies);
|
||||||
|
|
||||||
|
if (options.series_dir.empty()) {
|
||||||
|
options.series_dir = season.series_name;
|
||||||
|
|
||||||
|
std::replace(options.series_dir.begin(), options.series_dir.end(), ' ', '_');
|
||||||
|
|
||||||
|
std::replace(options.series_dir.begin(), options.series_dir.end(), ',', '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
season.download(options.quality, options.series_dir);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
dropout_dl::episode ep(options.url, options.cookies, options.verbose);
|
dropout_dl::episode ep(options.url, options.cookies, options.verbose);
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace dropout_dl {
|
||||||
for (int j = 0; j + i < html_data.size(); j++) {
|
for (int j = 0; j + i < html_data.size(); j++) {
|
||||||
if (html_data[i + j] == '"') {
|
if (html_data[i + j] == '"') {
|
||||||
start_point += 15;
|
start_point += 15;
|
||||||
return episode(html_data.substr(i, j), cookies);
|
return {html_data.substr(i, j), cookies};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ namespace dropout_dl {
|
||||||
if (e.episode_url.empty()) {
|
if (e.episode_url.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
std::cout << e.episode_number << ": " << e.name << ": " << e.episode_url << '\n';
|
||||||
out.push_back(e);
|
out.push_back(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +54,14 @@ namespace dropout_dl {
|
||||||
}
|
}
|
||||||
|
|
||||||
void season::download(const std::string &quality, const std::string &series_directory) {
|
void season::download(const std::string &quality, const std::string &series_directory) {
|
||||||
|
std::string dir = series_directory + "/" + this->name;
|
||||||
|
|
||||||
|
std::replace(dir.begin(), dir.end(), ' ', '_');
|
||||||
|
|
||||||
|
std::replace(dir.begin(), dir.end(), ',', '_');
|
||||||
|
|
||||||
for (auto& ep : episodes) {
|
for (auto& ep : episodes) {
|
||||||
ep.download(quality, series_directory + "/" + this->name);
|
ep.download(quality, dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // dropout_dl
|
} // dropout_dl
|
|
@ -13,6 +13,7 @@ namespace dropout_dl {
|
||||||
class season {
|
class season {
|
||||||
public:
|
public:
|
||||||
std::string name;
|
std::string name;
|
||||||
|
std::string series_name;
|
||||||
std::string url;
|
std::string url;
|
||||||
std::string page_data;
|
std::string page_data;
|
||||||
std::vector<episode> episodes;
|
std::vector<episode> episodes;
|
||||||
|
@ -21,9 +22,11 @@ namespace dropout_dl {
|
||||||
|
|
||||||
void download(const std::string& quality, const std::string& series_directory);
|
void download(const std::string& quality, const std::string& series_directory);
|
||||||
|
|
||||||
season(const std::string& url, const std::string& name, const std::vector<std::string>& cookies) {
|
season(const std::string& url, const std::string& name, const std::vector<std::string>& cookies, const std::string& series_name = "") {
|
||||||
this->url = url;
|
this->url = url;
|
||||||
this->name = name;
|
this->name = name;
|
||||||
|
this->series_name = series_name;
|
||||||
|
std::cout << series_name << ": " << name << ": " << url << "\n";
|
||||||
this->page_data = get_generic_page(url);
|
this->page_data = get_generic_page(url);
|
||||||
this->episodes = get_episodes(page_data, cookies);
|
this->episodes = get_episodes(page_data, cookies);
|
||||||
}
|
}
|
||||||
|
|
139
src/series.cpp
139
src/series.cpp
|
@ -6,51 +6,6 @@
|
||||||
|
|
||||||
namespace dropout_dl {
|
namespace dropout_dl {
|
||||||
|
|
||||||
std::string series::get_series_page(const std::string &url, bool verbose) {
|
|
||||||
CURLcode ret;
|
|
||||||
CURL *hnd;
|
|
||||||
struct curl_slist *slist1;
|
|
||||||
|
|
||||||
std::string series_data;
|
|
||||||
|
|
||||||
slist1 = NULL;
|
|
||||||
slist1 = curl_slist_append(slist1, "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0");
|
|
||||||
slist1 = curl_slist_append(slist1, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8");
|
|
||||||
slist1 = curl_slist_append(slist1, "Accept-Language: en-US,en;q=0.5");
|
|
||||||
slist1 = curl_slist_append(slist1, "Accept-Encoding: utf-8");
|
|
||||||
slist1 = curl_slist_append(slist1, "DNT: 1");
|
|
||||||
slist1 = curl_slist_append(slist1, "Connection: keep-alive");
|
|
||||||
slist1 = curl_slist_append(slist1, "Upgrade-Insecure-Requests: 1");
|
|
||||||
slist1 = curl_slist_append(slist1, "Sec-Fetch-Dest: document");
|
|
||||||
slist1 = curl_slist_append(slist1, "Sec-Fetch-Mode: navigate");
|
|
||||||
slist1 = curl_slist_append(slist1, "Sec-Fetch-Site: cross-site");
|
|
||||||
slist1 = curl_slist_append(slist1, "Sec-GPC: 1");
|
|
||||||
|
|
||||||
hnd = curl_easy_init();
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L);
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_URL, url.c_str());
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, slist1);
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.84.0");
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L);
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_VERBOSE, verbose);
|
|
||||||
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, WriteCallback);
|
|
||||||
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &series_data);
|
|
||||||
|
|
||||||
ret = curl_easy_perform(hnd);
|
|
||||||
|
|
||||||
curl_easy_cleanup(hnd);
|
|
||||||
hnd = NULL;
|
|
||||||
curl_slist_free_all(slist1);
|
|
||||||
slist1 = NULL;
|
|
||||||
|
|
||||||
return series_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string series::get_series_name(const std::string& html_data) {
|
std::string series::get_series_name(const std::string& html_data) {
|
||||||
std::string collection_title("collection-title");
|
std::string collection_title("collection-title");
|
||||||
std::string open_a_tag("<h1");
|
std::string open_a_tag("<h1");
|
||||||
|
@ -171,9 +126,103 @@ namespace dropout_dl {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
season series::get_season(const std::string &url, const std::vector<std::string>& cookies) {
|
||||||
|
std::string html_data = get_generic_page(url);
|
||||||
|
|
||||||
|
std::string search_class("js-switch-season");
|
||||||
|
std::string open_select("<select");
|
||||||
|
std::string close_tag(">");
|
||||||
|
std::string close_select("</select>");
|
||||||
|
|
||||||
|
std::string open_option("<option");
|
||||||
|
std::string close_option("</option>");
|
||||||
|
std::string value("value=");
|
||||||
|
|
||||||
|
bool seasons_dropdown = false;
|
||||||
|
bool selected = false;
|
||||||
|
std::string season_url;
|
||||||
|
std::string season_name;
|
||||||
|
for (int i = 0; i < html_data.size(); i++) {
|
||||||
|
if (substr_is(html_data, i, open_select)) {
|
||||||
|
for (int j = i; j < html_data.size(); j++) {
|
||||||
|
if (substr_is(html_data, j, search_class)) {
|
||||||
|
i = j;
|
||||||
|
seasons_dropdown = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (substr_is(html_data, j, close_tag)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (seasons_dropdown) {
|
||||||
|
if (substr_is(html_data, i, value)) {
|
||||||
|
i += value.size() + 1;
|
||||||
|
for (int j = 0; j + i < html_data.size(); j++) {
|
||||||
|
if (html_data[i + j] == '"') {
|
||||||
|
season_url = html_data.substr(i, j);
|
||||||
|
i += j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!season_url.empty() && substr_is(html_data, i, "selected")) {
|
||||||
|
selected = true;
|
||||||
|
}
|
||||||
|
else if (!season_url.empty() && substr_is(html_data, i, close_tag)) {
|
||||||
|
i += close_tag.size() + 1;
|
||||||
|
for (int j = 0; i + j < html_data.size(); j++) {
|
||||||
|
if (html_data[i + j] == '\n') {
|
||||||
|
if (selected) {
|
||||||
|
season_name = html_data.substr(i, j);
|
||||||
|
|
||||||
|
// Remove leading and trailing whitespace
|
||||||
|
bool leading_whitespace = true;
|
||||||
|
int name_start;
|
||||||
|
int name_end;
|
||||||
|
for (int k = 0; k < season_name.size(); k++) {
|
||||||
|
if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') {
|
||||||
|
name_start = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int k = season_name.size() - 1; k > 0; k--) {
|
||||||
|
if (season_name[k] != ' ' && season_name[k] != '\t' && season_name[k] != '\n') {
|
||||||
|
name_end = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
season_name = season_name.substr(name_start,
|
||||||
|
season_name.size() - name_start - name_end);
|
||||||
|
|
||||||
|
return {season_url, season_name, cookies, get_series_name(html_data)};
|
||||||
|
}
|
||||||
|
|
||||||
|
season_url.clear();
|
||||||
|
season_name.clear();
|
||||||
|
|
||||||
|
i = i + j;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr_is(html_data, i, close_select)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "SEASON PARSE ERROR: No selected season found\n";
|
||||||
|
exit(9);
|
||||||
|
}
|
||||||
|
|
||||||
void series::download(const std::string &quality, const std::string &series_directory) {
|
void series::download(const std::string &quality, const std::string &series_directory) {
|
||||||
for (auto& season : seasons) {
|
for (auto& season : seasons) {
|
||||||
season.download(quality, series_directory);
|
season.download(quality, series_directory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // dropout_dl
|
} // dropout_dl
|
|
@ -17,17 +17,17 @@ namespace dropout_dl {
|
||||||
std::string page_data;
|
std::string page_data;
|
||||||
std::vector<season> seasons;
|
std::vector<season> seasons;
|
||||||
|
|
||||||
static std::string get_series_page(const std::string& url, bool verbose = false);
|
|
||||||
|
|
||||||
static std::string get_series_name(const std::string& html_data);
|
static std::string get_series_name(const std::string& html_data);
|
||||||
|
|
||||||
static std::vector<season> get_seasons(const std::string& html_data, const std::vector<std::string>& cookies);
|
static std::vector<season> get_seasons(const std::string& html_data, const std::vector<std::string>& cookies);
|
||||||
|
|
||||||
|
static season get_season(const std::string& url, const std::vector<std::string>& cookies);
|
||||||
|
|
||||||
void download(const std::string& quality, const std::string& series_directory);
|
void download(const std::string& quality, const std::string& series_directory);
|
||||||
|
|
||||||
explicit series(const std::string& url, std::vector<std::string> cookies) {
|
explicit series(const std::string& url, const std::vector<std::string>& cookies) {
|
||||||
this->url = url;
|
this->url = url;
|
||||||
this->page_data = get_series_page(url);
|
this->page_data = get_generic_page(url);
|
||||||
this->name = get_series_name(page_data);
|
this->name = get_series_name(page_data);
|
||||||
this->seasons = get_seasons(page_data, cookies);
|
this->seasons = get_seasons(page_data, cookies);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue