diff options
Diffstat (limited to 'src/Downloader.cpp')
-rw-r--r-- | src/Downloader.cpp | 279 |
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; } } |