diff --git a/src/m3u8download.c b/src/m3u8download.c new file mode 100644 index 0000000..6cfdff2 --- /dev/null +++ b/src/m3u8download.c @@ -0,0 +1,567 @@ +#include +#include + +#include + +#include "m3u8.h" +#include "m3u8stream.h" +#include "m3u8errors.h" +#include "fstream.h" +#include "m3u8httpclient.h" +#include "pathsep.h" +#include "biggestint.h" +#include "m3u8download.h" +#include "m3u8utils.h" +#include "sutils.h" + +struct M3U8Download { + char* destination; + struct FStream* stream; + CURL* curl; + struct M3U8StreamItem* item; + size_t retries; + struct M3U8HTTPClientError error; +}; + +int m3u8download_retryable(CURL* const curl, const CURLcode code) { + + switch (code) { + case CURLE_HTTP_RETURNED_ERROR: { + long status_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code); + + if (status_code == 408 || status_code == 429 || status_code == 500 || + status_code == 502 || status_code == 503 || status_code == 504) { + return 1; + } + + break; + } + case CURLE_SEND_ERROR: + case CURLE_OPERATION_TIMEDOUT: + return 1; + default: + break; + } + + return 0; + +} + +void m3u8download_free(struct M3U8Download* const download) { + + download->destination = NULL; + + curl_easy_cleanup(download->curl); + download->curl = NULL; + + fstream_close(download->stream); + download->stream = NULL; + + m3u8httpclient_errfree(&download->error); + +} + +struct M3U8DownloadQeue { + size_t offset; + size_t size; + struct M3U8Download* items; +}; + +void m3u8dq_free(struct M3U8DownloadQeue* const queue) { + + size_t index = 0; + + for (index = 0; index < queue->offset; index++) { + struct M3U8Download* const download = &queue->items[index]; + m3u8download_free(download); + } + + queue->offset = 0; + queue->size = 0; + + free(queue->items); + queue->items = NULL; + +} + +size_t curl_write_file_cb(char* ptr, size_t size, size_t nmemb, void* userdata) { + + struct FStream* const stream = (struct FStream*) userdata; + const size_t csize = size * nmemb; + + if (fstream_write(stream, ptr, csize) == -1) { + return 0; + } + + return csize; + +} + +static int m3u8download_addqeue( + struct M3U8DownloadQeue* const qeue, + struct M3U8Stream* const root, + struct M3U8Stream* const resource, + const char* const temporary_directory, + const char* const uri, + const struct M3U8ByteRange byterange, + struct M3U8StreamItem* const item +) { + + int err = 0; + int wsize = 0; + size_t size = 0; + + CURLcode code = CURLE_OK; + + char* resolved_uri = NULL; + char* range = NULL; + + struct M3U8Download download = {0}; + + download.item = item; + + size = ( + strlen(temporary_directory) + strlen(PATHSEP) + uintlen((biguint_t) uri) + 1 + 3 + 1 + ); + + download.destination = malloc(size); + + if (download.destination == NULL) { + err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; + goto end; + } + + strcpy(download.destination, temporary_directory); + strcat(download.destination, PATHSEP); + + wsize = snprintf(download.destination + strlen(download.destination), 4096, "%"FORMAT_BIGGEST_INT_T, (biguint_t) uri); + + if (wsize < 1) { + err = M3U8ERR_PRINTF_WRITE_FAILURE; + goto end; + } + + strcat(download.destination, ".bin"); + + switch (resource->playlist.uri.type) { + case M3U8_BASE_URI_TYPE_URL: + err = m3u8uri_resolve_url(resource->playlist.uri.uri, uri, &resolved_uri); + break; + case M3U8_BASE_URI_TYPE_LOCAL_FILE: + err = m3u8uri_resolve_path(resource->playlist.uri.uri, uri, &resolved_uri); + break; + default: + err = M3U8ERR_LOAD_UNSUPPORTED_URI; + break; + } + + if (err != M3U8ERR_SUCCESS) { + goto end; + } + + /* + Prepend the scheme "file://" for local files, otherwise curl will fail to parse it. + */ + if (resource->playlist.uri.type == M3U8_BASE_URI_TYPE_LOCAL_FILE) { + char* new_resolved_uri = malloc(7 + strlen(resolved_uri) + 1); + + if (new_resolved_uri == NULL) { + err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; + goto end; + } + + strcpy(new_resolved_uri, "file://"); + strcat(new_resolved_uri, resolved_uri); + + free(resolved_uri); + resolved_uri = new_resolved_uri; + } + + download.curl = curl_easy_duphandle(root->playlist.client.curl); + + if (download.curl == NULL) { + err = M3U8ERR_CURL_INIT_FAILURE; + goto end; + } + + code = curl_easy_setopt(download.curl, CURLOPT_VERBOSE, 0L); + + if (code != CURLE_OK) { + err = M3U8ERR_CURL_SETOPT_FAILURE; + goto end; + } + + download.error.message = malloc(CURL_ERROR_SIZE); + + if (download.error.message == NULL) { + err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; + goto end; + } + + code = curl_easy_setopt(download.curl, CURLOPT_ERRORBUFFER, download.error.message); + + if (code != CURLE_OK) { + err = M3U8ERR_CURL_SETOPT_FAILURE; + goto end; + } + + code = curl_easy_setopt(download.curl, CURLOPT_REFERER, resource->playlist.uri.uri); + + if (code != CURLE_OK) { + err = M3U8ERR_CURL_SETOPT_FAILURE; + goto end; + } + + code = curl_easy_setopt(download.curl, CURLOPT_URL, resolved_uri); + + if (code != CURLE_OK) { + err = M3U8ERR_CURL_SETOPT_FAILURE; + goto end; + } + + code = curl_easy_setopt(download.curl, CURLOPT_WRITEFUNCTION, curl_write_file_cb); + + if (code != CURLE_OK) { + err = M3U8ERR_CURL_SETOPT_FAILURE; + goto end; + } + + download.stream = fstream_open(download.destination, FSTREAM_WRITE); + + if (download.stream == NULL) { + err = M3U8ERR_FSTREAM_OPEN_FAILURE; + goto end; + } + + if (fstream_lock(download.stream) == -1) { + err = M3U8ERR_FSTREAM_LOCK_FAILURE; + goto end; + } + + code = curl_easy_setopt(download.curl, CURLOPT_WRITEDATA, download.stream); + + if (code != CURLE_OK) { + err = M3U8ERR_CURL_SETOPT_FAILURE; + goto end; + } + + if (byterange.length > 0) { + const biguint_t start = byterange.offset; + const biguint_t end = (byterange.length + start) - 1; + + size = uintlen(start) + 1 + uintlen(end) + 1; + range = malloc(size); + + if (range == NULL) { + err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; + goto end; + } + + wsize = snprintf( + range, + size, + "%"FORMAT_BIGGEST_UINT_T"-%"FORMAT_BIGGEST_UINT_T, + start, + end + ); + + if (wsize < 1) { + err = M3U8ERR_PRINTF_WRITE_FAILURE; + goto end; + } + + code = curl_easy_setopt(download.curl, CURLOPT_RANGE, range); + + if (code != CURLE_OK) { + err = M3U8ERR_CURL_SETOPT_FAILURE; + goto end; + } + } + + qeue->items[qeue->offset++] = download; + + end:; + + if (err != M3U8ERR_SUCCESS) { + free(download.destination); + m3u8download_free(&download); + } + + free(range); + free(resolved_uri); + + return err; + +} + +static int m3u8download_pollqeue( + struct M3U8DownloadQeue* const qeue, + struct M3U8Stream* const root, + const struct M3U8DownloadOptions* const options +) { + + int err = M3U8ERR_SUCCESS; + CURLMcode code = CURLM_OK; + + size_t index = 0; + size_t current = 0; + + int running = 1; + CURLMsg* msg = NULL; + int left = 0; + + struct M3U8Download* download = NULL; + + CURLM* curl_multi = root->playlist.multi_client.curl_multi; + + for (index = 0; index < qeue->offset; index++) { + struct M3U8Download* const download = &qeue->items[index]; + code = curl_multi_add_handle(curl_multi, download->curl); + + if (code != CURLM_OK) { + err = M3U8ERR_CURLM_ADD_FAILURE; + goto end; + } + } + + (*options->progress_callback)(qeue->offset, current); + + while (running) { + CURLMcode mc = curl_multi_perform(curl_multi, &running); + + if (running) { + mc = curl_multi_poll(curl_multi, NULL, 0, 1000, NULL); + } + + while ((msg = curl_multi_info_read(curl_multi, &left))) { + if (msg->msg != CURLMSG_DONE) { + continue; + } + + download = NULL; + + for (index = 0; index < qeue->offset; index++) { + struct M3U8Download* const subdownload = &qeue->items[index]; + + if (subdownload->curl == msg->easy_handle) { + download = subdownload; + break; + } + } + + code = curl_multi_remove_handle(curl_multi, msg->easy_handle); + + if (code != CURLM_OK) { + err = M3U8ERR_CURLM_REMOVE_FAILURE; + goto end; + } + + if (msg->data.result != CURLE_OK) { + const int status = fstream_seek(download->stream, 0, FSTREAM_SEEK_BEGIN); + const int retryable = m3u8download_retryable(msg->easy_handle, msg->data.result); + + if (status == -1) { + err = M3U8ERR_FSTREAM_SEEK_FAILURE; + goto end; + } + + if (download->retries++ > options->retry || !retryable) { + struct M3U8HTTPClientError* const error = m3u8httpclient_geterror(&root->playlist.client); + + strcpy(error->message, download->error.message); + error->code = msg->data.result; + + if (root->playlist.client.error.message[0] == '\0') { + const char* const message = curl_easy_strerror(error->code); + strcpy(error->message, message); + } + + err = M3U8ERR_CURL_REQUEST_FAILURE; + goto end; + } + + curl_multi_add_handle(curl_multi, msg->easy_handle); + } else { + current++; + (*options->progress_callback)(qeue->offset, current); + } + } + + if (mc) { + break; + } + } + + end:; + + return err; + +} + + +int m3u8stream_download( + struct M3U8Stream* const root, + struct M3U8Stream* const resource, + const struct M3U8DownloadOptions* const options +) { + + size_t index = 0; + int status = 0; + + int err = M3U8ERR_SUCCESS; + + struct M3U8Tag* tag = NULL; + + struct M3U8DownloadQeue qeue = {0}; + + err = m3u8mhttpclient_init(&root->playlist.multi_client, options->concurrency); + + if (err != M3U8ERR_SUCCESS) { + goto end; + } + + qeue.size = sizeof(*qeue.items) * resource->offset; + qeue.items = malloc(qeue.size); + + if (qeue.items == NULL) { + err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; + goto end; + } + + for (index = 0; index < resource->offset; index++) { + struct M3U8StreamItem* const item = &resource->items[index]; + + switch (item->type) { + case M3U8_STREAM_SEGMENT: { + struct M3U8Segment* const segment = ((struct M3U8Segment*) item->item); + + if (segment->key.uri != NULL) { + err = m3u8download_addqeue( + &qeue, + root, + resource, + options->temporary_directory, + segment->key.uri, + (struct M3U8ByteRange) {}, + item + ); + + if (err != M3U8ERR_SUCCESS) { + goto end; + } + } + + err = m3u8download_addqeue( + &qeue, + root, + resource, + options->temporary_directory, + segment->uri, + segment->byterange, + item + ); + + if (err != M3U8ERR_SUCCESS) { + goto end; + } + + break; + } + case M3U8_STREAM_MAP: { + struct M3U8Map* const map = ((struct M3U8Map*) item->item); + + err = m3u8download_addqeue( + &qeue, + root, + resource, + options->temporary_directory, + map->uri, + map->byterange, + item + ); + + if (err != M3U8ERR_SUCCESS) { + goto end; + } + + break; + } + default: { + break; + } + } + } + + err = m3u8download_pollqeue(&qeue, root, options); + + if (err != M3U8ERR_SUCCESS) { + goto end; + } + + for (index = 0; index < qeue.offset; index++) { + struct M3U8Download* download = &qeue.items[index]; + struct M3U8StreamItem* item = download->item; + struct M3U8Attribute* attribute = NULL; + + switch (item->type) { + case M3U8_STREAM_SEGMENT: { + struct M3U8Segment* segment = item->item; + + if (segment->key.uri != NULL && segment->key.uri != download->destination) { + attribute = m3u8tag_igetattr(segment->key.tag, M3U8_ATTRIBUTE_URI); + + free(attribute->value); + attribute->value = download->destination; + + index++; + + download = &qeue.items[index]; + item = download->item; + + segment = item->item; + + free(segment->tag->uri); + segment->tag->uri = download->destination; + } else { + free(segment->tag->uri); + segment->tag->uri = download->destination; + } + + break; + } + case M3U8_STREAM_MAP: { + struct M3U8Map* const map = ((struct M3U8Map*) item->item); + attribute = m3u8tag_igetattr(map->tag, M3U8_ATTRIBUTE_URI); + + free(attribute->value); + attribute->value = download->destination; + + break; + } + default: { + break; + } + } + } + + status = 0; + + /* Delete all the #EXT-X-BYTERANGE tags within the playlist */ + do { + status = m3u8playlist_ideltag(&resource->playlist, M3U8_TAG_EXT_X_BYTERANGE); + } while (status); + + tag = m3u8playlist_igettag(&resource->playlist, M3U8_TAG_EXT_X_MAP); + + if (tag != NULL) { + m3u8tag_idelattr(tag, M3U8_ATTRIBUTE_BYTERANGE); + } + + end:; + + m3u8dq_free(&qeue); + + return err; + +} diff --git a/src/m3u8download.h b/src/m3u8download.h new file mode 100644 index 0000000..bb63483 --- /dev/null +++ b/src/m3u8download.h @@ -0,0 +1,21 @@ +#if !defined(M3U8DOWNLOAD_H) +#define M3U8DOWNLOAD_H + +#include + +#include "m3u8stream.h" + +struct M3U8DownloadOptions { + size_t concurrency; + size_t retry; + char* temporary_directory; + void (*progress_callback)(const size_t total, const size_t current); +}; + +int m3u8stream_download( + struct M3U8Stream* const root, + struct M3U8Stream* const resource, + const struct M3U8DownloadOptions* const options +); + +#endif diff --git a/src/m3u8stream.c b/src/m3u8stream.c index 9e97319..db95f5f 100644 --- a/src/m3u8stream.c +++ b/src/m3u8stream.c @@ -4,13 +4,11 @@ #include "m3u8.h" #include "m3u8stream.h" -#include "m3u8utils.h" #include "m3u8errors.h" #include "m3u8parser.h" #include "m3u8sizeof.h" #include "biggestint.h" -#include "pathsep.h" -#include "sutils.h" +#include "m3u8utils.h" #define M3U8_MAX_KFV_VALUE_LEN (8 + 1) @@ -2233,568 +2231,19 @@ int m3u8stream_load( int m3u8stream_load_subresource( const struct M3U8Stream* const root, - struct M3U8Stream* const subresource, + struct M3U8Stream* const resource, const char* const something ) { int err = 0; - err = m3u8playlist_load_subresource(&root->playlist, &subresource->playlist, something); + err = m3u8playlist_load_subresource(&root->playlist, &resource->playlist, something); if (err != M3U8ERR_SUCCESS) { return err; } - err = m3u8stream_parse(subresource); - - return err; - -} - -struct M3U8Download { - char* destination; - struct FStream* stream; - CURL* curl; - struct M3U8StreamItem* item; - size_t retries; - struct M3U8HTTPClientError error; -}; - -int m3u8download_retryable(CURL* const curl, const CURLcode code) { - - switch (code) { - case CURLE_HTTP_RETURNED_ERROR: { - long status_code = 0; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code); - - if (status_code == 408 || status_code == 429 || status_code == 500 || - status_code == 502 || status_code == 503 || status_code == 504) { - return 1; - } - - break; - } - case CURLE_SEND_ERROR: - case CURLE_OPERATION_TIMEDOUT: - return 1; - default: - break; - } - - return 0; - -} - -void m3u8download_free(struct M3U8Download* const download) { - - download->destination = NULL; - - curl_easy_cleanup(download->curl); - download->curl = NULL; - - fstream_close(download->stream); - download->stream = NULL; - - m3u8httpclient_errfree(&download->error); - -} - -struct M3U8DownloadQeue { - size_t offset; - size_t size; - struct M3U8Download* items; -}; - -void m3u8dq_free(struct M3U8DownloadQeue* const queue) { - - size_t index = 0; - - for (index = 0; index < queue->offset; index++) { - struct M3U8Download* const download = &queue->items[index]; - m3u8download_free(download); - } - - queue->offset = 0; - queue->size = 0; - - free(queue->items); - queue->items = NULL; - -} - -size_t curl_write_file_cb(char* ptr, size_t size, size_t nmemb, void* userdata) { - - struct FStream* const stream = (struct FStream*) userdata; - const size_t csize = size * nmemb; - - if (fstream_write(stream, ptr, csize) == -1) { - return 0; - } - - return csize; - -} - -static int m3u8download_addqeue( - struct M3U8DownloadQeue* const qeue, - struct M3U8Stream* const root, - struct M3U8Stream* const subresource, - const char* const temporary_directory, - const char* const uri, - const struct M3U8ByteRange byterange, - struct M3U8StreamItem* const item -) { - - int err = 0; - int wsize = 0; - size_t size = 0; - - CURLcode code = CURLE_OK; - - char* resolved_uri = NULL; - char* range = NULL; - - struct M3U8Download download = {0}; - - download.item = item; - - size = ( - strlen(temporary_directory) + strlen(PATHSEP) + uintlen((biguint_t) uri) + 1 + 3 + 1 - ); - - download.destination = malloc(size); - - if (download.destination == NULL) { - err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; - goto end; - } - - strcpy(download.destination, temporary_directory); - strcat(download.destination, PATHSEP); - - wsize = snprintf(download.destination + strlen(download.destination), 4096, "%"FORMAT_BIGGEST_INT_T, (biguint_t) uri); - - if (wsize < 1) { - err = M3U8ERR_PRINTF_WRITE_FAILURE; - goto end; - } - - strcat(download.destination, ".bin"); - - switch (subresource->playlist.uri.type) { - case M3U8_BASE_URI_TYPE_URL: - err = m3u8uri_resolve_url(subresource->playlist.uri.uri, uri, &resolved_uri); - break; - case M3U8_BASE_URI_TYPE_LOCAL_FILE: - err = m3u8uri_resolve_path(subresource->playlist.uri.uri, uri, &resolved_uri); - break; - default: - err = M3U8ERR_LOAD_UNSUPPORTED_URI; - break; - } - - if (err != M3U8ERR_SUCCESS) { - goto end; - } - - /* - Prepend the scheme "file://" for local files, otherwise curl will fail to parse it. - */ - if (subresource->playlist.uri.type == M3U8_BASE_URI_TYPE_LOCAL_FILE) { - char* new_resolved_uri = malloc(7 + strlen(resolved_uri) + 1); - - if (new_resolved_uri == NULL) { - err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; - goto end; - } - - strcpy(new_resolved_uri, "file://"); - strcat(new_resolved_uri, resolved_uri); - - free(resolved_uri); - resolved_uri = new_resolved_uri; - } - - download.curl = curl_easy_duphandle(root->playlist.client.curl); - - if (download.curl == NULL) { - err = M3U8ERR_CURL_INIT_FAILURE; - goto end; - } - - code = curl_easy_setopt(download.curl, CURLOPT_VERBOSE, 0L); - - if (code != CURLE_OK) { - err = M3U8ERR_CURL_SETOPT_FAILURE; - goto end; - } - - download.error.message = malloc(CURL_ERROR_SIZE); - - if (download.error.message == NULL) { - err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; - goto end; - } - - code = curl_easy_setopt(download.curl, CURLOPT_ERRORBUFFER, download.error.message); - - if (code != CURLE_OK) { - err = M3U8ERR_CURL_SETOPT_FAILURE; - goto end; - } - - code = curl_easy_setopt(download.curl, CURLOPT_REFERER, subresource->playlist.uri.uri); - - if (code != CURLE_OK) { - err = M3U8ERR_CURL_SETOPT_FAILURE; - goto end; - } - - code = curl_easy_setopt(download.curl, CURLOPT_URL, resolved_uri); - - if (code != CURLE_OK) { - err = M3U8ERR_CURL_SETOPT_FAILURE; - goto end; - } - - code = curl_easy_setopt(download.curl, CURLOPT_WRITEFUNCTION, curl_write_file_cb); - - if (code != CURLE_OK) { - err = M3U8ERR_CURL_SETOPT_FAILURE; - goto end; - } - - download.stream = fstream_open(download.destination, FSTREAM_WRITE); - - if (download.stream == NULL) { - err = M3U8ERR_FSTREAM_OPEN_FAILURE; - goto end; - } - - if (fstream_lock(download.stream) == -1) { - err = M3U8ERR_FSTREAM_LOCK_FAILURE; - goto end; - } - - code = curl_easy_setopt(download.curl, CURLOPT_WRITEDATA, download.stream); - - if (code != CURLE_OK) { - err = M3U8ERR_CURL_SETOPT_FAILURE; - goto end; - } - - if (byterange.length > 0) { - const biguint_t start = byterange.offset; - const biguint_t end = (byterange.length + start) - 1; - - size = uintlen(start) + 1 + uintlen(end) + 1; - range = malloc(size); - - if (range == NULL) { - err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; - goto end; - } - - wsize = snprintf( - range, - size, - "%"FORMAT_BIGGEST_UINT_T"-%"FORMAT_BIGGEST_UINT_T, - start, - end - ); - - if (wsize < 1) { - err = M3U8ERR_PRINTF_WRITE_FAILURE; - goto end; - } - - code = curl_easy_setopt(download.curl, CURLOPT_RANGE, range); - - if (code != CURLE_OK) { - err = M3U8ERR_CURL_SETOPT_FAILURE; - goto end; - } - } - - qeue->items[qeue->offset++] = download; - - end:; - - if (err != M3U8ERR_SUCCESS) { - free(download.destination); - m3u8download_free(&download); - } - - free(range); - free(resolved_uri); - - return err; - -} - -static int m3u8download_pollqeue( - struct M3U8DownloadQeue* const qeue, - struct M3U8Stream* const root, - const struct M3U8DownloadOptions* const options -) { - - int err = M3U8ERR_SUCCESS; - CURLMcode code = CURLM_OK; - - size_t index = 0; - size_t current = 0; - - int still_running = 1; - - CURLM* curl_multi = root->playlist.multi_client.curl_multi; - - for (index = 0; index < qeue->offset; index++) { - struct M3U8Download* const download = &qeue->items[index]; - code = curl_multi_add_handle(curl_multi, download->curl); - - if (code != CURLM_OK) { - err = M3U8ERR_CURLM_ADD_FAILURE; - goto end; - } - } - - (*options->progress_callback)(qeue->offset, current); - - while (still_running) { - CURLMcode mc = curl_multi_perform(curl_multi, &still_running); - - if (still_running) { - mc = curl_multi_poll(curl_multi, NULL, 0, 1000, NULL); - } - - CURLMsg* msg = NULL; - int msgs_left = 0; - - while ((msg = curl_multi_info_read(curl_multi, &msgs_left))) { - if (msg->msg != CURLMSG_DONE) { - continue; - } - - struct M3U8Download* download = NULL; - - for (index = 0; index < qeue->offset; index++) { - struct M3U8Download* const subdownload = &qeue->items[index]; - - if (subdownload->curl == msg->easy_handle) { - download = subdownload; - break; - } - } - - code = curl_multi_remove_handle(curl_multi, msg->easy_handle); - - if (code != CURLM_OK) { - err = M3U8ERR_CURLM_REMOVE_FAILURE; - goto end; - } - - if (msg->data.result != CURLE_OK) { - const int status = fstream_seek(download->stream, 0, FSTREAM_SEEK_BEGIN); - const int retryable = m3u8download_retryable(msg->easy_handle, msg->data.result); - - if (status == -1) { - err = M3U8ERR_FSTREAM_SEEK_FAILURE; - goto end; - } - - if (download->retries++ > options->retry || !retryable) { - strcpy(root->playlist.client.error.message, download->error.message); - root->playlist.client.error.code = msg->data.result; - - if (root->playlist.client.error.message[0] == '\0') { - const char* const message = curl_easy_strerror(root->playlist.client.error.code); - strcpy(root->playlist.client.error.message, message); - } - - err = M3U8ERR_CURL_REQUEST_FAILURE; - goto end; - } - - curl_multi_add_handle(curl_multi, msg->easy_handle); - } else { - current++; - (*options->progress_callback)(qeue->offset, current); - } - } - - if (mc) { - break; - } - } - - end:; - - return err; - -} - - -int m3u8stream_download( - struct M3U8Stream* const root, - struct M3U8Stream* const subresource, - const struct M3U8DownloadOptions* const options -) { - - size_t index = 0; - int status = 0; - - int err = M3U8ERR_SUCCESS; - - struct M3U8Tag* tag = NULL; - - struct M3U8DownloadQeue qeue = {0}; - - err = m3u8mhttpclient_init(&root->playlist.multi_client, options->concurrency); - - if (err != M3U8ERR_SUCCESS) { - goto end; - } - - qeue.size = sizeof(*qeue.items) * subresource->offset; - qeue.items = malloc(qeue.size); - - if (qeue.items == NULL) { - err = M3U8ERR_MEMORY_ALLOCATE_FAILURE; - goto end; - } - - for (index = 0; index < subresource->offset; index++) { - struct M3U8StreamItem* const item = &subresource->items[index]; - - switch (item->type) { - case M3U8_STREAM_SEGMENT: { - struct M3U8Segment* const segment = ((struct M3U8Segment*) item->item); - - if (segment->key.uri != NULL) { - err = m3u8download_addqeue( - &qeue, - root, - subresource, - options->temporary_directory, - segment->key.uri, - (struct M3U8ByteRange) {}, - item - ); - - if (err != M3U8ERR_SUCCESS) { - goto end; - } - } - - err = m3u8download_addqeue( - &qeue, - root, - subresource, - options->temporary_directory, - segment->uri, - segment->byterange, - item - ); - - if (err != M3U8ERR_SUCCESS) { - goto end; - } - - break; - } - case M3U8_STREAM_MAP: { - struct M3U8Map* const map = ((struct M3U8Map*) item->item); - - err = m3u8download_addqeue( - &qeue, - root, - subresource, - options->temporary_directory, - map->uri, - map->byterange, - item - ); - - if (err != M3U8ERR_SUCCESS) { - goto end; - } - - break; - } - default: { - break; - } - } - } - - err = m3u8download_pollqeue(&qeue, root, options); - - if (err != M3U8ERR_SUCCESS) { - goto end; - } - - for (index = 0; index < qeue.offset; index++) { - struct M3U8Download* download = &qeue.items[index]; - struct M3U8StreamItem* item = download->item; - struct M3U8Attribute* attribute = NULL; - - switch (item->type) { - case M3U8_STREAM_SEGMENT: { - struct M3U8Segment* segment = item->item; - - if (segment->key.uri != NULL && segment->key.uri != download->destination) { - attribute = m3u8tag_igetattr(segment->key.tag, M3U8_ATTRIBUTE_URI); - - free(attribute->value); - attribute->value = download->destination; - - index++; - - download = &qeue.items[index]; - item = download->item; - - segment = item->item; - - free(segment->tag->uri); - segment->tag->uri = download->destination; - } else { - free(segment->tag->uri); - segment->tag->uri = download->destination; - } - - break; - } - case M3U8_STREAM_MAP: { - struct M3U8Map* const map = ((struct M3U8Map*) item->item); - attribute = m3u8tag_igetattr(map->tag, M3U8_ATTRIBUTE_URI); - - free(attribute->value); - attribute->value = download->destination; - - break; - } - default: { - break; - } - } - } - - status = 0; - - /* Delete all the #EXT-X-BYTERANGE tags within the playlist */ - do { - status = m3u8playlist_ideltag(&subresource->playlist, M3U8_TAG_EXT_X_BYTERANGE); - } while (status); - - tag = m3u8playlist_igettag(&subresource->playlist, M3U8_TAG_EXT_X_MAP); - - if (tag != NULL) { - m3u8tag_idelattr(tag, M3U8_ATTRIBUTE_BYTERANGE); - } - - end:; - - m3u8dq_free(&qeue); + err = m3u8stream_parse(resource); return err; diff --git a/src/m3u8stream.h b/src/m3u8stream.h index 3036dca..8e5a8c1 100644 --- a/src/m3u8stream.h +++ b/src/m3u8stream.h @@ -291,7 +291,7 @@ int m3u8stream_load( int m3u8stream_load_subresource( const struct M3U8Stream* const root, - struct M3U8Stream* const subresource, + struct M3U8Stream* const resource, const char* const something ); @@ -300,17 +300,4 @@ void m3u8stream_free(struct M3U8Stream* const stream); bigfloat_t m3u8stream_getduration(const struct M3U8Stream* const stream); biguint_t m3u8stream_getsegments(const struct M3U8Stream* const stream); -struct M3U8DownloadOptions { - size_t concurrency; - size_t retry; - char* temporary_directory; - void (*progress_callback)(const size_t total, const size_t current); -}; - -int m3u8stream_download( - struct M3U8Stream* const root, - struct M3U8Stream* const subresource, - const struct M3U8DownloadOptions* const options -); - #endif diff --git a/src/main.c b/src/main.c index e780600..53f5bb4 100644 --- a/src/main.c +++ b/src/main.c @@ -8,6 +8,7 @@ #include "m3u8stream.h" #include "m3u8types.h" #include "m3u8utils.h" +#include "m3u8download.h" #include "m3u8httpclient.h" #include "m3u8errors.h" #include "m3u8.h"