m2ts duration

Started by Ron7, March 09, 2011, 07:46:05 PM

Previous topic - Next topic

Ron7

Exiftool was exactly what I needed for a rather large Perl app which creates ID3 tag files of video media for the Netgear media streamers.  Only problem/omission is it does not report the duration for m2ts video files.

There are other command line tools that do this, but they are C and rather convoluted, but at least I know the job can be done.  Any change of an enhancement so m2ts run length is output?

Phil Harvey

The duration is not stored as metadata in the M2TS format.  I could add the ability to calculate this if it is possible given the video parameters and file size, but I would need to do some research to see if this is possible.  Otherwise, scanning the entire video stream to determine the duration is not something that I want exiftool to do (this would be very slow in Perl).

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Ron7

#2
I looked at tsdemuxer source which gives duration for m2ts (and ts) videos.  It certainly makes the calculation by scanning and it's not fast in C, so understand what you mean, but mediainfo (no source available AFAIK) manages to dump the metadata including duration in an eyeblink (same test file and they get approximately the same answer within a few seconds of each other on a 55+ minute file), so it looks like there is a quick way.  It would be good (for me ;-) if exiftool could do this.

Update: I was wrong. Mediainfo source is available (Linux CLI version and a few others). The compiled source also reports duration of the test m2ts file instantly.  I'll have a dig, but it's a big source...

Phil Harvey

Thanks for looking into this.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Ron7

#4
The source (MediaInfo_CLI_0.7.42_GNU_FromSource.tar.bz2) came from sourceforge.net and I am in absolute awe of the amount of work the author of MediaInfo has poured into the code, though a few more comments would be nice.  I built my test version from the top level dir with:

CLI_Compile.sh --enable-debug

then ran it under gdb using a m2ts test file.

The duration for m2ts is set in File_MpegTs.cpp (starting line 792) and decoded for display in File_Analyze_Streams (starting 1543 though it is rather trivial).  The approach is convoluted, but he does not appear to have unpicked the frames and totalled them like tsdemuxer does, though he has done a file scan using offsets and markers.  The area where the timestamp start and end are set is around line 1083 (subtracting the two gives the duration in ms).


Phil Harvey

Thanks, I'll take a look when I get a chance.

Yes, decoding video files is a lot of work.  Especially stupid formats like M2TS. :(

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Ron7

#6
Fun read  :).  In years past I was involved with concocting 2 OMG specifications and came to the conclusion that the people assigned to standards writing by big companies were those who were such a pain, assigning them out of the way seemed like a good idea to management!  In the end we'd agree to just about anything to shut them up.

I had a deeper look into the File_MpegTs source and agree m2ts is remarkably complicated to pull apart.  The duration is found by searching for the 192 byte chunks (188 + 4 for DBAV) with chunk+4 == 0x47, the "adaptation field control" bit (0x20) set in the byte at chunk+7, a field length >= 5 (chunk+8), and bit 0x10 set in chunk+9 indicating the presence of a "program clock reference" (PCR) sequence. When PCR is set, the next 5 bytes (chunk+10..15) are combined to form the 33 bit PCR value using some remarkable shifting and bit twiddling (and multiplying by 300 at one point for some reason). 

The first PCR value encountered is stored as "timestamp start" and "timestamp end".  The end value is then replaced by successive PCR's until there are no more. The ms duration is the difference between end and start (seems the PCR value is cyclic and wrap-around has to be checked and adjusted for if necessary).  The 13 bit "Packet Identifier" (PID) formed from chunk+5 and 6 is used as a key for multiple PCR sequences per file. The sample stepped through with gdb had three identical duration values: 'General', Video, and Audio. I've seen others with multiple audio streams, not all identical in duration.

I hope some of this rings bells for you and I'm sure it's more complex than my simplistic investigation suggests.  For now, I'm extracting m2ts duration by backtick execution of mediainfo and a bit of RE work to get the displayed value.  This is not ideal as not everyone (hardly anyone?) is going to have that tool installed and my app is intended to be run by the technically ignorant as a compiled and packaged (ActiveState app builder) gui or cli app on Windoze, and Unix/Linux including OS-X, which is why I did it in Perl and was so pleased to discover Exiftool was Perl too (see http://itee.uq.edu.au/~chernich/tagsuite.html if you're bored).

Later: You're right. I've done a prototype and unless you know a trick I don't Perl is just too slow given we need byte level access to the first 16 bytes of every 192 bytes.  Too bad...

Phil Harvey

This sounds very useful although I haven't gone through your instructions in detail.

Are you saying that we need to scan through all M2TS chunks to find the last timestamp?  If so, maybe scanning backwards from the end of the file will give us the speed we need.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Ron7

#8
Good idea--wish I'd thought of that... maybe I would, eventually.

My prototype Perl program now gives the m2ts duration in less than a second.  The value agrees with that displayed by mediainfo tested over about 10 different m2ts files ranging between 20 minutes and 3 hours long. The code is not awful, but some more error checking and recovery might be nice ;)

But using the "first/last" approach we could report a low value if the duration is over 26 hours--unlikely but possible.

The problem is the 33 bit PCR (injected at least every 100ms for A/V sync).  This starts at some random value (the studio clock?) and wraps around.  During mediainfo's full sequential scan, if the end ever becomes less than the start, 0x200000000LL*300 is added to the end value to compensate. 

By simply going to the last PCR frame for the end value, we can tell if wraparound has occurred and fix that, but we don't know how many times it has occurred, although the file size would be an unreliable clue (could be one video stream and lots of audio streams). 

Still, 33 bits is a big number and wrap-arounds will be roughly 26.5 hours of program apart!  So chances favour none, or one only and I'd say we could be 99.98% confident in the result.

BTW, the 188 byte frame size comes from some strange original desire for Frame Relay compatibility!  BDMV format adds another 4 bytes per frame, so a valid m2ts file must be a multiple of 192 (dunno about .ts).  At least that simplified the backwards scan.

You are welcome to the prototype to use if and how you wish.  You (or anyone) can download it from:
http://itee.uq.edu.au/~chernich/downloads/ts_0.1.tar.gz 
This was a fun thing to do and many thanks for the suggestion.  I've wound the code into TagSuite so my crisis is over...

Phil Harvey

Excellent!  Thanks very much for posting your code.  Although you have a solution that works now, I will look into adding this ability to exiftool as well.   Your work on this matter is appreciated.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Phil Harvey

I just tried to open your download file to see your code, but it is not a valid TAR file. Instead, it appears to be a gzipped HTML document.  :(

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Phil Harvey

#11
Using the description you posted and the MediaInfo source I have figured this out and have added the ability to calculate the Duration of M2TS videos.  This update will appear in ExifTool 8.52 when it is released.

The only question I have is: Do the various programs (as identified by the PID) use different clocks?  Or can I just take the difference between the first and last clock times in the file, ignoring which program they came from?

For all of my test samples, the strategy of ignoring PID works very well, and is certainly simpler than scanning for the last timestamp of each program and calculating the duration of the longest one.

- Phil

Edit: I should have also mentioned that I found the iso13818-1 documentation extremely useful too.
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Ron7

#12
Sorry 'bout that, you were getting uq's 404 message.  I mistyped when creating the tar and called it "tag".  All fixed now, and download validated like I should have done in the first place!  ;D

Re the PID, I believe there could be a differences in the per-PID first/last values, especially when there are multiple audio streams present.  That said it would probably be small in most cases.  However, I think they would all use the same master clock. On some of my tests of files with up to 4 audio streams, mediainfo showed variations of up to a minute in duration, so using the video stream is the safest bet.

To safeguard, my prototype prototype extracts the PID of the first PCR frame encountered and does a match against this when looking for the last PCR, so at least we are using the same program, hopefully the video!

My proto printed the PID used and in all my test cases, the first PID was always 0x1001 (4096).  On the same files, Mediainfo reports the video PID as 4113, even though single step through the c++ shows it's extracting 0x1001!  Maybe something I don't know about raw PID processing. Audio PIDs seem to start at 0x1100.  Note too I give up looking for a PCR after reading 1000 frames.  Ten would more like it.

Great that Exiftool will have this feature wound into the next release (less code for me to maintain ;))  As you could not download my example, you must have done a green field impl.  I think that Mediainfo may have a small error in their code that converts the 5 btyes to the PCR in the way they handle byte 14, so would be interesting to compare (they are using only the MSB and LSB--I've not consulted the spec, so could be wrong here).


sub get_pcr
{
  my @chunk = @_;
  my $pcr;

  if (($chunk[7] & 0x20) &&  ($chunk[8] >= 5) && ($chunk[9] & 0x10)) {
    $pcr = Math::BigInt->new(
      $chunk[10] << 25 |
      $chunk[11] << 17 |
      $chunk[12] <<  9 |
      $chunk[13] <<  1 |
      $chunk[14] >> 7
    );
    $pcr->bmul(300);
    $pcr->badd(($chunk[14] & 0x01) << 8 | $chunk[15]);
  }
  return $pcr;
}


      if ($end_pcr < $start_pcr) {
        my $pcr_inc = Math::BinInt->new(0x2 << 8);
        $pcr_inc->bmul(300);
        $end_pcr->badd($pcr_inc);
      }

Phil Harvey

#13
Thanks for the comments.

About the 1000 chunk search limit:  In my code I am scanning backwards up to 256000 bytes (or about 1300 chunks).  In my collection of 24 M2TS samples, the most I have to scan back is 1086 chunks.

I think the way that MediaInfo handles byte 14 is correct.  The upper bit of this byte is the lsb of the 33-bit PCR base timecode, and the lower bit is the msb of the PCR extension.  The middle 6 bits are reserved.

My code looks quite different because I don't start from a byte array, and I avoid using 64-bit integers:

if ($adaptation_field_exists) {
   my $len = Get8u(\$buff, $pos++);
   $pos + $len > $plen and $exifTool->Warn('Invalid adaptation field length'), last;
   # read PCR value for calculation of Duration
   if ($len > 6) {
       my $flags = Get8u(\$buff, $pos);
       if ($flags & 0x10) { # PCR_flag
           # combine 33-bit program_clock_reference_base and 9 bit extension
           my $pcrBase = Get32u(\$buff, $pos + 1);
           my $pcrExt  = Get16u(\$buff, $pos + 5);
           $endTime = 300 * (2 * $pcrBase + ($pcrExt >> 15)) + ($pcrExt & 0x01ff);
           $startTime = $endTime unless defined $startTime;
       }
   }
   $pos += $len;
}


- Phil

Edit: Also I see you fail if the file size is not a multiple of the chunk size.  I think it is valid to truncate the M2TS at any point in the stream, so I calculate the position of the last integral number of chunks and scan backward from there instead of the end of file.  A number of my samples end with partial chunks, and this works well for them.  In theory, I suppose that the file could also begin at any point in the stream, but I fail on this case because I don't know of a reliable synchronization technique (just searching for 0x47 won't do it).
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Ron7

Agree that if we've found a valid first PCR, being tolerant over file length is probably a good idea.  None of my test files exhibit this but they were all created by VID2EVA from DVD's and DVD iso rips.  Also like the way you pass a ref to a scalar to the GetXXu subs.  Must remember that--sure avoids a lot of shifting and BigInt.

I think the only other possible bell and whistle could be to output the duration of all program streams found and label them with the PID (and language for audio--must be in the metadata someplace), sort of like mediainfo, but I can certainly live without that and would only use the video duration anyway.

Will now wait patiently for the Next release.  Thanks for being so responsive over this.

Ron

Phil Harvey

I have uploaded an 8.52 pre-release here in case you get a chance to test it out before the official release (which could be this weekend if I get a chance).  I don't have a large set of M2TS samples, so it would be nice if you could test this if possible on your samples.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Ron7

Phil, that works fine for me.  I ran it over about 60 files getting the same results as my prototype with no failures, so I'd say ship it!

I've never encountered a .ts file in the wild, but the Netgear devices are supposed to stream them, so they are in my list.  From a (very) little research, they look like m2ts without the extra 4 bytes at the start of each frame.  May be an easy one for you.

Phil Harvey

Excellent, thanks.  And thanks for the note about .ts, I'll look into this.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).