-
Notifications
You must be signed in to change notification settings - Fork 229
mp4
Mattias Wadman edited this page Oct 23, 2023
·
11 revisions
Show edit lists and stts sums per track and also scale to seconds
#!/usr/bin/env fq -o decode_samples=false -d mp4 -f
# TODO: esds, make fancy printer? shared?
# TODO: handle -1 media_time
# TODO: fragmented mp4
# root
# moov
# mvhd (movie header)
# trak (track)
# mdia
# mdhd (media header)
# hdlr (handler?)
# minf
# stbl
# stsd (sample description)
# elst (edit list)
( first(.boxes[] | select(.type == "moov")?)
| first(.boxes[] | select(.type == "mvhd")?) as $mvhd
| { duration: $mvhd.duration,
time_scale: $mvhd.time_scale,
duration_s: ($mvhd.duration / $mvhd.time_scale),
tracks:
[ .boxes[]
| select(.type == "trak")
| first(.. | select(.type == "tkhd")?) as $tkhd
| first(.. | select(.type == "mdhd")?) as $mdhd
| first(.. | select(.type == "hdlr")?) as $hdlr
| first(.. | select(.type == "stsd")?) as $stsd
| (first(.. | select(.type == "elst")?) // null) as $elst
| first(.. | select(.type == "stts")?) as $stts
| ([$stts.entries[] | .count * .delta] | add) as $stts_sum
| { component_type: $hdlr.component_subtype,
duration: $tkhd.duration,
duration_s: ($tkhd.duration / $mvhd.time_scale),
media_duration: $mdhd.duration,
media_duration_s: ($mdhd.duration / $mdhd.time_scale),
# the sample descriptors are handled as boxes by the mp4 decoder
data_format: $stsd.boxes[0].type,
media_scale: $mdhd.time_scale,
edit_list:
( if $elst then
[ $elst.entries[]
| { track_duration: .segment_duration,
media_time: .media_time,
track_duration_s: (.segment_duration / $mvhd.time_scale),
media_time_s: (.media_time / $mdhd.time_scale)
}
]
else null
end
),
stts:
{ sum: $stts_sum,
sum_s: ($stts_sum / $mdhd.time_scale)
}
}
]
}
)
./mp4time.jq file.mp4
Will output something like
{
"duration": 880128,
"duration_s": 880.128,
"time_scale": 1000,
"tracks": [
{
"component_type": "vide",
"data_format": "avc1",
"duration": 880113,
"duration_s": 880.113,
"edit_list": [
{
"media_time": 6006,
"media_time_s": 0.06673333333333334,
"track_duration": 880113,
"track_duration_s": 880.113
}
],
"media_duration": 79210131,
"media_duration_s": 880.1125666666667,
"media_scale": 90000,
"stts": {
"sum": 79210131,
"sum_s": 880.1125666666667
}
},
{
"component_type": "soun",
"data_format": "mp4a",
"duration": 880128,
"duration_s": 880.128,
"edit_list": [
{
"media_time": 1024,
"media_time_s": 0.021333333333333333,
"track_duration": 880128,
"track_duration_s": 880.128
}
],
"media_duration": 42246144,
"media_duration_s": 880.128,
"media_scale": 48000,
"stts": {
"sum": 42247168,
"sum_s": 880.1493333333333
}
}
]
}
Summary of mp4 tracks for list of mp4 files
$ fq -n -d mp4 -o decode_samples=false '[inputs | {(input_filename): [grep_by(.type=="trak") | {s: (grep_by(.type=="mdhd") | .duration / .time_scale), comp: (grep_by(.type=="hdlr").component_subtype)}]}] | add' *.mp4
{
"a.mp4": [
{
"comp": "soun",
"s": 2.1
},
{
"comp": "vide",
"s": 3.1
}
],
...
Manual decode samples for a track assuming it only has one chunk as samples
nth(0; .. | select(.type=="trak")?) as $trak | first($trak | .. | select(.type=="stco")?).entries[0] as $stco_off | first($trak | .. | select(.type=="stsz")?) as $stsz | (foreach (if $stsz.sample_size != 0 then (range($stsz.entry_count) | $stsz.sample_size) else $stsz.entries[] end) as $sz ([$stco_off,$stco_off]; [.[1], .[1]+$sz]; .)) as $r | tobytesrange[$r[0]:$r[1]] | aac_frame | select(._error)
Debug track drift:
# checks drift for second track in file (nth(1;...))
# assumes constant 1024 samples per frames (aac) and sample rate 44100
fq -r -o decode_samples=false 'nth(1; grep_by(.type == "stts")) | [foreach (.entries[] | range(.count) as $_ | .delta) as $n (0; .+($n-1024);.)] | . as $d | range(length) | "\((.*1024)/44100): \($d[.]/44100)"' file
PTS from stts/ctts:
$ fq -o decode_samples=false 'limit(10; (grep_by(.type=="ctts") | [.entries[] | range(.sample_count) as $_ | .sample_offset]) as $ctts | (grep_by(.type=="stts") | [foreach (.entries[] | range(.count) as $_ | .delta) as $i (0;.+$i;.)]) as $stts | range($stts | length) as $i | $stts[$i]+$ctts[$i] - ($stts[0]+$ctts[0]) )' file.mp4
fmp4 duration:
grep_by(.type=="mdhd").time_scale as $scale | [grep_by(.type=="moof") | grep_by(.type=="tfhd").default_sample_duration as $d | grep_by(.type=="trun").samples[] | .sample_duration // $d] | add / $scale
find mp4s with no ctts box but has a sps with frame reorder > 0
fq -o decode_samples=false '((first(grep_by(.type=="ctts")) | true) // false) as $has_ctts | (first(grep_by(has("max_num_reorder_frames")).max_num_reorder_frames) // null) as $reorder | if $has_ctts == false and $reorder > 0 then {filename: input_filename, $has_ctts, $reorder} else empty end' *.mp4