mkvmerge: add track's minimum timestamp to identification output for Matroska

Frontends like gMKVExtractGUI currently use mkvinfo for determining the
offset between tracks. This is an attempt to provide that information
with mkvmerge.
This commit is contained in:
Moritz Bunkus 2016-04-02 15:14:19 +02:00
parent 90687297a8
commit c8ba161c8e
8 changed files with 472 additions and 7 deletions

View File

@ -1,3 +1,11 @@
2016-04-02 Moritz Bunkus <moritz@bunkus.org>
* mkvmerge: enhancement: added the minimum timestamp for each
track in verbose/JSON identification outputs (key
"minimum_timestamp") when identifying Matroska files. At most the
first ten seconds are probed; if no block is found for a track
within that range then the key is not output for the track.
2016-03-31 Moritz Bunkus <moritz@bunkus.org>
* MKVToolNix GUI: merge tool change: attachments from source files

View File

@ -0,0 +1,370 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://mkvtoolnix.download/doc/mkvmerge-identification-output-schema-v4.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": "An array of names of additional files processed as well",
"type": "array",
"items": {
"type": "string"
}
},
"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": {
"type": "object",
"properties": {
"num_entries": {
"type": "integer"
}
},
"additionalProperties": false,
"required": [
"num_entries"
]
}
},
"identification_format_version": {
"description": "The output format's version",
"type": "integer",
"minimum": 4,
"maximum": 4
},
"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"
},
"minimum_timestamp": {
"description": "The minimum timestamp in nanoseconds of all the frames of this track found within the first couple of seconds of the file",
"type": "integer",
"minimum": 0
},
"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

@ -36,6 +36,7 @@ char const * const duration = "duration";
char const * const enabled_track = "enabled_track"; // track boolean
char const * const forced_track = "forced_track"; // track boolean
char const * const language = "language"; // track ascii-string format:^\w{3}$
char const * const minimum_timestamp = "minimum_timestamp"; // track unsigned-integer
char const * const mpeg4_p10_es_video = "mpeg4_p10_es_video"; // track boolean
char const * const mpeg4_p10_video = "mpeg4_p10_video"; // track boolean
char const * const mpegh_p2_es_video = "mpegh_p2_es_video"; // track boolean

View File

@ -2303,8 +2303,88 @@ kax_reader_c::set_headers() {
PTZR(track->ptzr)->get_track_entry()->EnableLacing(track->lacing_flag);
}
std::unordered_map<uint64_t, timestamp_c>
kax_reader_c::determine_minimum_timestamps() {
if (m_tracks.empty())
return {};
std::unordered_map<uint64_t, timestamp_c> timestamps_by_number;
std::unordered_map<uint64_t, kax_track_cptr> tracks_by_number;
timestamp_c first_timestamp, last_timestamp;
auto probe_time_limit = timestamp_c::s(10);
auto video_time_limit = timestamp_c::s(1);
for (auto &track : m_tracks)
tracks_by_number[track->track_number] = track;
while (true) {
try {
auto cluster = std::shared_ptr<KaxCluster>(m_in_file->read_next_cluster());
if (!cluster)
return timestamps_by_number;
auto cluster_tc = FindChildValue<KaxClusterTimecode>(*cluster);
cluster->InitTimecode(cluster_tc, m_tc_scale);
for (auto const &element : *cluster) {
uint64_t track_number{};
if (Is<KaxSimpleBlock>(element)) {
auto block = static_cast<KaxSimpleBlock *>(element);
block->SetParent(*cluster);
last_timestamp = timestamp_c::ns(block->GlobalTimecode());
track_number = block->TrackNum();
} else if (Is<KaxBlockGroup>(element)) {
auto block = FindChild<KaxBlock>(static_cast<KaxBlockGroup *>(element));
if (!block)
continue;
block->SetParent(*cluster);
last_timestamp = timestamp_c::ns(block->GlobalTimecode());
track_number = block->TrackNum();
} else
continue;
if (!first_timestamp.valid())
first_timestamp = last_timestamp;
if ((last_timestamp - first_timestamp) >= probe_time_limit)
return timestamps_by_number;
auto &track = tracks_by_number[track_number];
if (!track)
continue;
auto &recorded_timestamp = timestamps_by_number[track_number];
if (!recorded_timestamp.valid() || (last_timestamp < recorded_timestamp))
recorded_timestamp = last_timestamp;
if ( (track->type == 'v')
&& ((last_timestamp - recorded_timestamp) < video_time_limit))
continue;
tracks_by_number.erase(track_number);
if (tracks_by_number.empty())
return timestamps_by_number;
}
} catch (...) {
break;
}
}
return timestamps_by_number;
}
void
kax_reader_c::identify() {
auto minimum_timestamps_by_number = determine_minimum_timestamps();
auto info = mtx::id::info_c{};
if (!m_title.empty())
@ -2389,6 +2469,10 @@ kax_reader_c::identify() {
track->add_track_tags_to_identification(info);
auto &minimum_timestamp = minimum_timestamps_by_number[track->track_number];
if (minimum_timestamp.valid())
info.set(mtx::id::minimum_timestamp, minimum_timestamp.to_ns());
id_result_track(track->tnum,
track->type == 'v' ? ID_RESULT_TRACK_VIDEO
: track->type == 'a' ? ID_RESULT_TRACK_AUDIO

View File

@ -302,6 +302,8 @@ protected:
void init_l1_position_storage(deferred_positions_t &storage);
virtual bool has_deferred_element_been_processed(deferred_l1_type_e type, int64_t position);
virtual std::unordered_map<uint64_t, timestamp_c> determine_minimum_timestamps();
};
#endif // MTX_INPUT_R_MATROSKA_H

View File

@ -27,7 +27,7 @@
#define ID_RESULT_TAGS "tags"
#define ID_RESULT_GLOBAL_TAGS_ID -1
#define ID_JSON_FORMAT_VERSION 3
#define ID_JSON_FORMAT_VERSION 4
struct id_result_t {
int64_t id;

View File

@ -63,7 +63,7 @@ T_213mp4_broken_pixel_dimensions:b0ffac7af09e87a2e4c7ace7b09b1b46:passed:2005091
T_214one_frame_avi:a4b223e7f22b5e3c2bcf70e455188f79:passed:20051004-192755:0.039489971
T_215X_codec_extradata_avi:6d668338e66c695c72098902b0ce513c-74ac799ad899f703cbb6c6654e5f9f51:passed:20051004-194707:0.052219855
T_216mp4_editlists:5c548c99e1fa4f27afaa5c4fe198924c:passed:20051118-191453:0.106975045
T_217file_identification:7a2a506954a56f21739e7912897e07ad-903514f1cd84055d9b06ecff5e8d1ea1-d02809ab6dc92a41ef60b571bd117e4d-9b7ea962c56888037231707f9311e080-279167d14c30edc4788cc066f22c941f-ccd6c289299801382b09a9bc13326092-44256b48783ae449ceca65e8419d72e8-cd6039be3553f8bf857c77c8bb99ad1b-55543e6744a979d4a3ffbf0e0bcc855c-d2389a900373adc4864fb3534e145fb0-7febbb46072c2b256735786471c02df4-e2853a83b5964834faa6aa34318aad9c-734e635a1b254319593e24ba89e2434c-1c31748e6eaabc9b84eb737124f457d3-f4bf52b0bf7c773ec337a9d29043b84c-ab8604871b63846cd7df9d5282db3f35-2b05e8b45ff5b8568be5f4aed1bd18bc-2000bb80eaecc682af955c3d64847fc8-3b9e8de7136f2fffa4e2bbf1b4aa38e0-db05f705a1e059b29f4db1ea3ee9d59e-e38a8502cd0c407d8ce517913a2db8c0-588d6dd39935990b73cbb2158cf960fe:passed:20051209-180815:1.882517588
T_217file_identification:7a2a506954a56f21739e7912897e07ad-903514f1cd84055d9b06ecff5e8d1ea1-5079cbde3f69747c622c08b7fdcee66f-80fa1910c6a50d00753313ebd89e67ab-279167d14c30edc4788cc066f22c941f-ccd6c289299801382b09a9bc13326092-44256b48783ae449ceca65e8419d72e8-cd6039be3553f8bf857c77c8bb99ad1b-55543e6744a979d4a3ffbf0e0bcc855c-d2389a900373adc4864fb3534e145fb0-7febbb46072c2b256735786471c02df4-e2853a83b5964834faa6aa34318aad9c-734e635a1b254319593e24ba89e2434c-1c31748e6eaabc9b84eb737124f457d3-f4bf52b0bf7c773ec337a9d29043b84c-ab8604871b63846cd7df9d5282db3f35-2b05e8b45ff5b8568be5f4aed1bd18bc-2000bb80eaecc682af955c3d64847fc8-3b9e8de7136f2fffa4e2bbf1b4aa38e0-db05f705a1e059b29f4db1ea3ee9d59e-e38a8502cd0c407d8ce517913a2db8c0-588d6dd39935990b73cbb2158cf960fe:passed:20051209-180815:1.882517588
T_218theora:eb650da44ce14cd749ebfb94d270e33c-902b1d711e150c3e923aa43a88970ff4:passed:20060428-105054:0.392912102
T_219srt_short_timecodes:4d58c1d5ddab6368080d54a7585b0f83:passed:20060926-112658:0.117747192
T_220ass_with_comments_at_start:30926355189808086b52edf95c8f49d0:passed:20060926-120101:0.382410266
@ -190,7 +190,7 @@ T_341vob_interlaced_mpeg2:c51b19eee5fad05ab8cde09ae561677b:passed:20120304-16331
T_342m2ts_interlaced_h264_from_arte:b02e855c2c8bcdde94081df8f43f895f:passed:20120304-165917:4.5443942
T_343m2ts_interlaced_h264_match_of_the_day:5b902a498c5e473ec584a2d1bb39517b:passed:20120304-171453:3.858753744
T_344microdvd_recognition:ok:passed:20120304-175209:0.114439546
T_345flag_enabled:105245e80fc4a79be3ea27a50ae4564e-24dd401b3d6e814e8a412be30e8a0669:passed:20120304-181150:0.410047671
T_345flag_enabled:b7a4b2b1ae9e9b946f828a8272e94158-24dd401b3d6e814e8a412be30e8a0669:passed:20120304-181150:0.410047671
T_347h264_misdetected_as_ac3:f2e3101ea1fad1752d24c9143fbea954:passed:20120305-160017:1.722170533
T_348srt_negative_timecodes2:beaee30a910a72560a844628f3a072ac:passed:20120307-115726:0.120842971
T_349h264_interlaced_default_duration:abc9dd7b4579a2e14783271d1d64d486-051cfe8f2749c2c8bdda0f2e2110af68-051cfe8f2749c2c8bdda0f2e2110af68-ff863149bd403bf445a8542f0d830e99:passed:20120307-184849:7.012794592
@ -273,7 +273,7 @@ T_424avc_recover_point_sei_before_second_field:6398b8fc42da442ccc2d599a9df63d89:
T_425mpeg_ts_timestamp_outlier:a5204ef83771296b32ea8d7052cba956:passed:20140305-203603:2.509694471
T_426extract_write_bom_only_once:a9255d40de93e2731aaead0a746e582f-a9255d40de93e2731aaead0a746e582f:passed:20140310-195606:0.0
T_427ui_locale_pt_BR:8719aedc77a0435129c79e3a061642bf-344b51e9ae6fe2d8ce60fef18ee0e7d1:passed:20140418-103113:0.143370167
T_428mkv_misdetected_as_ass:1f0df0dbdbbe6f310e6edc12fc22b29e:passed:20140518-155446:0.033341203
T_428mkv_misdetected_as_ass:b5818edd4d65f28fae73bfb2e7d3be55:passed:20140518-155446:0.033341203
T_429track_statistics_tags:43d4c0ec5cbbc31018715e62a99377df-b726a7c00558fd45468ceb163016eeb4-68d639b8e42bfd0e2b7b4105dea7c091-e179bf70e6b4f2316292d70f4eda2c2e:passed:20140524-194544:0.635343822
T_430cues_multiple_blocks_same_timecode:f1ab5c927064537eb59ab0f5195d6a1d:passed:20140525-173642:0.033316759
T_431ssa_comments_exclamation_mark:3caa9ad1716134cc1f3e229b88ff94ea:passed:20140618-232324:0.072735677
@ -349,7 +349,7 @@ T_500mp4_eac3_fourcc_ec_3:c98039cef2a6919c6fa3d9b1912873cf:passed:20150621-22424
T_501mpeg_ts_pat_and_pmt_crc_errors:bc9d2e9e0fa7d962b04e719e9bce3856:passed:20150704-110859:0.857051787
T_502ui_locale_sr_RS:650e05c9c091234882980f4c1da05dc4-25fa402006e7971d8e5a2b1139a4c4d8:passed:20150829-213708:0.062939538
T_503pcm_in_mkv_varying_samples_per_packet:091442963a5879e8e1d8fca62458a06a:passed:20151004-215848:0.430291421
T_504dts_96_24_identification:1837ab5b411944e143f9dae6fe6436d8-bb7c41b5aa1b57f18741b792897b0b59-8ad5d7ed98bee700e91689784c82717f:passed:20151006-223804:2.278995107
T_504dts_96_24_identification:1837ab5b411944e143f9dae6fe6436d8-5d139c9c7c7233563b2ab98d5f63deb2-8ad5d7ed98bee700e91689784c82717f:passed:20151006-223804:2.278995107
T_505cisco_talos_can_0036:bf0fedc494cf99a0920d7a6e69edf952-6ef415b0f84d3e5dd435244362a37584:passed:20151020-161153:0.071686357
T_506cisco_talos_can_0037:5461288548eac976164cd13f01bc9426-ed695caee29b1456da8629d38321ec9c-92b7169fc05ddf54c46816869c108f31-54a55a6d87bd4c08269891efb03980b3-fef3d018523c7d1fbed763f6666c1ae2-ac584cc44854f9396739df6e93d78acc-b415b2ef2a6dddf5d89733446fae2970-dd53fee23372c569d35e0b2918d86239:passed:20151020-161234:0.319298931
T_507rerender_track_headers:0572728ed778526af9a0e36c7872fab6-c49848577778e5c5146e5b875e3a07d6:passed:20151022-104930:2.573204876
@ -357,7 +357,7 @@ T_508splitting_by_parts_with_segment_linking:existence0-true-true-true-existence
T_509rerender_track_headers_chapters_attachments:21e5c9991875b114014050d68bd2a31c: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: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_512json_identification:e2c1bd814ea805d9711a3875e646a51d+ok-d815a6390e25b861d21093ef66f17191+ok-19f449d85ad9c8c2a3d3840bc3734e58+ok-a86b43982a842c6b9c8572534e40dcca+ok-bf4664fb8aac8ad51e16d018d2c6697e+ok-e6e342718925ef84c9c65288d477ac76+ok-59c47f40f7c35d90a4a8ba0f15aebf5b+ok-40e49f1a1c18e4a2975e7472e6b20c60+ok-198c8949e157e08526189cc0aec964c8+ok-01f6792cb05fb5dd4823a5919a5565b4+ok-3f9fd90d34f591fa205eb235b1efe36c+ok-f7c9d7c612f38a0a2c0548a38008882f+ok-4acc3a60e97069072c3e586888a32776+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

View File

@ -319,7 +319,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-v3.json"))
schema = parser.parse JSON.load(File.read("../doc/json-schema/mkvmerge-identification-output-schema-v4.json"))
expander.expand(schema, store: json_store)
json_store.add_schema schema