diff --git a/src/hls.c b/src/hls.c index a007b8f..2b83fee 100644 --- a/src/hls.c +++ b/src/hls.c @@ -1467,7 +1467,7 @@ static int vod_download_segment(void **psession, hls_media_playlist_t *me, struc int retries = 0; int ret = 0; while (true) { - MSG_PRINT("Downloading part %d\n", ms->sequence_number); + MSG_PRINT("Downloading part %d from %s\n", ms->sequence_number, ms->url); memset(seg, 0x00, sizeof(*seg)); size_t size = 0; @@ -1551,7 +1551,7 @@ uint8_t * find_first_ts_packet(ByteBuffer_t *buf) { return NULL; } -int download_hls(write_ctx_t *out_ctx, hls_media_playlist_t *me, hls_media_playlist_t *me_audio) +int download_hls(hls_media_playlist_t *me, hls_media_playlist_t *me_audio, bool merge, bool ignore_http_errors) { MSG_VERBOSE("Downloading segments.\n"); MSG_API("{\"d_t\":\"vod\"}\n"); // d_t - download type @@ -1562,12 +1562,29 @@ int download_hls(write_ctx_t *out_ctx, hls_media_playlist_t *me, hls_media_playl set_timeout_session(session, 2L, 3L); assert(session); time_t repTime = 0; + int i = 0; uint64_t downloaded_duration_ms = 0; int64_t download_size = 0; struct ByteBuffer seg; struct ByteBuffer seg_audio; + char prefix[5]; + if (!merge) { + sprintf(prefix, "%03d_", i); + } else { + strcpy(prefix, ""); + } + + FILE *out_file = get_output_file(prefix); + if (!out_file) { + MSG_ERROR("Failed to open output file!\n"); + return -1; + } + + write_ctx_t out_ctx_ = {priv_write, out_file}; + write_ctx_t *out_ctx = &out_ctx_; + struct hls_media_segment *ms = me->first_media_segment; struct hls_media_segment *ms_audio = NULL; merge_context_t merge_context; @@ -1579,55 +1596,75 @@ int download_hls(write_ctx_t *out_ctx, hls_media_playlist_t *me, hls_media_playl } while(ms) { - if (0 != vod_download_segment(&session, me, ms, &seg)) { - break; - } - - uint8_t *first_video_packet = find_first_ts_packet(&seg); - uint8_t *first_audio_packet = NULL; - if (ms_audio) { - if ( 0 != vod_download_segment(&session, me_audio, ms_audio, &seg_audio)) { - break; + if (0 == vod_download_segment(&session, me, ms, &seg)) { + uint8_t *first_video_packet = find_first_ts_packet(&seg); + uint8_t *first_audio_packet = NULL; + if (ms_audio) { + if ( 0 != vod_download_segment(&session, me_audio, ms_audio, &seg_audio)) { + break; + } + first_audio_packet = find_first_ts_packet(&seg_audio); } - first_audio_packet = find_first_ts_packet(&seg_audio); - } - // first segment should be TS for success merge - if (first_video_packet && first_audio_packet) { - size_t video_len = seg.len - (first_video_packet - seg.data); - size_t audio_len = seg_audio.len - (first_audio_packet - seg_audio.data); - - download_size += merge_packets( - &merge_context, - first_video_packet, - video_len, - first_audio_packet, - audio_len - ); - } else { - download_size += out_ctx->write(seg.data, seg.len, out_ctx->opaque); - } + // first segment should be TS for success merge + if (first_video_packet && first_audio_packet) { + size_t video_len = seg.len - (first_video_packet - seg.data); + size_t audio_len = seg_audio.len - (first_audio_packet - seg_audio.data); + + download_size += merge_packets( + &merge_context, + first_video_packet, + video_len, + first_audio_packet, + audio_len + ); + } else { + download_size += out_ctx->write(seg.data, seg.len, out_ctx->opaque); + } - if (ms_audio) { - free(seg_audio.data); - ms_audio = ms_audio->next; - } + if (ms_audio) { + free(seg_audio.data); + ms_audio = ms_audio->next; + } - free(seg.data); + free(seg.data); - downloaded_duration_ms += ms->duration_ms; + downloaded_duration_ms += ms->duration_ms; - time_t curRepTime = time(NULL); - if ((curRepTime - repTime) >= 1) { - MSG_API("{\"t_d\":%u,\"d_d\":%u,\"d_s\":%"PRId64"}\n", (uint32_t)(me->total_duration_ms / 1000), (uint32_t)(downloaded_duration_ms / 1000), download_size); - repTime = curRepTime; + time_t curRepTime = time(NULL); + if ((curRepTime - repTime) >= 1) { + MSG_API("{\"t_d\":%u,\"d_d\":%u,\"d_s\":%"PRId64"}\n", (uint32_t)(me->total_duration_ms / 1000), (uint32_t)(downloaded_duration_ms / 1000), download_size); + repTime = curRepTime; + } + } else if (ignore_http_errors) { + MSG_WARNING("Ignoring error downloading segment %d\n", i+1); + } else { + break; } ms = ms->next; + i++; + + if (ms && !merge) { + if (out_file) { + fclose(out_file); + } + + sprintf(prefix, "%03d_", i); + out_file = get_output_file(prefix); + if (!out_file) { + MSG_ERROR("Failed to open output file!\n"); + return -1; + } + } } MSG_API("{\"t_d\":%u,\"d_d\":%u,\"d_s\":%"PRId64"}\n", (uint32_t)(me->total_duration_ms / 1000), (uint32_t)(downloaded_duration_ms / 1000), download_size); + if (out_file) { + fclose(out_file); + } + if (session) { clean_http_session(session); } diff --git a/src/hls.h b/src/hls.h index ee1b863..e07a1d0 100644 --- a/src/hls.h +++ b/src/hls.h @@ -97,7 +97,7 @@ int handle_hls_media_playlist(hls_media_playlist_t *me); int download_live_hls(write_ctx_t *ctx, hls_media_playlist_t *me); bool consecutive_sync_byte(uint8_t *buf, size_t len, uint8_t n); uint8_t * find_first_ts_packet(ByteBuffer_t *buf); -int download_hls(write_ctx_t *ctx, hls_media_playlist_t *me, hls_media_playlist_t *me_audio); +int download_hls(hls_media_playlist_t *me, hls_media_playlist_t *me_audio, bool merge, bool ignore_http_errors); int print_enc_keys(hls_media_playlist_t *me); void print_hls_master_playlist(struct hls_master_playlist *ma); void media_playlist_cleanup(hls_media_playlist_t *me); diff --git a/src/main.c b/src/main.c index c157c76..f3303f1 100644 --- a/src/main.c +++ b/src/main.c @@ -21,80 +21,6 @@ #include "msg.h" #include "misc.h" -static size_t priv_write(const uint8_t *data, size_t len, void *opaque) { - return fwrite(data, 1, len, opaque); -} - -static bool is_file_exists(const char *filename) -{ -#ifndef _MSC_VER - return access(filename, F_OK) != -1; -#else - struct stat info; - int ret = -1; - - ret = stat(filename, &info); - return 0 == ret; -#endif -} - -static FILE* get_output_file(void) -{ - FILE *pFile = NULL; - - if (hls_args.filename && 0 == strncmp(hls_args.filename, "-", 2)) { - // Set "stdout" to have binary mode: - fflush(stdout); -#if !defined(_MSC_VER) && !defined(__MINGW32__) - pFile = freopen(NULL, "wb", stdout); -#else - if (-1 != setmode(_fileno(stdout), _O_BINARY)) { - pFile = stdout; - } -#endif - fflush(stdout); - } else { - char filename[MAX_FILENAME_LEN]; - if (hls_args.filename) { - strcpy(filename, hls_args.filename); - } - else { - strcpy(filename, "000_hls_output.ts"); - } - - if (is_file_exists(filename)) { - if (hls_args.force_overwrite) { - if (remove(filename) != 0) { - MSG_ERROR("Error overwriting file"); - exit(1); - } - } - else { - char userchoice = '\0'; - MSG_PRINT("File already exists. Overwrite? (y/n) "); - if (scanf("\n%c", &userchoice) && userchoice == 'y') { - if (remove(filename) != 0) { - MSG_ERROR("Error overwriting file"); - exit(1); - } - } - else { - MSG_WARNING("Choose a different filename. Exiting.\n"); - exit(0); - } - } - } - - pFile = fopen(filename, "wb"); - } - - if (pFile == NULL) - { - MSG_ERROR("Error can not open output file\n"); - exit(1); - } - return pFile; -} static bool get_data_with_retry(char *url, char **hlsfile_source, char **finall_url, int tries) { @@ -389,16 +315,20 @@ int main(int argc, char *argv[]) } } else { int ret = -1; - FILE *out_file = get_output_file(); - if (out_file) { - write_ctx_t out_ctx = {priv_write, out_file}; - if (media_playlist.is_endlist) { - ret = download_hls(&out_ctx, &media_playlist, &audio_media_playlist); - } else { + + if (media_playlist.is_endlist) { + ret = download_hls(&media_playlist, &audio_media_playlist, !hls_args.skip_merge, + hls_args.ignore_http_errors); + } else { + char prefix[] = ""; + FILE *out_file = get_output_file(prefix); + if (out_file) { + write_ctx_t out_ctx = {priv_write, out_file}; ret = download_live_hls(&out_ctx, &media_playlist); + fclose(out_file); } - fclose(out_file); } + return ret ? 1 : 0; } diff --git a/src/misc.c b/src/misc.c index 40cc334..de2e080 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -28,6 +29,7 @@ static void print_help(const char *filename) "-A ... Select audio language.\n" "-v ... Verbose more information.\n" "-o ... Choose name of output file (\"-\" alias for stdout).\n" + "-m ... Don't merge output - save individual parts instead.\n" "-u ... Set custom HTTP User-Agent header.\n" "-h ... Set custom HTTP header.\n" "-p ... Set proxy uri.\n" @@ -41,6 +43,7 @@ static void print_help(const char *filename) "-t ... Print the links to the .ts files.\n" "-s ... Set live start offset in seconds.\n" "-i ... Set live stream download duration in seconds.\n" + "-I ... Ignore HTTP errors.\n" "-e ... Set refresh delay in seconds.\n" "-r ... Set max retries at open.\n" "-w ... Set max download segment retries.\n" @@ -55,7 +58,7 @@ int parse_argv(int argc, char * const argv[]) int ret = 0; int c = 0; int custom_header_idx = 0; - while ( (c = getopt(argc, argv, "bH:W:A:vqbfFK:ctdo:u:h:s:i:r:w:e:p:k:n:a:C:")) != -1) + while ( (c = getopt(argc, argv, "bH:W:A:vqbfFK:ctdo:mu:h:s:i:Ir:w:e:p:k:n:a:C:")) != -1) { switch (c) { @@ -114,9 +117,15 @@ int parse_argv(int argc, char * const argv[]) case 'o': hls_args.filename = optarg; break; + case 'm': + hls_args.skip_merge = true; + break; case 't': hls_args.dump_ts_urls = true; break; + case 'I': + hls_args.ignore_http_errors = true; + break; case 'd': hls_args.dump_dec_cmd = true; break; @@ -253,3 +262,87 @@ char *repl_str(const char *str, const char *from, const char *to) free(pos_cache); return ret; } + +FILE* get_output_file(char prefix[]) +{ + FILE *pFile = NULL; + + if (hls_args.filename && 0 == strncmp(hls_args.filename, "-", 2)) { + // Set "stdout" to have binary mode: + fflush(stdout); +#if !defined(_MSC_VER) && !defined(__MINGW32__) + pFile = freopen(NULL, "wb", stdout); +#else + if (-1 != setmode(_fileno(stdout), _O_BINARY)) { + pFile = stdout; + } +#endif + fflush(stdout); + } else { + char filename[MAX_FILENAME_LEN + strlen(prefix) + 1]; + char *file_basename = NULL; + file_basename = (char *) malloc(MAX_FILENAME_LEN + strlen(prefix) + 1); + + if (hls_args.filename) { + strcpy(filename, dirname(hls_args.filename)); + strcat(filename, "/"); + strcpy(file_basename, basename(hls_args.filename)); + } else { + strcpy(file_basename, "hls_output.ts"); + } + + strncat(filename, prefix, strlen(prefix)); + strncat(filename, file_basename, strlen(file_basename)); + + MSG_DBG("Using output filename: %s\n", filename); + + if (is_file_exists(filename)) { + if (hls_args.force_overwrite) { + if (remove(filename) != 0) { + MSG_ERROR("Error overwriting file"); + exit(1); + } + } + else { + char userchoice = '\0'; + MSG_PRINT("File already exists. Overwrite? (y/n) "); + if (scanf("\n%c", &userchoice) && userchoice == 'y') { + if (remove(filename) != 0) { + MSG_ERROR("Error overwriting file"); + exit(1); + } + } + else { + MSG_WARNING("Choose a different filename. Exiting.\n"); + exit(0); + } + } + } + + pFile = fopen(filename, "wb"); + } + + if (pFile == NULL) + { + MSG_ERROR("Error can not open output file\n"); + exit(1); + } + return pFile; +} + +size_t priv_write(const uint8_t *data, size_t len, void *opaque) { + return fwrite(data, 1, len, opaque); +} + +bool is_file_exists(const char *filename) +{ +#ifndef _MSC_VER + return access(filename, F_OK) != -1; +#else + struct stat info; + int ret = -1; + + ret = stat(filename, &info); + return 0 == ret; +#endif +} diff --git a/src/misc.h b/src/misc.h index 069d873..1839e7c 100644 --- a/src/misc.h +++ b/src/misc.h @@ -37,6 +37,8 @@ struct hls_args { bool force_ignoredrm; bool dump_ts_urls; bool dump_dec_cmd; + bool skip_merge; + bool ignore_http_errors; int live_start_offset_sec; int live_duration_sec; int refresh_delay_sec; @@ -60,6 +62,9 @@ extern struct hls_args hls_args; int str_to_bin(uint8_t *data, char *hexstring, int len); int parse_argv(int argc, char * const argv[]); +FILE* get_output_file(char prefix[]); +size_t priv_write(const uint8_t *data, size_t len, void *opaque); +bool is_file_exists(const char *filename); char *repl_str(const char *str, const char *from, const char *to);