Options: Added Season Option

This allows an entire season to be downloaded at once.
This commit is contained in:
Moss 2022-09-29 14:33:56 -04:00
parent 570793c973
commit cd72083b69
7 changed files with 132 additions and 90 deletions

View File

@ -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`

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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);
} }

View File

@ -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

View File

@ -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);
} }