From a3a25225c608b474de09c2f237cf2cfca8905b5a Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Wed, 16 Dec 2015 21:58:51 +0100 Subject: [PATCH] mkvmerge: output warnings/errors as JSON in JSON identification mode Implements #1537. --- ChangeLog | 5 + ...merge-identification-output-schema-v2.json | 345 ++++++++++++++++++ src/common/output.cpp | 38 ++ src/common/output.h | 4 + src/merge/generic_reader.cpp | 4 +- src/merge/id_result.cpp | 4 +- src/merge/mkvmerge.cpp | 9 +- tests/results.txt | 2 +- tests/test.d/simple_test.rb | 2 +- 9 files changed, 403 insertions(+), 10 deletions(-) create mode 100644 doc/json-schema/mkvmerge-identification-output-schema-v2.json diff --git a/ChangeLog b/ChangeLog index 6c2ef4a9f..7bbcafd3e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2015-12-16 Moritz Bunkus + * mkvmerge: enhancement: if JSON identification mode is active + then warnings and errors will be output as JSON as well. They're + output as arrays of strings as the keys "warnings" and "errors" of + the main JSON object. Implements #1537. + * all: reversion of a change: several ISO 639-2 codes of languages that are very old and not spoken anymore have been re-added (e.g. "English, Middle (1100-1500)") due to feedback from users diff --git a/doc/json-schema/mkvmerge-identification-output-schema-v2.json b/doc/json-schema/mkvmerge-identification-output-schema-v2.json new file mode 100644 index 000000000..76af0f18c --- /dev/null +++ b/doc/json-schema/mkvmerge-identification-output-schema-v2.json @@ -0,0 +1,345 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://mkvtoolnix.download/doc/mkvmerge-identification-output-schema-v2.json", + "title": "mkvmerge identification output", + "description": "The JSON output produced by mkvmerge's file identification mode", + "type": "object", + "properties": { + "attachments": { + "description": "an array describing the attachments found if any", + "type": "array", + "items": { + "type": "object", + "properties": { + "content_type": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + }, + "file_name": { + "type": "string" + }, + "id": { + "type": "integer", + "minimum": 0 + }, + "size": { + "type": "integer", + "minimum": 0 + }, + "properties": { + "type": "object", + "properties": { + "uid": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "type": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "file_name", + "id", + "properties", + "size" + ] + } + }, + "chapters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "num_entries": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "num_entries" + ] + } + }, + "container": { + "description": "information about the identified container", + "type": "object", + "properties": { + "properties": { + "description": "additional properties for the container varying by container format", + "type": "object", + "properties": { + "container_type": { + "description": "A unique number identifying the container type that's supposed to stay constant over all future releases of MKVToolNix", + "type": "integer", + "minLength": 1 + }, + "duration": { + "description": "The file's/segment's duration in nanoseconds", + "type": "integer", + "minimum": 0 + }, + "is_providing_timecodes": { + "description": "States whether or not the container has timestamps for the packets (e.g. Matroska, MP4) or not (e.g. SRT, MP3)", + "type": "boolean" + }, + "segment_uid": { + "description": "A hexadecimal string of the segment's UID (only for Matroska files)", + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "next_segment_uid": { + "description": "A hexadecimal string of the next segment's UID (only for Matroska files)", + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "previous_segment_uid": { + "description": "A hexadecimal string of the previous segment's UID (only for Matroska files)", + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "other_file": { + "description": "The file name of an additional file processed as well", + "type": "string", + "minLength": 1 + }, + "playlist": { + "description": "States whether or not the identified file is a playlist (e.g. MPLS) referring to several other files", + "type": "boolean" + }, + "playlist_chapters": { + "description": "The number of chapters in a playlist if it is a one", + "type": "integer", + "minimum": 0 + }, + "playlist_duration": { + "description": "The total duration in nanoseconds of all files referenced by the playlist if it is a one", + "type": "integer", + "minimum": 0 + }, + "playlist_size": { + "description": "The total size in bytes of all files referenced by the playlist if it is a one", + "type": "integer", + "minimum": 0 + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "recognized": { + "description": "States whether or not mkvmerge knows about the format", + "type": "boolean" + }, + "supported": { + "description": "States whether or not mkvmerge can read the format", + "type": "boolean" + }, + "type": { + "description": "A human-readable description/name for the container format", + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false, + "required": [ + "recognized", + "supported" + ] + }, + "errors": { + "type": "array", + "items": { + "type": "string" + } + }, + "file_name": { + "description": "the identified file's name", + "type": "string", + "minLength": 1 + }, + "global_tags": { + "type": "array", + "items": { + } + }, + "identification_format_version": { + "description": "The output format's version", + "type": "integer", + "minimum": 1 + }, + "track_tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "num_entries": { + "type": "integer" + }, + "track_id": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "num_entries", + "track_id" + ] + } + }, + "tracks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "codec": { + "type": "string", + "minLength": 1 + }, + "id": { + "type": "integer", + "minLength": 0 + }, + "type": { + "type": "string" + }, + "properties": { + "type": "object", + "properties": { + "aac_is_sbr": { + "type": "string", + "enum": [ + "true", + "false", + "unknown" + ] + }, + "audio_bits_per_sample": { + "type": "integer", + "minimum": 0 + }, + "audio_channels": { + "type": "integer", + "minimum": 0 + }, + "audio_sampling_frequency": { + "type": "integer", + "minimum": 0 + }, + "codec_id": { + "type": "string" + }, + "codec_private_data": { + "type": "string" + }, + "codec_private_length": { + "type": "integer", + "minimum": 0 + }, + "content_encoding_algorithms": { + "type": "string", + "minLength": 1 + }, + "default_duration": { + "type": "integer", + "minimum": 0 + }, + "default_track": { + "type": "boolean" + }, + "display_dimensions": { + "type": "string", + "pattern": "^[0-9]+x[0-9]+$" + }, + "enabled_track": { + "type": "boolean" + }, + "forced_track": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "number": { + "type": "integer", + "minimum": 0 + }, + "packetizer": { + "type": "string", + "minLength": 1 + }, + "pixel_dimensions": { + "type": "string", + "pattern": "^[0-9]+x[0-9]+$" + }, + "stereo_mode": { + "type": "integer", + "minimum": 0 + }, + "stream_id": { + "type": "string", + "pattern": "^[0-9a-f]{2}$" + }, + "sub_stream_id": { + "type": "string", + "pattern": "^[0-9a-f]{2}$" + }, + "tag_artist": { + "type": "string" + }, + "tag_bitsps": { + "type": "string" + }, + "tag_bps": { + "type": "string" + }, + "tag_fps": { + "type": "string" + }, + "tag_title": { + "type": "string" + }, + "text_subtitles": { + "type": "boolean" + }, + "track_name": { + "type": "string" + }, + "ts_pid": { + "type": "integer", + "minimum": 0 + }, + "uid": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [ + "codec", + "id", + "type" + ] + } + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/src/common/output.cpp b/src/common/output.cpp index 7cc2f7f39..fd49d3ef6 100644 --- a/src/common/output.cpp +++ b/src/common/output.cpp @@ -33,6 +33,44 @@ charset_converter_cptr g_cc_stdio = charset_converter_cptr(new charset_converter std::shared_ptr g_mm_stdio = std::shared_ptr(new mm_stdio_c); static mxmsg_handler_t s_mxmsg_info_handler, s_mxmsg_warning_handler, s_mxmsg_error_handler; +static std::vector s_warnings_emitted, s_errors_emitted; + +static nlohmann::json +to_json_array(std::vector const &messages) { + auto result = nlohmann::json::array(); + + for (auto const &message : messages) + result.push_back(message); + + return result; +} + +void +display_json_output(nlohmann::json json) { + json["warnings"] = to_json_array(s_warnings_emitted); + json["errors"] = to_json_array(s_errors_emitted); + + mxinfo(boost::format("%1%\n") % json.dump(2)); +} + +static void +json_warning_error_handler(unsigned int level, + std::string const &message) { + if (MXMSG_WARNING == level) + s_warnings_emitted.push_back(message); + + else { + s_errors_emitted.push_back(message); + display_json_output(nlohmann::json{}); + mxexit(2); + } +} + +void +redirect_warnings_and_errors_to_json() { + set_mxmsg_handler(MXMSG_WARNING, json_warning_error_handler); + set_mxmsg_handler(MXMSG_ERROR, json_warning_error_handler); +} void redirect_stdio(const mm_io_cptr &stdio) { diff --git a/src/common/output.h b/src/common/output.h index b26f4b47f..0fbe241b4 100644 --- a/src/common/output.h +++ b/src/common/output.h @@ -22,6 +22,7 @@ #include "common/locale.h" #include "common/mm_io.h" +#include "nlohmann-json/src/json.hpp" using namespace libebml; @@ -36,6 +37,9 @@ extern std::shared_ptr g_mm_stdio; void redirect_stdio(const mm_io_cptr &new_stdio); bool stdio_redirected(); +void redirect_warnings_and_errors_to_json(); +void display_json_output(nlohmann::json json); + void init_common_output(bool no_charset_detection); void set_cc_stdio(const std::string &charset); diff --git a/src/merge/generic_reader.cpp b/src/merge/generic_reader.cpp index 8a861d9df..1b6c1f3a8 100644 --- a/src/merge/generic_reader.cpp +++ b/src/merge/generic_reader.cpp @@ -415,7 +415,7 @@ generic_reader_c::display_identification_results_as_json() { }; auto json = nlohmann::json{ - { "identification_format_version", 1 }, + { "identification_format_version", 2 }, { "file_name", m_ti.m_fname }, { "tracks", nlohmann::json::array() }, { "attachments", nlohmann::json::array() }, @@ -465,7 +465,7 @@ generic_reader_c::display_identification_results_as_json() { }; } - mxinfo(boost::format("%1%\n") % json.dump(2)); + display_json_output(json); } std::string diff --git a/src/merge/id_result.cpp b/src/merge/id_result.cpp index 17938480f..f3c3c5e13 100644 --- a/src/merge/id_result.cpp +++ b/src/merge/id_result.cpp @@ -33,7 +33,7 @@ static void output_container_unsupported_json(std::string const &filename, translatable_string_c const &info) { auto json = nlohmann::json{ - { "identification_format_version", 1 }, + { "identification_format_version", 2 }, { "file_name", filename }, { "container", { { "recognized", true }, @@ -42,7 +42,7 @@ output_container_unsupported_json(std::string const &filename, } }, }; - mxinfo(boost::format("%1%\n") % json.dump(2)); + display_json_output(json); mxexit(0); } diff --git a/src/merge/mkvmerge.cpp b/src/merge/mkvmerge.cpp index c4fb55ea9..28c7552fd 100644 --- a/src/merge/mkvmerge.cpp +++ b/src/merge/mkvmerge.cpp @@ -369,7 +369,7 @@ list_file_types() { static void display_unsupported_file_type_json(filelist_t const &file) { auto json = nlohmann::json{ - { "identification_format_version", 1 }, + { "identification_format_version", 2 }, { "file_name", file.name }, { "container", { { "recognized", false }, @@ -377,7 +377,7 @@ display_unsupported_file_type_json(filelist_t const &file) { } }, }; - mxinfo(boost::format("%1%\n") % json.dump(2)); + display_json_output(json); mxexit(0); } @@ -1843,10 +1843,11 @@ parse_arg_identification_format(std::vector::const_iterator &sit, else if (next_arg == "gui") g_identification_output_format = identification_output_format_e::gui; - else if (next_arg == "json") + else if (next_arg == "json") { g_identification_output_format = identification_output_format_e::json; + redirect_warnings_and_errors_to_json(); - else + } else mxerror(boost::format(Y("Invalid identification format in '%1% %2%'.\n")) % *sit % *(sit + 1)); ++sit; diff --git a/tests/results.txt b/tests/results.txt index 5206ff208..591645fbc 100644 --- a/tests/results.txt +++ b/tests/results.txt @@ -357,6 +357,6 @@ T_508splitting_by_parts_with_segment_linking:existence0-true-true-true-existence T_509rerender_track_headers_chapters_attachments:1ad646e49e231108ed8b5d1c6dce8b1c:passed:20151115-230226:0.287840782 T_510propedit_add_attachments_without_meta_seek_present:770103c238a0f502c9ec55f0599d8544:passed:20151121-101043:0.070892905 T_511propedit_ensure_seek_head_exists_at_front:20f53afd94e39f5bbf3f1091eefbe31d:passed:20151129-194025:0.152563199 -T_512json_identification:231941d7da79ca0eab3f8f5cd2a66e3c-ok-793233ac68c8c0af280898ec33aa6e05-ok-06dde7e7bcbec8fc9cd8f101662906dd-ok-8797f2adcd279428afa4834d93f0be34-ok-90de565f88767c95673e7b2d5ecf85d9-ok-d9c71aaad86e27979d1b5bf71cb1143f-ok-acddfd1324aa62e187f8d4023f24ec2a-ok-6c20b604bd1652766e442bf52f284a18-ok-cdf24911aad494277fd2a032affdf498-ok-dd2d4b55d2fe3f56c697371325b7d8e9-ok-7dc0464e838aa187128964387b9adcba-ok-6d365dd230156e30bfe3d60a3c6c24dc-ok-7fada0edcbd9818093ba57b78ae5706a-ok-ca5e66fad9ed5af2d672c4432cbded48-ok-b859b2500847aca0ac91918fdc8eae70-ok:passed:20151207-223859:6.280036064 +T_512json_identification:dc56910afee27e5f42414fde294a262c-ok-4d5b44ce8fea381a4de100ed77ee77bc-ok-4ce52c415319a3c9ace3394b251bfa04-ok-b31447af73fb7453a6801f6817a5f904-ok-d9eb73880861a423ee0d33364685d0a5-ok-4147bc09272d6a650f3ebac47008129a-ok-966a3a948e86b73f25d0cbd20e659dda-ok-5105d97f6ca79db07caab7bb66f12ef4-ok-76d5c58b6fc06efcfea66e447ad8bf44-ok-bb52b7e2c30f3741c92e5152bef8ea8a-ok-8dd46981cf9e1787ea682fe03aba4d92-ok-ebaf88003f5d09295d18e255edf689be-ok-c56940a2497513380531e693ec06b76c-ok-ee1ae1f2602ebaef4e83772ab5e39804-ok-617e011e200630baff85bc674b2a1292-ok:passed:20151207-223859:6.280036064 T_513vp9_10bit_key_frame_detection:9eab6e85ec792dcf670873d70a87f6ea:passed:20151208-224613:0.267556245 T_514remove_track_statistics_tags_during_remux:022578a22c45c06ab23dc453df71f7c0-afe190e36be530592fe3b83fb28d3e69-a7f246fe02132a1fb9cd3d7d0f85f180:passed:20151215-134129:1.426290351 diff --git a/tests/test.d/simple_test.rb b/tests/test.d/simple_test.rb index 382980c01..ab96d3dd1 100644 --- a/tests/test.d/simple_test.rb +++ b/tests/test.d/simple_test.rb @@ -302,7 +302,7 @@ class SimpleTest json_store = JsonSchema::DocumentStore.new parser = JsonSchema::Parser.new expander = JsonSchema::ReferenceExpander.new - schema = parser.parse JSON.load(File.read("../doc/json-schema/mkvmerge-identification-output-schema-v1.json")) + schema = parser.parse JSON.load(File.read("../doc/json-schema/mkvmerge-identification-output-schema-v2.json")) expander.expand(schema, store: json_store) json_store.add_schema schema