From c9eb86f88db377df2b63d8775b795a74cae82ace Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Sun, 31 Jan 2016 13:30:25 +0100 Subject: [PATCH] mkvmerge: fix "playlist_file" type in JSON output "playlist_file" must be an array of strings, not a single string. In verbose identification output mode this property has always been output multiple times. In JSON only one of those was actually output, the last one. Additionally the JSON schema didn't contain "playlist_file" at all. --- ChangeLog | 7 + ...merge-identification-output-schema-v3.json | 353 ++++++++++++++++++ src/common/mm_mpls_multi_file_io.cpp | 6 +- src/merge/generic_reader.cpp | 9 +- src/merge/id_result.cpp | 4 +- src/merge/id_result.h | 2 + src/merge/mkvmerge.cpp | 4 +- tests/results.txt | 4 +- tests/test-512json_identification.rb | 8 +- tests/test-520truehd_mlp_atmos_detection.rb | 3 +- tests/test.d/simple_test.rb | 2 +- 11 files changed, 390 insertions(+), 12 deletions(-) create mode 100644 doc/json-schema/mkvmerge-identification-output-schema-v3.json diff --git a/ChangeLog b/ChangeLog index f2109f165..d3bf7ba30 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2016-01-31 Moritz Bunkus + + * mkvmerge: bug fix: fixed the output of the "playlist_file" + property of the "container" entity in the JSON identification + format from a single string to an array of strings. The format + version has been bumped to 3 due to this change. + 2016-01-30 Moritz Bunkus * docs: added a Polish translation of the man pages by Daniel Kluz diff --git a/doc/json-schema/mkvmerge-identification-output-schema-v3.json b/doc/json-schema/mkvmerge-identification-output-schema-v3.json new file mode 100644 index 000000000..4c039f66d --- /dev/null +++ b/doc/json-schema/mkvmerge-identification-output-schema-v3.json @@ -0,0 +1,353 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://mkvtoolnix.download/doc/mkvmerge-identification-output-schema-v3.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_file": { + "description": "An array of file names the playlist contains", + "type": "array", + "items": { + "type": "string" + } + }, + "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": 3, + "maximum": 3 + }, + "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/mm_mpls_multi_file_io.cpp b/src/common/mm_mpls_multi_file_io.cpp index ab9f32ceb..e7154c8a5 100644 --- a/src/common/mm_mpls_multi_file_io.cpp +++ b/src/common/mm_mpls_multi_file_io.cpp @@ -101,6 +101,10 @@ mm_mpls_multi_file_io_c::create_verbose_identification_info(mtx::id::info_c &inf info.add(mtx::id::playlist_duration, m_mpls_parser->get_playlist().duration.to_ns()); info.add(mtx::id::playlist_size, m_total_size); info.add(mtx::id::playlist_chapters, m_mpls_parser->get_chapters().size()); + + auto file_names = nlohmann::json::array(); for (auto &file : m_files) - info.add(mtx::id::playlist_file, file.string()); + file_names.push_back(file.string()); + + info.add(mtx::id::playlist_file, file_names); } diff --git a/src/merge/generic_reader.cpp b/src/merge/generic_reader.cpp index f10b41c79..c079c558e 100644 --- a/src/merge/generic_reader.cpp +++ b/src/merge/generic_reader.cpp @@ -320,6 +320,13 @@ generic_reader_c::display_identification_results_as_text() { auto formatter = boost::format{"%1%:%2%"}; for (auto const &pair : info) { + if (pair.second.is_array()) { + for (auto it = pair.second.begin(), end = pair.second.end(); it != end; ++it) + formatted.emplace_back((formatter % escape(pair.first) % escape(it->get())).str()); + + continue; + } + auto value = pair.second.is_number() ? to_string(pair.second.get()) : pair.second.is_boolean() ? std::string{pair.second.get() ? "1" : "0"} : pair.second.get(); @@ -415,7 +422,7 @@ generic_reader_c::display_identification_results_as_json() { }; auto json = nlohmann::json{ - { "identification_format_version", 2 }, + { "identification_format_version", ID_JSON_FORMAT_VERSION }, { "file_name", m_ti.m_fname }, { "tracks", nlohmann::json::array() }, { "attachments", nlohmann::json::array() }, diff --git a/src/merge/id_result.cpp b/src/merge/id_result.cpp index 07daa7553..ab364fde1 100644 --- a/src/merge/id_result.cpp +++ b/src/merge/id_result.cpp @@ -33,8 +33,8 @@ static void output_container_unsupported_json(std::string const &filename, translatable_string_c const &info) { auto json = nlohmann::json{ - { "identification_format_version", 2 }, - { "file_name", filename }, + { "identification_format_version", ID_JSON_FORMAT_VERSION }, + { "file_name", filename }, { "container", { { "recognized", true }, { "supported", false }, diff --git a/src/merge/id_result.h b/src/merge/id_result.h index 3264f014e..3e02d3707 100644 --- a/src/merge/id_result.h +++ b/src/merge/id_result.h @@ -27,6 +27,8 @@ #define ID_RESULT_TAGS "tags" #define ID_RESULT_GLOBAL_TAGS_ID -1 +#define ID_JSON_FORMAT_VERSION 3 + struct id_result_t { int64_t id; std::string type, info, description; diff --git a/src/merge/mkvmerge.cpp b/src/merge/mkvmerge.cpp index 9da1a4235..95ff098f0 100644 --- a/src/merge/mkvmerge.cpp +++ b/src/merge/mkvmerge.cpp @@ -369,8 +369,8 @@ list_file_types() { static void display_unsupported_file_type_json(filelist_t const &file) { auto json = nlohmann::json{ - { "identification_format_version", 2 }, - { "file_name", file.name }, + { "identification_format_version", ID_JSON_FORMAT_VERSION }, + { "file_name", file.name }, { "container", { { "recognized", false }, { "supported", false }, diff --git a/tests/results.txt b/tests/results.txt index aeb316292..e3416b43b 100644 --- a/tests/results.txt +++ b/tests/results.txt @@ -357,7 +357,7 @@ T_508splitting_by_parts_with_segment_linking:existence0-true-true-true-existence T_509rerender_track_headers_chapters_attachments:76712bb36be12e38e98a1342452f28d5: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:dc56910afee27e5f42414fde294a262c+ok-4d5b44ce8fea381a4de100ed77ee77bc+ok-4ce52c415319a3c9ace3394b251bfa04+ok-b31447af73fb7453a6801f6817a5f904+ok-35d9a8927414617bce87deeeacb24a0b+ok-1d7e45d0520f72ac8cc40b4a31f62ce2+ok-7c4bfe1e467c782175892a155c9769aa+ok-2f1b1e7b1845e40b66c42c555e2214bd+ok-76d5c58b6fc06efcfea66e447ad8bf44+ok-4f84d928bfa74c33a5fd2f08f8e138b7+ok-8dd46981cf9e1787ea682fe03aba4d92+ok-e0fd5247650d9eee6a259b0a796cbcb0+ok-c56940a2497513380531e693ec06b76c+ok-ee1ae1f2602ebaef4e83772ab5e39804+ok-617e011e200630baff85bc674b2a1292+ok:passed:20151207-223859:6.280036064 +T_512json_identification:e2c1bd814ea805d9711a3875e646a51d+ok-d815a6390e25b861d21093ef66f17191+ok-19f449d85ad9c8c2a3d3840bc3734e58+ok-a86b43982a842c6b9c8572534e40dcca+ok-bf4664fb8aac8ad51e16d018d2c6697e+ok-e6e342718925ef84c9c65288d477ac76+ok-59c47f40f7c35d90a4a8ba0f15aebf5b+ok-b524a708c8e65517026daf4e18c6efcf+ok-eabca6419fcd27291e5db66c1e391c0d+ok-01f6792cb05fb5dd4823a5919a5565b4+ok-3f9fd90d34f591fa205eb235b1efe36c+ok-f7c9d7c612f38a0a2c0548a38008882f+ok-10bd29b4b37ba8e8fd746a45e67af4a1+ok-b137e5af83e68f2c52af9bfe3976a977+ok-c8b2bd3f66486fc47ad98d7b6a06f713+ok:passed:20151207-223859:1.876447112 T_513vp9_10bit_key_frame_detection:9eab6e85ec792dcf670873d70a87f6ea:passed:20151208-224613:0.267556245 T_514remove_track_statistics_tags_during_remux:43d4c0ec5cbbc31018715e62a99377df-afe190e36be530592fe3b83fb28d3e69-a7f246fe02132a1fb9cd3d7d0f85f180:passed:20151215-134129:1.426290351 T_515aac_sampling_frequency_8000_is_not_sbr:b8f857faf759eae5362fefbb7cbeed23:passed:20151219-130357:0.066237884 @@ -365,7 +365,7 @@ T_516hevc_rap_sample_grouping:bb42041df575edd35f36d47aebc341a7:passed:20151228-1 T_517h264_forbidden_byte_sequence_in_slice_nalu:74e0fb1a25397078335c6d94974dc168:passed:20151228-134333:3.276176597 T_518mlp:bc0269df9e0c0c968e4a408d7da46d35-0624d3f1975273199a0e12a9f4b0fd20:passed:20151229-134422:0.895368448 T_519truehd:33f8c0f013c71529281179cf8669c567-33f8c0f013c71529281179cf8669c567:passed:20151229-135950:3.160459472 -T_520truehd_mlp_atmos_detection:790fe7a0cca6dfe0f4439fb36afd9d40+true-dc4a06c661033cb21e70bb25b661130a+true-565bc55dc9b15f81064785367040a978+true-93866c2e47b7aa3b8cf2483e8beb09ac+true-39d23fdc03248412b4bd364e51e4cf23+true-b41aaef4bd7e736256ff497be64e1e1f+true:passed:20151229-160649:5.913862696 +T_520truehd_mlp_atmos_detection:9337a350fa1e3451ff22b16afe0c770c+true-307f37e86e9df0245aca5908c0409c66+true-e1d9b0e42dc386b54916cfd8d6f6722b+true-531bd72e14816ccacd33202720f94544+true-12b89a0d1fe60bb5271bafbf1b34d275+true-01bcbc0330a73eb417d83c5576378466+true:passed:20151229-160649:2.357134495 T_521mp4_edit_list_constant_offset_with_segment_duration_not_0:07984c40325903cbc35c8ae05dc86a72:passed:20151228-185646:0.621563048 T_522mpeg_1_2_es_no_start_code_at_beginning:067554d94399178b6bb56b49746b04b6:passed:20151230-182435:0.299243222 T_523mpeg_ts_pes_size_0:5351b2c74ad4327eeecee1ccf658ef5f-217f7e6e3e04a6afe253487af08c0598:passed:20151230-221825:1.635929691 diff --git a/tests/test-512json_identification.rb b/tests/test-512json_identification.rb index 177a2e96a..c1f293bdc 100755 --- a/tests/test-512json_identification.rb +++ b/tests/test-512json_identification.rb @@ -26,15 +26,19 @@ test "identification and validation" do files.each do |file| output, _ = identify file, :format => :json output = output.join '' + json = JSON.load(output) - valid, errors = json_schema_identification.validate(JSON.load(output)) + valid, errors = json_schema_identification.validate(json) if !valid puts " JSON validation errors in #{file}:" puts errors.join("\n") end - hashes << "#{output.md5}+#{valid ? "ok" : "invalid"}" + json.delete("identification_format_version") + json_md5 = JSON.dump(json).md5 + + hashes << "#{json_md5}+#{valid ? "ok" : "invalid"}" end hashes.join '-' diff --git a/tests/test-520truehd_mlp_atmos_detection.rb b/tests/test-520truehd_mlp_atmos_detection.rb index 6bfc94380..b74ab0af2 100755 --- a/tests/test-520truehd_mlp_atmos_detection.rb +++ b/tests/test-520truehd_mlp_atmos_detection.rb @@ -22,7 +22,8 @@ def verify520 file, track, codec end - "#{output.md5}+#{ok}" + json.delete("identification_format_version") + JSON.dump(json).md5 + "+#{ok}" end end diff --git a/tests/test.d/simple_test.rb b/tests/test.d/simple_test.rb index eeaf7b032..8de583af9 100644 --- a/tests/test.d/simple_test.rb +++ b/tests/test.d/simple_test.rb @@ -308,7 +308,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-v2.json")) + schema = parser.parse JSON.load(File.read("../doc/json-schema/mkvmerge-identification-output-schema-v3.json")) expander.expand(schema, store: json_store) json_store.add_schema schema