After converting an HLS M3U8 into an MP4, how to find out if there were TS segments missing, originally?

1.3k Views Asked by At

I have a few .ts files and the corresponding index.m3u8 file which looks something like this:

#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:15
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:1
#EXTINF:3.170,
seg-1-v1-a1.ts
#EXTINF:3.170,
seg-2-v1-a1.ts
#EXTINF:3.170,
seg-3-v1-a1.ts
#EXTINF:3.170,
seg-4-v1-a1.ts
#EXTINF:3.170,
seg-5-v1-a1.ts
#EXT-X-ENDLIST

So, to convert it into a single output.mp4 file, I ran the following command:

$ ffmpeg -i index.m3u8 -c copy output.mp4
...
[hls @ 0x55d78a3a6280] Opening 'seg-1-v1-a1.ts' for reading
...
[hls @ 0x55d78a3a6280] Opening 'seg-2-v1-a1.ts' for reading
[hls @ 0x55d78a3a6280] Opening 'seg-3-v1-a1.ts' for reading
[hls @ 0x55d78a3a6280] Opening 'seg-4-v1-a1.ts' for reading
[hls @ 0x55d78a3a6280] Opening 'seg-5-v1-a1.ts' for reading
...

Afterwards, I removed the .ts files and the index.m3u8 and just kept output.mp4. However, as it turns out, I did not have all the .ts files and the output was originally more like this:

$ rm seg-3-v1-a1.ts
$ ffmpeg -i index.m3u8 -c copy output.mp4
...
[hls @ 0x5555d0d86280] Opening 'seg-1-v1-a1.ts' for reading
...
[hls @ 0x5555d0d86280] Opening 'seg-2-v1-a1.ts' for reading
[hls @ 0x5555d0d86280] Opening 'seg-3-v1-a1.ts' for reading
[hls @ 0x5555d0d86280] Failed to open segment 3 of playlist 0
[hls @ 0x5555d0d86280] Opening 'seg-4-v1-a1.ts' for reading
[hls @ 0x5555d0d86280] Opening 'seg-5-v1-a1.ts' for reading
...

When watching the video, at the position of the missing segment, the picture freezes for a few seconds, then the video continues. I have a few .mp4 files and I want to find out which of them are affected by this issue. Without having the original log, how can I find out if there were any segments missing when I ran the ffmpeg -i index.m3u8 -c copy output.mp4 command?

I found one command at https://superuser.com/questions/100288/how-can-i-check-the-integrity-of-a-video-file-avi-mpeg-mp4 which unfortunately doesn't find any issues:

$ ffmpeg -v error -i output.mp4 -f null -
$ echo $?
0
1

There are 1 best solutions below

6
Sylogista On

You can check if r_frame_rate and avg_frame_rate are equal.
The command below:

ffprobe -v error -show_streams output.mp4 | grep "frame_rate"

will output (for correct file) something like this:

r_frame_rate=24/1
avg_frame_rate=24/1

and (for corrupted file) something like this:

r_frame_rate=24/1
avg_frame_rate=886/37

This method is not perfect, but in many cases should be totally fine.


Second method, intuitive and in my honest opinion working only in specific conditions, is looking for freezes. For example:

ffmpeg -i output.mp4 -vf "freezedetect=n=-60dB:d=2" -f null - 1>/dev/null 2>&1 \
| grep "duration"

It can work, but only if stream is really specific.


Another method, similar to the first one, is looking for VFR.
The command:

ffmpeg -i output.mp4 -vf vfrdet -an -f null -

will output:

[Parsed_vfrdet_0 @ 0x55563547bf00] VFR:0.000094 (2/21261) min: 3750 max: 183750 avg: 93750

for corrupted file. For correct it will be:

[Parsed_vfrdet_0 @ 0x564c33d53140] VFR:0.000000 (0/21311)

This is similar to the first method, but contrary to appearances – different!
As far as I know, it is possible to have equal r_frame_rate and avg_frame_rate, while VFR is not zero!

In my honest opinion checking VFR is the best option and may work. That's bad you didn't provide any example file. I assume that your stream should've VFR:0.000000, but that's reading tea leaves.

As far as I know, there is no universal method to do what you need. I hope any of my examples will fit, but it really depends and need to be tested in practice, on the right files. Exactly these, which you want to check.


According to your reply below my answer, I have implemented simple script that's:

  1. downloading ts files
  2. converting these to correct mp4
  3. corrupting one of the ts files
  4. converting these (with missing ts file) to corrupted mp4
  5. testing each mp4 file using my first method (comparing r_frame_rate == avg_frame_rate)
#!/usr/bin/bash

# Blender demo movie https://en.wikipedia.org/wiki/Sintel
# Full URL: https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8

bURL=https://bitdash-a.akamaihd.net/content/sintel/hls/video/

# I'm checking each available bitrate. You can modify that.
bitratesArray=("250" "500" "800" "1100" "1500" "4000" "6000" "10000")

# Here you can choose which ts file you want to remove from
# the ffmpeg conversion (it'll be temporary renamed, doesn't matter).
tsToCorrupt="14"

for i in ${bitratesArray[@]}
do
  # Download all files.
  # To be nice, I'm preventing unnecessary server load.
  if [ ! -d "${i}kbit" ]
  then
    echo -e "\nDownloading ${i}kbit version"
        
    curl "${bURL}${i}kbit.m3u8" | tee -a "${i}kbit.m3u8" | grep '\.ts' | \
    sed -e "s|^|$bURL|g" -e "s/\.ts/\.ts /g" > "${i}kbit_fullURL.m3u8"
        
    aria2c -q -c -j 16 -x 16 -d "${i}kbit" -i"${i}kbit_fullURL.m3u8"
    echo -e "\n"
  fi
    
  # Convert proper
  echo "Converting: ${i}kbit_proper.mp4" 
  ffmpeg -y -hide_banner -loglevel error \
  -i "${i}kbit.m3u8" -c copy "${i}kbit_proper.mp4" 
    
  # Make temporal source defect
  mv "${i}kbit/seq-${tsToCorrupt}.ts" "${i}kbit/seq-${tsToCorrupt}.ts_CORRUPTED"
    
  # Convert defective
  echo "Converting: ${i}kbit_defective.mp4" 
  ffmpeg -y -hide_banner -loglevel error \
  -i "${i}kbit.m3u8" -c copy "${i}kbit_defective.mp4" 
    
  # Repair defected source (obviously converted file is still corrupted)
  mv "${i}kbit/seq-${tsToCorrupt}.ts_CORRUPTED" "${i}kbit/seq-${tsToCorrupt}.ts"
done

# Examine generated files
echo -e "\n\nREPORT"
for i in *.mp4
do
  echo -e "\n---------------------------\nFilename: \n${i}"

  report=$(ffprobe -v error -show_streams $i | grep "frame_rate")

  echo -e "\ncalculated frame rates:\n${report}"
    echo -e "\nVerdict: \nFile ${i} is "

  echo ${report} | \
  awk -F'[=\n ]' ' { if ( $2 == $4 ) { print "OK" } else { print "WRONG" }; } ' 
done

For me it's working fine.