aboutsummaryrefslogtreecommitdiff
path: root/src/Downloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Downloader.cpp')
-rw-r--r--src/Downloader.cpp279
1 files changed, 205 insertions, 74 deletions
diff --git a/src/Downloader.cpp b/src/Downloader.cpp
index 89d83cb..24f670d 100644
--- a/src/Downloader.cpp
+++ b/src/Downloader.cpp
@@ -4,6 +4,8 @@
#include "../include/Notification.hpp"
#include <unistd.h>
#include <signal.h>
+#include <sys/wait.h>
+#include <fcntl.h>
namespace QuickMedia {
static bool youtube_url_is_live_stream(const std::string &url) {
@@ -112,9 +114,9 @@ namespace QuickMedia {
}
std::lock_guard<std::mutex> lock(content_length_mutex);
- size_t output_file_size = 0;
+ int64_t output_file_size = 0;
file_get_size(output_filepath_tmp, &output_file_size);
- size_t downloaded_size = std::min((int64_t)output_file_size, content_length);
+ int64_t downloaded_size = std::min((int64_t)output_file_size, content_length);
if(content_length == -1) {
progress_text = std::to_string(output_file_size / 1024) + "/Unknown";
@@ -127,7 +129,7 @@ namespace QuickMedia {
}
// TODO: Take into consideration time overflow?
- size_t downloaded_diff = std::max(0lu, downloaded_size - downloaded_since_last_check);
+ int64_t downloaded_diff = std::max((int64_t)0, downloaded_size - downloaded_since_last_check);
download_speed_text = file_size_to_human_readable_string(downloaded_diff) + "/s";
downloaded_since_last_check = downloaded_size;
@@ -279,79 +281,178 @@ namespace QuickMedia {
return download_speed_text;
}
+ static int64_t seek_fn(YoutubeReadProgram *program, int64_t offset) {
+ if(program->read_program.pid != -1) {
+ kill(program->read_program.pid, SIGTERM);
+ int status;
+ waitpid(program->read_program.pid, &status, 0);
+ program->read_program.pid = -1;
+ }
+
+ if(program->read_program.read_fd != -1) {
+ close(program->read_program.read_fd);
+ program->read_program.read_fd = -1;
+ }
+
+ program->offset = offset;
+
+ char range[64];
+ snprintf(range, sizeof(range), "%lld-%lld", (long long)program->offset, (long long)program->offset + 5242870LL);
+ const char *args[] = { "curl",
+ "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive",
+ "--no-buffer",
+ "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "-g", "-s", "-L", "-f",
+ "-r", range, "--", program->url, nullptr };
+ int res = exec_program_pipe(args, &program->read_program);
+ if(res != 0)
+ return -1;
+
+ int flags = fcntl(program->read_program.read_fd, F_GETFL, 0);
+ if(flags != -1)
+ fcntl(program->read_program.read_fd, F_SETFL, flags | O_NONBLOCK);
+
+ return offset;
+ }
+
+ static int64_t read_fn(YoutubeReadProgram *program, char *buf, uint64_t nbytes) {
+ ssize_t bytes_read = read(program->read_program.read_fd, buf, nbytes);
+ if(bytes_read > 0) {
+ program->offset += bytes_read;
+ program->bytes_downloaded += bytes_read;
+ } else if(bytes_read == 0) {
+ // End of current range, progress to next range
+ bytes_read = seek_fn(program, program->offset);
+ if(bytes_read >= 0) {
+ bytes_read = read(program->read_program.read_fd, buf, nbytes);
+ if(bytes_read > 0) {
+ program->offset += bytes_read;
+ program->bytes_downloaded += bytes_read;
+ }
+ }
+ } else if(bytes_read < 0) {
+ if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
+ return 0;
+ }
+ return bytes_read;
+ }
+
+ static void close_fn(YoutubeReadProgram *program) {
+ if(program->read_program.pid != -1) {
+ kill(program->read_program.pid, SIGTERM);
+ int status;
+ waitpid(program->read_program.pid, &status, 0);
+ program->read_program.pid = -1;
+ }
+
+ if(program->read_program.read_fd != -1) {
+ close(program->read_program.read_fd);
+ program->read_program.read_fd = -1;
+ }
+
+ if(program->url) {
+ free(program->url);
+ program->url = nullptr;
+ }
+ }
+
+ static YoutubeReadProgram* open_fn(const char *uri) {
+ YoutubeReadProgram *program = new YoutubeReadProgram();
+ program->read_program.read_fd = -1;
+ program->read_program.pid = -1;
+ program->url = strdup(uri);
+ program->offset = 0;
+ if(seek_fn(program, program->offset) < 0) {
+ delete program;
+ return nullptr;
+ }
+ return program;
+ }
+
YoutubeDownloader::YoutubeDownloader(const MediaMetadata &video_metadata, const MediaMetadata &audio_metadata, const std::string &output_filepath) :
Downloader("", output_filepath),
video_metadata(video_metadata),
audio_metadata(audio_metadata)
{
for(int i = 0; i < 2; ++i)
- downloaders[i] = nullptr;
+ program[i] = nullptr;
+ }
+
+ YoutubeDownloader::~YoutubeDownloader() {
+ for(int i = 0; i < 2; ++i) {
+ if(program[i]) {
+ close_fn(program[i]);
+ delete program[i];
+ program[i] = nullptr;
+ }
+ }
}
bool YoutubeDownloader::start() {
- struct MediaProxyMetadata {
- std::unique_ptr<YoutubeMediaProxy> *media_proxy;
+ struct MediaMetadataInfo {
MediaMetadata *media_metadata;
- std::unique_ptr<CurlDownloader> *downloader;
std::string *output_filepath;
bool tmp_audio_file;
};
const bool has_video = !video_metadata.url.empty();
- MediaProxyMetadata media_proxies[2] = {
- { &youtube_video_media_proxy, &video_metadata, &downloaders[0], &video_output_filepath, false },
- { &youtube_audio_media_proxy, &audio_metadata, &downloaders[1], &audio_output_filepath, has_video }
+ MediaMetadataInfo media_info[2] = {
+ { &video_metadata, &video_output_filepath, false },
+ { &audio_metadata, &audio_output_filepath, has_video }
};
- int num_proxied_media = 0;
+ int num_streams = 0;
for(int i = 0; i < 2; ++i) {
- media_proxies[i].output_filepath->clear();
- if(media_proxies[i].media_metadata->url.empty() || youtube_url_is_live_stream(media_proxies[i].media_metadata->url))
+ media_info[i].output_filepath->clear();
+ if(media_info[i].media_metadata->url.empty() || youtube_url_is_live_stream(media_info[i].media_metadata->url))
continue;
- *media_proxies[i].media_proxy = std::make_unique<YoutubeStaticMediaProxy>();
- if(!(*media_proxies[i].media_proxy)->start(media_proxies[i].media_metadata->url, media_proxies[i].media_metadata->content_length)) {
- fprintf(stderr, "Failed to load start youtube media proxy\n");
- return false;
- }
+ if(media_info[i].tmp_audio_file)
+ *media_info[i].output_filepath = output_filepath + ".audio";
+ else
+ *media_info[i].output_filepath = output_filepath;
- std::string media_proxy_addr;
- if(!(*media_proxies[i].media_proxy)->get_address(media_proxy_addr)) {
- fprintf(stderr, "Failed to load start youtube media proxy\n");
+ program[i] = open_fn(media_info[i].media_metadata->url.c_str());
+ if(!program[i]) {
+ fprintf(stderr, "Error: open failed for %s\n", media_info[i].media_metadata->url.c_str());
return false;
}
+ program[i]->content_length = media_info[i].media_metadata->content_length;
+ program[i]->output_filepath = *media_info[i].output_filepath;
+ program[i]->output_filepath_tmp = program[i]->output_filepath + ".tmp";
- if(media_proxies[i].tmp_audio_file)
- *media_proxies[i].output_filepath = output_filepath + ".audio";
- else
- *media_proxies[i].output_filepath = output_filepath;
+ program[i]->progress_text = "0 bytes/Unknown";
+ program[i]->download_speed_text = "Unknown/s";
- remove(media_proxies[i].output_filepath->c_str());
- fprintf(stderr, "Downloading %s to %s\n", media_proxy_addr.c_str(), media_proxies[i].output_filepath->c_str());
- *media_proxies[i].downloader = std::make_unique<CurlDownloader>(media_proxy_addr, *media_proxies[i].output_filepath, media_proxies[i].media_metadata->content_length);
- *media_proxies[i].output_filepath = (*media_proxies[i].downloader)->get_output_filepath();
- if(!(*media_proxies[i].downloader)->start())
- return false;
- ++num_proxied_media;
- }
+ remove(program[i]->output_filepath.c_str());
+ remove(program[i]->output_filepath_tmp.c_str());
- if(num_proxied_media == 0) {
- return false;
- }
+ downloader_tasks[i] = AsyncTask<bool>([this, i]() {
+ FILE *f = fopen(program[i]->output_filepath_tmp.c_str(), "wb");
+ if(!f)
+ return false;
+
+ char buffer[8192];
+ while(!program_is_dead_in_current_thread()) {
+ if(program[i]->offset >= program[i]->content_length)
+ break;
- downloader_task = AsyncTask<void>([this]() {
- // TODO: Poll instead of sleep
- while(!program_is_dead_in_current_thread()) {
- if(youtube_video_media_proxy)
- youtube_video_media_proxy->update();
+ int64_t bytes_read = read_fn(program[i], buffer, sizeof(buffer));
+ if(bytes_read > 0)
+ fwrite(buffer, 1, bytes_read, f);
+ }
- if(youtube_audio_media_proxy)
- youtube_audio_media_proxy->update();
+ fclose(f);
+ close_fn(program[i]);
+ return true;
+ });
- usleep(1000);
- }
- });
+ ++num_streams;
+ }
+
+ if(num_streams == 0)
+ return false;
return true;
}
@@ -362,27 +463,22 @@ namespace QuickMedia {
}
bool YoutubeDownloader::stop(bool download_completed) {
+ bool success = true;
for(int i = 0; i < 2; ++i) {
- if(downloaders[i]) {
- downloaders[i]->stop(download_completed);
- downloaders[i].reset();
+ if(program[i]) {
+ downloader_tasks[i].cancel();
+ if(downloader_tasks[i].ready() && !downloader_tasks[i].get())
+ success = false;
+ if(rename_atomic(program[i]->output_filepath_tmp.c_str(), program[i]->output_filepath.c_str()) != 0)
+ success = false;
+ close_fn(program[i]);
}
}
- if(youtube_video_media_proxy)
- youtube_video_media_proxy->stop();
-
- if(youtube_audio_media_proxy)
- youtube_audio_media_proxy->stop();
-
- downloader_task.cancel();
- youtube_video_media_proxy.reset();
- youtube_audio_media_proxy.reset();
-
- if(!download_completed)
+ if(!download_completed || !success)
return false;
- bool success = true;
+ success = true;
if(!video_metadata.url.empty() && !audio_metadata.url.empty()) {
std::string tmp_file = video_output_filepath + ".tmp.mkv";
if(ffmpeg_merge_audio_and_video(video_output_filepath, audio_output_filepath, tmp_file)) {
@@ -406,9 +502,9 @@ namespace QuickMedia {
int num_downloaders = 0;
for(int i = 0; i < 2; ++i) {
- if(downloaders[i]) {
+ if(program[i]) {
++num_downloaders;
- DownloadUpdateStatus update_status = downloaders[i]->update();
+ DownloadUpdateStatus update_status = update(i);
switch(update_status) {
case DownloadUpdateStatus::DOWNLOADING:
++num_downloading;
@@ -432,33 +528,68 @@ namespace QuickMedia {
}
float YoutubeDownloader::get_progress() {
- return get_min_progress_downloader()->get_progress();
+ YoutubeReadProgram *min_program = get_min_progress_downloader();
+ return min_program->progress;
}
std::string YoutubeDownloader::get_progress_text() {
- return get_min_progress_downloader()->get_progress_text();
+ YoutubeReadProgram *min_program = get_min_progress_downloader();
+ return min_program->progress_text;
}
std::string YoutubeDownloader::get_download_speed_text() {
- return get_min_progress_downloader()->get_download_speed_text();
+ YoutubeReadProgram *min_program = get_min_progress_downloader();
+ return min_program->download_speed_text;
}
- CurlDownloader* YoutubeDownloader::get_min_progress_downloader() {
+ YoutubeReadProgram* YoutubeDownloader::get_min_progress_downloader() {
int min_index = -1;
float progress = 999999.0f;
for(int i = 0; i < 2; ++i) {
- if(downloaders[i]) {
- float item_progress = downloaders[i]->get_progress();
- if(item_progress < progress) {
+ if(program[i]) {
+ if(program[i]->progress < progress) {
min_index = i;
- progress = item_progress;
+ progress = program[i]->progress;
}
}
}
- //assert(min_index != -1);
- // Shouldn't happen
if(min_index == -1)
min_index = 0;
- return downloaders[min_index].get();
+ return program[min_index];
+ }
+
+ DownloadUpdateStatus YoutubeDownloader::update(size_t program_index) {
+ bool finished = false;
+ YoutubeReadProgram *youtube_program = program[program_index];
+ auto &downloader_task = downloader_tasks[program_index];
+
+ if(youtube_program->finished)
+ return DownloadUpdateStatus::FINISHED;
+
+ if(downloader_task.ready()) {
+ if(downloader_task.get()) {
+ finished = true;
+ youtube_program->finished = true;
+ } else {
+ return DownloadUpdateStatus::ERROR;
+ }
+ }
+
+ int64_t output_file_size = 0;
+ file_get_size(youtube_program->output_filepath_tmp, &output_file_size);
+ int64_t downloaded_size = std::min((int64_t)output_file_size, youtube_program->content_length);
+
+ int64_t percentage = 0;
+ if(output_file_size > 0)
+ percentage = (double)downloaded_size / (double)youtube_program->content_length * 100.0;
+ youtube_program->progress = (double)percentage / 100.0;
+ youtube_program->progress_text = file_size_to_human_readable_string(downloaded_size) + "/" + file_size_to_human_readable_string(youtube_program->content_length) + " (" + std::to_string(percentage) + "%)";
+
+ // TODO: Take into consideration time overflow?
+ int64_t downloaded_diff = std::max((int64_t)0, downloaded_size - youtube_program->downloaded_since_last_check);
+ youtube_program->download_speed_text = file_size_to_human_readable_string(downloaded_diff) + "/s";
+ youtube_program->downloaded_since_last_check = downloaded_size;
+
+ return finished ? DownloadUpdateStatus::FINISHED : DownloadUpdateStatus::DOWNLOADING;
}
}