| /* |
| * Copyright (c) The FFmpeg developers |
| * |
| * This file is part of FFmpeg. |
| * |
| * FFmpeg is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * FFmpeg is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with FFmpeg; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include "avtextformat.h" |
| #include "libavutil/bprint.h" |
| #include "libavutil/error.h" |
| #include "libavutil/opt.h" |
| #include "tf_internal.h" |
| |
| /* XML output */ |
| |
| typedef struct XMLContext { |
| const AVClass *class; |
| int within_tag; |
| int indent_level; |
| int fully_qualified; |
| int xsd_strict; |
| } XMLContext; |
| |
| #undef OFFSET |
| #define OFFSET(x) offsetof(XMLContext, x) |
| |
| static const AVOption xml_options[] = { |
| { "fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1 }, |
| { "q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1 }, |
| { "xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1 }, |
| { "x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1 }, |
| { NULL }, |
| }; |
| |
| DEFINE_FORMATTER_CLASS(xml); |
| |
| static av_cold int xml_init(AVTextFormatContext *wctx) |
| { |
| XMLContext *xml = wctx->priv; |
| |
| if (xml->xsd_strict) { |
| xml->fully_qualified = 1; |
| #define CHECK_COMPLIANCE(opt, opt_name) \ |
| if (opt) { \ |
| av_log(wctx, AV_LOG_ERROR, \ |
| "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \ |
| "You need to disable such option with '-no%s'\n", opt_name, opt_name); \ |
| return AVERROR(EINVAL); \ |
| } |
| ////CHECK_COMPLIANCE(show_private_data, "private"); |
| CHECK_COMPLIANCE(wctx->show_value_unit, "unit"); |
| CHECK_COMPLIANCE(wctx->use_value_prefix, "prefix"); |
| } |
| |
| return 0; |
| } |
| |
| #define XML_INDENT() writer_printf(wctx, "%*c", xml->indent_level * 4, ' ') |
| |
| static void xml_print_section_header(AVTextFormatContext *wctx, const void *data) |
| { |
| XMLContext *xml = wctx->priv; |
| const AVTextFormatSection *section = tf_get_section(wctx, wctx->level); |
| const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level); |
| |
| if (!section) |
| return; |
| |
| if (wctx->level == 0) { |
| const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " |
| "xmlns:ffprobe=\"http://www.ffmpeg.org/schema/ffprobe\" " |
| "xsi:schemaLocation=\"http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd\""; |
| |
| writer_put_str(wctx, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
| writer_printf(wctx, "<%sffprobe%s>\n", |
| xml->fully_qualified ? "ffprobe:" : "", |
| xml->fully_qualified ? qual : ""); |
| return; |
| } |
| |
| if (xml->within_tag) { |
| xml->within_tag = 0; |
| writer_put_str(wctx, ">\n"); |
| } |
| |
| if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) && |
| wctx->level && wctx->nb_item[wctx->level - 1]) |
| writer_w8(wctx, '\n'); |
| xml->indent_level++; |
| |
| if (section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY | AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS)) { |
| XML_INDENT(); |
| writer_printf(wctx, "<%s", section->name); |
| |
| if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) { |
| AVBPrint buf; |
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
| av_bprint_escape(&buf, section->get_type(data), NULL, |
| AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); |
| writer_printf(wctx, " type=\"%s\"", buf.str); |
| } |
| writer_printf(wctx, ">\n", section->name); |
| } else { |
| XML_INDENT(); |
| writer_printf(wctx, "<%s ", section->name); |
| xml->within_tag = 1; |
| } |
| } |
| |
| static void xml_print_section_footer(AVTextFormatContext *wctx) |
| { |
| XMLContext *xml = wctx->priv; |
| const AVTextFormatSection *section = tf_get_section(wctx, wctx->level); |
| |
| if (!section) |
| return; |
| |
| if (wctx->level == 0) { |
| writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : ""); |
| } else if (xml->within_tag) { |
| xml->within_tag = 0; |
| writer_put_str(wctx, "/>\n"); |
| xml->indent_level--; |
| } else { |
| XML_INDENT(); |
| writer_printf(wctx, "</%s>\n", section->name); |
| xml->indent_level--; |
| } |
| } |
| |
| static void xml_print_value(AVTextFormatContext *wctx, const char *key, |
| const char *str, int64_t num, const int is_int) |
| { |
| AVBPrint buf; |
| XMLContext *xml = wctx->priv; |
| const AVTextFormatSection *section = tf_get_section(wctx, wctx->level); |
| |
| if (!section) |
| return; |
| |
| av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
| |
| if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS) { |
| xml->indent_level++; |
| XML_INDENT(); |
| av_bprint_escape(&buf, key, NULL, |
| AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); |
| writer_printf(wctx, "<%s key=\"%s\"", |
| section->element_name, buf.str); |
| av_bprint_clear(&buf); |
| |
| if (is_int) { |
| writer_printf(wctx, " value=\"%"PRId64"\"/>\n", num); |
| } else { |
| av_bprint_escape(&buf, str, NULL, |
| AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); |
| writer_printf(wctx, " value=\"%s\"/>\n", buf.str); |
| } |
| xml->indent_level--; |
| } else { |
| if (wctx->nb_item[wctx->level]) |
| writer_w8(wctx, ' '); |
| |
| if (is_int) { |
| writer_printf(wctx, "%s=\"%"PRId64"\"", key, num); |
| } else { |
| av_bprint_escape(&buf, str, NULL, |
| AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); |
| writer_printf(wctx, "%s=\"%s\"", key, buf.str); |
| } |
| } |
| |
| av_bprint_finalize(&buf, NULL); |
| } |
| |
| static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) |
| { |
| xml_print_value(wctx, key, value, 0, 0); |
| } |
| |
| static void xml_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) |
| { |
| xml_print_value(wctx, key, NULL, value, 1); |
| } |
| |
| const AVTextFormatter avtextformatter_xml = { |
| .name = "xml", |
| .priv_size = sizeof(XMLContext), |
| .init = xml_init, |
| .print_section_header = xml_print_section_header, |
| .print_section_footer = xml_print_section_footer, |
| .print_integer = xml_print_int, |
| .print_string = xml_print_str, |
| .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, |
| .priv_class = &xml_class, |
| }; |