| @@ -29,6 +29,7 @@ | |||
| #include "libavformat/avformat.h" | |||
| #include "libavcodec/avcodec.h" | |||
| #include "libavutil/avstring.h" | |||
| #include "libavutil/bprint.h" | |||
| #include "libavutil/opt.h" | |||
| #include "libavutil/pixdesc.h" | |||
| #include "libavutil/dict.h" | |||
| @@ -384,31 +385,6 @@ fail: | |||
| return NULL; | |||
| } | |||
| #define ESCAPE_INIT_BUF_SIZE 256 | |||
| #define ESCAPE_CHECK_SIZE(src, size, max_size) \ | |||
| if (size > max_size) { \ | |||
| char buf[64]; \ | |||
| snprintf(buf, sizeof(buf), "%s", src); \ | |||
| av_log(log_ctx, AV_LOG_WARNING, \ | |||
| "String '%s...' is too big\n", buf); \ | |||
| return "FFPROBE_TOO_BIG_STRING"; \ | |||
| } | |||
| #define ESCAPE_REALLOC_BUF(dst_size_p, dst_p, src, size) \ | |||
| if (*dst_size_p < size) { \ | |||
| char *q = av_realloc(*dst_p, size); \ | |||
| if (!q) { \ | |||
| char buf[64]; \ | |||
| snprintf(buf, sizeof(buf), "%s", src); \ | |||
| av_log(log_ctx, AV_LOG_WARNING, \ | |||
| "String '%s...' could not be escaped\n", buf); \ | |||
| return "FFPROBE_THIS_STRING_COULD_NOT_BE_ESCAPED"; \ | |||
| } \ | |||
| *dst_size_p = size; \ | |||
| *dst = q; \ | |||
| } | |||
| /* WRITERS */ | |||
| /* Default output */ | |||
| @@ -487,81 +463,51 @@ static const Writer default_writer = { | |||
| * Escape \n, \r, \\ and sep characters contained in s, and print the | |||
| * resulting string. | |||
| */ | |||
| static const char *c_escape_str(char **dst, size_t *dst_size, | |||
| const char *src, const char sep, void *log_ctx) | |||
| static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) | |||
| { | |||
| const char *p; | |||
| char *q; | |||
| size_t size = 1; | |||
| /* precompute size */ | |||
| for (p = src; *p; p++, size++) { | |||
| ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-2); | |||
| if (*p == '\n' || *p == '\r' || *p == '\\') | |||
| size++; | |||
| } | |||
| ESCAPE_REALLOC_BUF(dst_size, dst, src, size); | |||
| q = *dst; | |||
| for (p = src; *p; p++) { | |||
| switch (*src) { | |||
| case '\n': *q++ = '\\'; *q++ = 'n'; break; | |||
| case '\r': *q++ = '\\'; *q++ = 'r'; break; | |||
| case '\\': *q++ = '\\'; *q++ = '\\'; break; | |||
| case '\n': av_bprintf(dst, "%s", "\\n"); break; | |||
| case '\r': av_bprintf(dst, "%s", "\\r"); break; | |||
| case '\\': av_bprintf(dst, "%s", "\\\\"); break; | |||
| default: | |||
| if (*p == sep) | |||
| *q++ = '\\'; | |||
| *q++ = *p; | |||
| av_bprint_chars(dst, '\\', 1); | |||
| av_bprint_chars(dst, *p, 1); | |||
| } | |||
| } | |||
| *q = 0; | |||
| return *dst; | |||
| return dst->str; | |||
| } | |||
| /** | |||
| * Quote fields containing special characters, check RFC4180. | |||
| */ | |||
| static const char *csv_escape_str(char **dst, size_t *dst_size, | |||
| const char *src, const char sep, void *log_ctx) | |||
| static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) | |||
| { | |||
| const char *p; | |||
| char *q; | |||
| size_t size = 1; | |||
| int quote = 0; | |||
| /* precompute size */ | |||
| for (p = src; *p; p++, size++) { | |||
| ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-4); | |||
| /* check if input needs quoting */ | |||
| for (p = src; *p; p++) | |||
| if (*p == '"' || *p == sep || *p == '\n' || *p == '\r') | |||
| if (!quote) { | |||
| quote = 1; | |||
| size += 2; | |||
| } | |||
| if (*p == '"') | |||
| size++; | |||
| } | |||
| ESCAPE_REALLOC_BUF(dst_size, dst, src, size); | |||
| quote = 1; | |||
| q = *dst; | |||
| p = src; | |||
| if (quote) | |||
| *q++ = '\"'; | |||
| while (*p) { | |||
| av_bprint_chars(dst, '\"', 1); | |||
| for (p = src; *p; p++) { | |||
| if (*p == '"') | |||
| *q++ = '\"'; | |||
| *q++ = *p++; | |||
| av_bprint_chars(dst, '\"', 1); | |||
| av_bprint_chars(dst, *p, 1); | |||
| } | |||
| if (quote) | |||
| *q++ = '\"'; | |||
| *q = 0; | |||
| return *dst; | |||
| av_bprint_chars(dst, '\"', 1); | |||
| return dst->str; | |||
| } | |||
| static const char *none_escape_str(char **dst, size_t *dst_size, | |||
| const char *src, const char sep, void *log_ctx) | |||
| static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) | |||
| { | |||
| return src; | |||
| } | |||
| @@ -571,11 +517,8 @@ typedef struct CompactContext { | |||
| char *item_sep_str; | |||
| char item_sep; | |||
| int nokey; | |||
| char *buf; | |||
| size_t buf_size; | |||
| char *escape_mode_str; | |||
| const char * (*escape_str)(char **dst, size_t *dst_size, | |||
| const char *src, const char sep, void *log_ctx); | |||
| const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx); | |||
| } CompactContext; | |||
| #define OFFSET(x) offsetof(CompactContext, x) | |||
| @@ -621,10 +564,6 @@ static av_cold int compact_init(WriterContext *wctx, const char *args, void *opa | |||
| } | |||
| compact->item_sep = compact->item_sep_str[0]; | |||
| compact->buf_size = ESCAPE_INIT_BUF_SIZE; | |||
| if (!(compact->buf = av_malloc(compact->buf_size))) | |||
| return AVERROR(ENOMEM); | |||
| if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str; | |||
| else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str; | |||
| else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str; | |||
| @@ -641,7 +580,6 @@ static av_cold void compact_uninit(WriterContext *wctx) | |||
| CompactContext *compact = wctx->priv; | |||
| av_freep(&compact->item_sep_str); | |||
| av_freep(&compact->buf); | |||
| av_freep(&compact->escape_mode_str); | |||
| } | |||
| @@ -660,12 +598,14 @@ static void compact_print_section_footer(WriterContext *wctx, const char *sectio | |||
| static void compact_print_str(WriterContext *wctx, const char *key, const char *value) | |||
| { | |||
| CompactContext *compact = wctx->priv; | |||
| AVBPrint buf; | |||
| if (wctx->nb_item) printf("%c", compact->item_sep); | |||
| if (!compact->nokey) | |||
| printf("%s=", key); | |||
| printf("%s", compact->escape_str(&compact->buf, &compact->buf_size, | |||
| value, compact->item_sep, wctx)); | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf("%s", compact->escape_str(&buf, value, compact->item_sep, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| } | |||
| static void compact_print_int(WriterContext *wctx, const char *key, long long int value) | |||
| @@ -682,14 +622,20 @@ static void compact_show_tags(WriterContext *wctx, AVDictionary *dict) | |||
| { | |||
| CompactContext *compact = wctx->priv; | |||
| AVDictionaryEntry *tag = NULL; | |||
| AVBPrint buf; | |||
| while ((tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX))) { | |||
| if (wctx->nb_item) printf("%c", compact->item_sep); | |||
| if (!compact->nokey) | |||
| printf("tag:%s=", compact->escape_str(&compact->buf, &compact->buf_size, | |||
| tag->key, compact->item_sep, wctx)); | |||
| printf("%s", compact->escape_str(&compact->buf, &compact->buf_size, | |||
| tag->value, compact->item_sep, wctx)); | |||
| if (!compact->nokey) { | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf("tag:%s=", compact->escape_str(&buf, tag->key, compact->item_sep, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| } | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf("%s", compact->escape_str(&buf, tag->value, compact->item_sep, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| } | |||
| } | |||
| @@ -731,8 +677,6 @@ static const Writer csv_writer = { | |||
| typedef struct { | |||
| const AVClass *class; | |||
| int multiple_entries; ///< tells if the given chapter requires multiple entries | |||
| char *buf; | |||
| size_t buf_size; | |||
| int print_packets_and_frames; | |||
| int indent_level; | |||
| int compact; | |||
| @@ -776,52 +720,27 @@ static av_cold int json_init(WriterContext *wctx, const char *args, void *opaque | |||
| json->item_sep = json->compact ? ", " : ",\n"; | |||
| json->item_start_end = json->compact ? " " : "\n"; | |||
| json->buf_size = ESCAPE_INIT_BUF_SIZE; | |||
| if (!(json->buf = av_malloc(json->buf_size))) | |||
| return AVERROR(ENOMEM); | |||
| return 0; | |||
| } | |||
| static av_cold void json_uninit(WriterContext *wctx) | |||
| { | |||
| JSONContext *json = wctx->priv; | |||
| av_freep(&json->buf); | |||
| } | |||
| static const char *json_escape_str(char **dst, size_t *dst_size, const char *src, | |||
| void *log_ctx) | |||
| static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx) | |||
| { | |||
| static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0}; | |||
| static const char json_subst[] = {'"', '\\', 'b', 'f', 'n', 'r', 't', 0}; | |||
| const char *p; | |||
| char *q; | |||
| size_t size = 1; | |||
| // compute the length of the escaped string | |||
| for (p = src; *p; p++) { | |||
| ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-6); | |||
| if (strchr(json_escape, *p)) size += 2; // simple escape | |||
| else if ((unsigned char)*p < 32) size += 6; // handle non-printable chars | |||
| else size += 1; // char copy | |||
| } | |||
| ESCAPE_REALLOC_BUF(dst_size, dst, src, size); | |||
| q = *dst; | |||
| for (p = src; *p; p++) { | |||
| char *s = strchr(json_escape, *p); | |||
| if (s) { | |||
| *q++ = '\\'; | |||
| *q++ = json_subst[s - json_escape]; | |||
| av_bprint_chars(dst, '\\', 1); | |||
| av_bprint_chars(dst, json_subst[s - json_escape], 1); | |||
| } else if ((unsigned char)*p < 32) { | |||
| snprintf(q, 7, "\\u00%02x", *p & 0xff); | |||
| q += 6; | |||
| av_bprintf(dst, "\\u00%02x", *p & 0xff); | |||
| } else { | |||
| *q++ = *p; | |||
| av_bprint_chars(dst, *p, 1); | |||
| } | |||
| } | |||
| *q = 0; | |||
| return *dst; | |||
| return dst->str; | |||
| } | |||
| static void json_print_header(WriterContext *wctx) | |||
| @@ -843,6 +762,7 @@ static void json_print_footer(WriterContext *wctx) | |||
| static void json_print_chapter_header(WriterContext *wctx, const char *chapter) | |||
| { | |||
| JSONContext *json = wctx->priv; | |||
| AVBPrint buf; | |||
| if (wctx->nb_chapter) | |||
| printf(","); | |||
| @@ -852,7 +772,9 @@ static void json_print_chapter_header(WriterContext *wctx, const char *chapter) | |||
| !strcmp(chapter, "streams") || !strcmp(chapter, "library_versions"); | |||
| if (json->multiple_entries) { | |||
| JSON_INDENT(); | |||
| printf("\"%s\": [\n", json_escape_str(&json->buf, &json->buf_size, chapter, wctx)); | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf("\"%s\": [\n", json_escape_str(&buf, chapter, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| json->print_packets_and_frames = !strcmp(chapter, "packets_and_frames"); | |||
| json->indent_level++; | |||
| } | |||
| @@ -903,10 +825,15 @@ static void json_print_section_footer(WriterContext *wctx, const char *section) | |||
| static inline void json_print_item_str(WriterContext *wctx, | |||
| const char *key, const char *value) | |||
| { | |||
| JSONContext *json = wctx->priv; | |||
| AVBPrint buf; | |||
| printf("\"%s\":", json_escape_str(&json->buf, &json->buf_size, key, wctx)); | |||
| printf(" \"%s\"", json_escape_str(&json->buf, &json->buf_size, value, wctx)); | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf("\"%s\":", json_escape_str(&buf, key, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf(" \"%s\"", json_escape_str(&buf, value, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| } | |||
| static void json_print_str(WriterContext *wctx, const char *key, const char *value) | |||
| @@ -922,12 +849,15 @@ static void json_print_str(WriterContext *wctx, const char *key, const char *val | |||
| static void json_print_int(WriterContext *wctx, const char *key, long long int value) | |||
| { | |||
| JSONContext *json = wctx->priv; | |||
| AVBPrint buf; | |||
| if (wctx->nb_item) printf("%s", json->item_sep); | |||
| if (!json->compact) | |||
| JSON_INDENT(); | |||
| printf("\"%s\": %lld", | |||
| json_escape_str(&json->buf, &json->buf_size, key, wctx), value); | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf("\"%s\": %lld", json_escape_str(&buf, key, wctx), value); | |||
| av_bprint_finalize(&buf, NULL); | |||
| } | |||
| static void json_show_tags(WriterContext *wctx, AVDictionary *dict) | |||
| @@ -960,7 +890,6 @@ static const Writer json_writer = { | |||
| .name = "json", | |||
| .priv_size = sizeof(JSONContext), | |||
| .init = json_init, | |||
| .uninit = json_uninit, | |||
| .print_header = json_print_header, | |||
| .print_footer = json_print_footer, | |||
| .print_chapter_header = json_print_chapter_header, | |||
| @@ -982,8 +911,6 @@ typedef struct { | |||
| int indent_level; | |||
| int fully_qualified; | |||
| int xsd_strict; | |||
| char *buf; | |||
| size_t buf_size; | |||
| } XMLContext; | |||
| #undef OFFSET | |||
| @@ -1043,61 +970,25 @@ static av_cold int xml_init(WriterContext *wctx, const char *args, void *opaque) | |||
| } | |||
| } | |||
| xml->buf_size = ESCAPE_INIT_BUF_SIZE; | |||
| if (!(xml->buf = av_malloc(xml->buf_size))) | |||
| return AVERROR(ENOMEM); | |||
| return 0; | |||
| } | |||
| static av_cold void xml_uninit(WriterContext *wctx) | |||
| { | |||
| XMLContext *xml = wctx->priv; | |||
| av_freep(&xml->buf); | |||
| } | |||
| static const char *xml_escape_str(char **dst, size_t *dst_size, const char *src, | |||
| void *log_ctx) | |||
| static const char *xml_escape_str(AVBPrint *dst, const char *src, void *log_ctx) | |||
| { | |||
| const char *p; | |||
| char *q; | |||
| size_t size = 1; | |||
| /* precompute size */ | |||
| for (p = src; *p; p++, size++) { | |||
| ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-10); | |||
| switch (*p) { | |||
| case '&' : size += strlen("&"); break; | |||
| case '<' : size += strlen("<"); break; | |||
| case '>' : size += strlen(">"); break; | |||
| case '\"': size += strlen("""); break; | |||
| case '\'': size += strlen("'"); break; | |||
| default: size++; | |||
| } | |||
| } | |||
| ESCAPE_REALLOC_BUF(dst_size, dst, src, size); | |||
| #define COPY_STR(str) { \ | |||
| const char *s = str; \ | |||
| while (*s) \ | |||
| *q++ = *s++; \ | |||
| } | |||
| p = src; | |||
| q = *dst; | |||
| while (*p) { | |||
| for (p = src; *p; p++) { | |||
| switch (*p) { | |||
| case '&' : COPY_STR("&"); break; | |||
| case '<' : COPY_STR("<"); break; | |||
| case '>' : COPY_STR(">"); break; | |||
| case '\"': COPY_STR("""); break; | |||
| case '\'': COPY_STR("'"); break; | |||
| default: *q++ = *p; | |||
| case '&' : av_bprintf(dst, "%s", "&"); break; | |||
| case '<' : av_bprintf(dst, "%s", "<"); break; | |||
| case '>' : av_bprintf(dst, "%s", ">"); break; | |||
| case '\"': av_bprintf(dst, "%s", """); break; | |||
| case '\'': av_bprintf(dst, "%s", "'"); break; | |||
| default: av_bprint_chars(dst, *p, 1); | |||
| } | |||
| p++; | |||
| } | |||
| *q = 0; | |||
| return *dst; | |||
| return dst->str; | |||
| } | |||
| static void xml_print_header(WriterContext *wctx) | |||
| @@ -1172,11 +1063,13 @@ static void xml_print_section_footer(WriterContext *wctx, const char *section) | |||
| static void xml_print_str(WriterContext *wctx, const char *key, const char *value) | |||
| { | |||
| XMLContext *xml = wctx->priv; | |||
| AVBPrint buf; | |||
| if (wctx->nb_item) | |||
| printf(" "); | |||
| printf("%s=\"%s\"", key, xml_escape_str(&xml->buf, &xml->buf_size, value, wctx)); | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf("%s=\"%s\"", key, xml_escape_str(&buf, value, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| } | |||
| static void xml_print_int(WriterContext *wctx, const char *key, long long int value) | |||
| @@ -1191,6 +1084,7 @@ static void xml_show_tags(WriterContext *wctx, AVDictionary *dict) | |||
| XMLContext *xml = wctx->priv; | |||
| AVDictionaryEntry *tag = NULL; | |||
| int is_first = 1; | |||
| AVBPrint buf; | |||
| xml->indent_level++; | |||
| while ((tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX))) { | |||
| @@ -1201,10 +1095,14 @@ static void xml_show_tags(WriterContext *wctx, AVDictionary *dict) | |||
| is_first = 0; | |||
| } | |||
| XML_INDENT(); | |||
| printf("<tag key=\"%s\"", | |||
| xml_escape_str(&xml->buf, &xml->buf_size, tag->key, wctx)); | |||
| printf(" value=\"%s\"/>\n", | |||
| xml_escape_str(&xml->buf, &xml->buf_size, tag->value, wctx)); | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf("<tag key=\"%s\"", xml_escape_str(&buf, tag->key, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |||
| printf(" value=\"%s\"/>\n", xml_escape_str(&buf, tag->value, wctx)); | |||
| av_bprint_finalize(&buf, NULL); | |||
| } | |||
| xml->indent_level--; | |||
| } | |||
| @@ -1213,7 +1111,6 @@ static Writer xml_writer = { | |||
| .name = "xml", | |||
| .priv_size = sizeof(XMLContext), | |||
| .init = xml_init, | |||
| .uninit = xml_uninit, | |||
| .print_header = xml_print_header, | |||
| .print_footer = xml_print_footer, | |||
| .print_chapter_header = xml_print_chapter_header, | |||