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.
This commit is contained in:
Moritz Bunkus 2016-01-31 13:30:25 +01:00
parent ddfb5cc92e
commit c9eb86f88d
11 changed files with 390 additions and 12 deletions

View File

@ -1,3 +1,10 @@
2016-01-31 Moritz Bunkus <moritz@bunkus.org>
* 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 <moritz@bunkus.org> 2016-01-30 Moritz Bunkus <moritz@bunkus.org>
* docs: added a Polish translation of the man pages by Daniel Kluz * docs: added a Polish translation of the man pages by Daniel Kluz

View File

@ -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"
}
}
}
}

View File

@ -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_duration, m_mpls_parser->get_playlist().duration.to_ns());
info.add(mtx::id::playlist_size, m_total_size); info.add(mtx::id::playlist_size, m_total_size);
info.add(mtx::id::playlist_chapters, m_mpls_parser->get_chapters().size()); info.add(mtx::id::playlist_chapters, m_mpls_parser->get_chapters().size());
auto file_names = nlohmann::json::array();
for (auto &file : m_files) 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);
} }

View File

@ -320,6 +320,13 @@ generic_reader_c::display_identification_results_as_text() {
auto formatter = boost::format{"%1%:%2%"}; auto formatter = boost::format{"%1%:%2%"};
for (auto const &pair : info) { 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<std::string>())).str());
continue;
}
auto value = pair.second.is_number() ? to_string(pair.second.get<uint64_t>()) auto value = pair.second.is_number() ? to_string(pair.second.get<uint64_t>())
: pair.second.is_boolean() ? std::string{pair.second.get<bool>() ? "1" : "0"} : pair.second.is_boolean() ? std::string{pair.second.get<bool>() ? "1" : "0"}
: pair.second.get<std::string>(); : pair.second.get<std::string>();
@ -415,7 +422,7 @@ generic_reader_c::display_identification_results_as_json() {
}; };
auto json = nlohmann::json{ auto json = nlohmann::json{
{ "identification_format_version", 2 }, { "identification_format_version", ID_JSON_FORMAT_VERSION },
{ "file_name", m_ti.m_fname }, { "file_name", m_ti.m_fname },
{ "tracks", nlohmann::json::array() }, { "tracks", nlohmann::json::array() },
{ "attachments", nlohmann::json::array() }, { "attachments", nlohmann::json::array() },

View File

@ -33,8 +33,8 @@ static void
output_container_unsupported_json(std::string const &filename, output_container_unsupported_json(std::string const &filename,
translatable_string_c const &info) { translatable_string_c const &info) {
auto json = nlohmann::json{ auto json = nlohmann::json{
{ "identification_format_version", 2 }, { "identification_format_version", ID_JSON_FORMAT_VERSION },
{ "file_name", filename }, { "file_name", filename },
{ "container", { { "container", {
{ "recognized", true }, { "recognized", true },
{ "supported", false }, { "supported", false },

View File

@ -27,6 +27,8 @@
#define ID_RESULT_TAGS "tags" #define ID_RESULT_TAGS "tags"
#define ID_RESULT_GLOBAL_TAGS_ID -1 #define ID_RESULT_GLOBAL_TAGS_ID -1
#define ID_JSON_FORMAT_VERSION 3
struct id_result_t { struct id_result_t {
int64_t id; int64_t id;
std::string type, info, description; std::string type, info, description;

View File

@ -369,8 +369,8 @@ list_file_types() {
static void static void
display_unsupported_file_type_json(filelist_t const &file) { display_unsupported_file_type_json(filelist_t const &file) {
auto json = nlohmann::json{ auto json = nlohmann::json{
{ "identification_format_version", 2 }, { "identification_format_version", ID_JSON_FORMAT_VERSION },
{ "file_name", file.name }, { "file_name", file.name },
{ "container", { { "container", {
{ "recognized", false }, { "recognized", false },
{ "supported", false }, { "supported", false },

View File

@ -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_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_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_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_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_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 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_517h264_forbidden_byte_sequence_in_slice_nalu:74e0fb1a25397078335c6d94974dc168:passed:20151228-134333:3.276176597
T_518mlp:bc0269df9e0c0c968e4a408d7da46d35-0624d3f1975273199a0e12a9f4b0fd20:passed:20151229-134422:0.895368448 T_518mlp:bc0269df9e0c0c968e4a408d7da46d35-0624d3f1975273199a0e12a9f4b0fd20:passed:20151229-134422:0.895368448
T_519truehd:33f8c0f013c71529281179cf8669c567-33f8c0f013c71529281179cf8669c567:passed:20151229-135950:3.160459472 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_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_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 T_523mpeg_ts_pes_size_0:5351b2c74ad4327eeecee1ccf658ef5f-217f7e6e3e04a6afe253487af08c0598:passed:20151230-221825:1.635929691

View File

@ -26,15 +26,19 @@ test "identification and validation" do
files.each do |file| files.each do |file|
output, _ = identify file, :format => :json output, _ = identify file, :format => :json
output = output.join '' 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 if !valid
puts " JSON validation errors in #{file}:" puts " JSON validation errors in #{file}:"
puts errors.join("\n") puts errors.join("\n")
end 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 end
hashes.join '-' hashes.join '-'

View File

@ -22,7 +22,8 @@ def verify520 file, track, codec
end end
"#{output.md5}+#{ok}" json.delete("identification_format_version")
JSON.dump(json).md5 + "+#{ok}"
end end
end end

View File

@ -308,7 +308,7 @@ class SimpleTest
json_store = JsonSchema::DocumentStore.new json_store = JsonSchema::DocumentStore.new
parser = JsonSchema::Parser.new parser = JsonSchema::Parser.new
expander = JsonSchema::ReferenceExpander.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) expander.expand(schema, store: json_store)
json_store.add_schema schema json_store.add_schema schema