$VERSION
=
'1.29'
;
my
%streamType
= (
0x00
=>
'Reserved'
,
0x01
=>
'MPEG-1 Video'
,
0x02
=>
'MPEG-2 Video'
,
0x03
=>
'MPEG-1 Audio'
,
0x04
=>
'MPEG-2 Audio'
,
0x05
=>
'ISO 13818-1 private sections'
,
0x06
=>
'ISO 13818-1 PES private data'
,
0x07
=>
'ISO 13522 MHEG'
,
0x08
=>
'ISO 13818-1 DSM-CC'
,
0x09
=>
'ISO 13818-1 auxiliary'
,
0x0A
=>
'ISO 13818-6 multi-protocol encap'
,
0x0B
=>
'ISO 13818-6 DSM-CC U-N msgs'
,
0x0C
=>
'ISO 13818-6 stream descriptors'
,
0x0D
=>
'ISO 13818-6 sections'
,
0x0E
=>
'ISO 13818-1 auxiliary'
,
0x0F
=>
'MPEG-2 AAC Audio'
,
0x10
=>
'MPEG-4 Video'
,
0x11
=>
'MPEG-4 LATM AAC Audio'
,
0x12
=>
'MPEG-4 generic'
,
0x13
=>
'ISO 14496-1 SL-packetized'
,
0x14
=>
'ISO 13818-6 Synchronized Download Protocol'
,
0x15
=>
'Packetized metadata'
,
0x16
=>
'Sectioned metadata'
,
0x17
=>
'ISO/IEC 13818-6 DSM CC Data Carousel metadata'
,
0x18
=>
'ISO/IEC 13818-6 DSM CC Object Carousel metadata'
,
0x19
=>
'ISO/IEC 13818-6 Synchronized Download Protocol metadata'
,
0x1a
=>
'ISO/IEC 13818-11 IPMP'
,
0x1b
=>
'H.264 (AVC) Video'
,
0x1c
=>
'ISO/IEC 14496-3 (MPEG-4 raw audio)'
,
0x1d
=>
'ISO/IEC 14496-17 (MPEG-4 text)'
,
0x1e
=>
'ISO/IEC 23002-3 (MPEG-4 auxiliary video)'
,
0x1f
=>
'ISO/IEC 14496-10 SVC (MPEG-4 AVC sub-bitstream)'
,
0x20
=>
'ISO/IEC 14496-10 MVC (MPEG-4 AVC sub-bitstream)'
,
0x21
=>
'ITU-T Rec. T.800 and ISO/IEC 15444 (JPEG 2000 video)'
,
0x24
=>
'H.265 (HEVC) Video'
,
0x42
=>
'Chinese Video Standard'
,
0x7f
=>
'ISO/IEC 13818-11 IPMP (DRM)'
,
0x80
=>
'DigiCipher II Video'
,
0x81
=>
'A52/AC-3 Audio'
,
0x82
=>
'HDMV DTS Audio'
,
0x83
=>
'LPCM Audio'
,
0x84
=>
'SDDS Audio'
,
0x85
=>
'ATSC Program ID'
,
0x86
=>
'DTS-HD Audio'
,
0x87
=>
'E-AC-3 Audio'
,
0x8a
=>
'DTS Audio'
,
0x90
=>
'Presentation Graphic Stream (subtitle)'
,
0x91
=>
'A52b/AC-3 Audio'
,
0x92
=>
'DVD_SPU vls Subtitle'
,
0x94
=>
'SDDS Audio'
,
0xa0
=>
'MSCODEC Video'
,
0xea
=>
'Private ES (VC-1)'
,
);
my
%tableID
= (
0x00
=>
'Program Association'
,
0x01
=>
'Conditional Access'
,
0x02
=>
'Program Map'
,
0x03
=>
'Transport Stream Description'
,
0x40
=>
'Actual Network Information'
,
0x41
=>
'Other Network Information'
,
0x42
=>
'Actual Service Description'
,
0x46
=>
'Other Service Description'
,
0x4a
=>
'Bouquet Association'
,
0x4e
=>
'Actual Event Information - Present/Following'
,
0x4f
=>
'Other Event Information - Present/Following'
,
0x50
=>
'Actual Event Information - Schedule'
,
0x60
=>
'Other Event Information - Schedule'
,
0x70
=>
'Time/Date'
,
0x71
=>
'Running Status'
,
0x72
=>
'Stuffing'
,
0x73
=>
'Time Offset'
,
0x7e
=>
'Discontinuity Information'
,
0x7f
=>
'Selection Information'
,
);
my
%noSyntax
= (
0xbc
=> 1,
0xbe
=> 1,
0xbf
=> 1,
0xf0
=> 1,
0xf1
=> 1,
0xf2
=> 1,
0xf8
=> 1,
0xff
=> 1,
);
my
$knotsToKph
= 1.852;
%Image::ExifTool::M2TS::Main
= (
GROUPS
=> {
2
=>
'Video'
},
VARS
=> {
NO_ID
=> 1 },
NOTES
=>
q{
The MPEG-2 transport stream is used as a container for many different
audio/video formats (including AVCHD). This table lists information
extracted from M2TS files.
}
,
VideoStreamType
=> {
PrintHex
=> 1,
PrintConv
=> \
%streamType
,
SeparateTable
=>
'StreamType'
,
},
AudioStreamType
=> {
PrintHex
=> 1,
PrintConv
=> \
%streamType
,
SeparateTable
=>
'StreamType'
,
},
Duration
=> {
Notes
=>
q{
the -fast option may be used to avoid scanning to the end of file to
calculate the Duration
}
,
ValueConv
=>
'$val / 27000000'
,
PrintConv
=>
'ConvertDuration($val)'
,
},
_AC3
=> {
SubDirectory
=> {
TagTable
=>
'Image::ExifTool::M2TS::AC3'
} },
_H264
=> {
SubDirectory
=> {
TagTable
=>
'Image::ExifTool::H264::Main'
} },
_MISB
=> {
SubDirectory
=> {
TagTable
=>
'Image::ExifTool::MISB::Main'
} },
);
%Image::ExifTool::M2TS::AC3
= (
GROUPS
=> {
1
=>
'AC3'
,
2
=>
'Audio'
},
VARS
=> {
NO_ID
=> 1 },
NOTES
=>
'Tags extracted from AC-3 audio streams.'
,
AudioSampleRate
=> {
PrintConv
=> {
0
=>
'48000'
,
1
=>
'44100'
,
2
=>
'32000'
,
},
},
AudioBitrate
=> {
PrintConvColumns
=> 2,
ValueConv
=> {
0
=> 32000,
1
=> 40000,
2
=> 48000,
3
=> 56000,
4
=> 64000,
5
=> 80000,
6
=> 96000,
7
=> 112000,
8
=> 128000,
9
=> 160000,
10
=> 192000,
11
=> 224000,
12
=> 256000,
13
=> 320000,
14
=> 384000,
15
=> 448000,
16
=> 512000,
17
=> 576000,
18
=> 640000,
32
=>
'32000 max'
,
33
=>
'40000 max'
,
34
=>
'48000 max'
,
35
=>
'56000 max'
,
36
=>
'64000 max'
,
37
=>
'80000 max'
,
38
=>
'96000 max'
,
39
=>
'112000 max'
,
40
=>
'128000 max'
,
41
=>
'160000 max'
,
42
=>
'192000 max'
,
43
=>
'224000 max'
,
44
=>
'256000 max'
,
45
=>
'320000 max'
,
46
=>
'384000 max'
,
47
=>
'448000 max'
,
48
=>
'512000 max'
,
49
=>
'576000 max'
,
50
=>
'640000 max'
,
},
PrintConv
=>
'ConvertBitrate($val)'
,
},
SurroundMode
=> {
PrintConv
=> {
0
=>
'Not indicated'
,
1
=>
'Not Dolby surround'
,
2
=>
'Dolby surround'
,
},
},
AudioChannels
=> {
PrintConvColumns
=> 2,
PrintConv
=> {
0
=>
'1 + 1'
,
1
=> 1,
2
=> 2,
3
=> 3,
4
=>
'2/1'
,
5
=>
'3/1'
,
6
=>
'2/2'
,
7
=>
'3/2'
,
8
=> 1,
9
=>
'2 max'
,
10
=>
'3 max'
,
11
=>
'4 max'
,
12
=>
'5 max'
,
13
=>
'6 max'
,
},
},
);
sub
ParseAC3Audio($$)
{
my
(
$et
,
$dataPt
) =
@_
;
if
(
$$dataPt
=~ /\x0b\x77..(.)/sg) {
my
$sampleRate
=
ord
($1) >> 6;
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::M2TS::AC3'
);
$et
->HandleTag(
$tagTablePtr
,
AudioSampleRate
=>
$sampleRate
);
}
}
sub
ParseAC3Descriptor($$)
{
my
(
$et
,
$dataPt
) =
@_
;
return
if
length
$$dataPt
< 3;
my
@v
=
unpack
(
'C3'
,
$$dataPt
);
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::M2TS::AC3'
);
$et
->HandleTag(
$tagTablePtr
,
'AudioBitrate'
,
$v
[1] >> 2);
$et
->HandleTag(
$tagTablePtr
,
'SurroundMode'
,
$v
[1] & 0x03);
$et
->HandleTag(
$tagTablePtr
,
'AudioChannels'
, (
$v
[2] >> 1) & 0x0f);
}
sub
ParsePID($$$$$)
{
my
(
$et
,
$pid
,
$type
,
$pidName
,
$dataPt
) =
@_
;
return
-1
unless
defined
$type
;
my
$verbose
=
$et
->Options(
'Verbose'
);
if
(
$verbose
> 1) {
my
$out
=
$et
->Options(
'TextOut'
);
printf
$out
"Parsing stream 0x%.4x (%s) %d bytes\n"
,
$pid
,
$pidName
,
length
(
$$dataPt
);
$et
->VerboseDump(
$dataPt
);
}
my
$more
= 0;
if
(
$type
== 0x01 or
$type
== 0x02) {
Image::ExifTool::MPEG::ParseMPEGAudioVideo(
$et
,
$dataPt
);
}
elsif
(
$type
== 0x03 or
$type
== 0x04) {
Image::ExifTool::MPEG::ParseMPEGAudio(
$et
,
$dataPt
);
}
elsif
(
$type
== 6 and
$pid
== 0x0300) {
if
(
$$dataPt
=~ /^LIGOGPSINFO/s) {
my
$tbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
my
%dirInfo
= (
DataPt
=>
$dataPt
,
DirName
=>
'Ligo0x0300'
);
Image::ExifTool::LigoGPS::ProcessLigoGPS(
$et
, \
%dirInfo
,
$tbl
, 1);
$$et
{FoundGoodGPS} = 1;
$more
= 1;
}
}
elsif
(
$type
== 0x1b) {
$more
= Image::ExifTool::H264::ParseH264Video(
$et
,
$dataPt
);
if
(
$$et
{OPTIONS}{ExtractEmbedded}) {
$more
= 1;
}
elsif
(not
$$et
{OPTIONS}{Validate}) {
$et
->Warn(
'The ExtractEmbedded option may find more tags in the video data'
,3);
}
}
elsif
(
$type
== 0x81 or
$type
== 0x87 or
$type
== 0x91) {
ParseAC3Audio(
$et
,
$dataPt
);
}
elsif
(
$type
== 0x15) {
if
(
$$dataPt
=~ /^.{5}\x06\x0e\x2b\x34/s) {
$more
= Image::ExifTool::MISB::ParseMISB(
$et
,
$dataPt
, GetTagTable(
'Image::ExifTool::MISB::Main'
));
if
(not
$$et
{OPTIONS}{ExtractEmbedded}) {
$more
= 0;
}
elsif
(
$$et
{OPTIONS}{ExtractEmbedded} > 2) {
$more
= 1;
}
}
}
elsif
(
$type
< 0) {
if
(
$$dataPt
=~ /^(.{164})?(.{24})A[NS][EW]/s) {
my
$dat
= (
"\0"
x 16) .
substr
(
$$dataPt
,
length
($1 ||
''
)) . (
"\0"
x 20);
my
$tbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
Image::ExifTool::QuickTime::ProcessFreeGPS(
$et
, {
DataPt
=> \
$dat
},
$tbl
);
$more
= 1;
}
elsif
(
$$dataPt
=~ /^(V00|A([NS])([EW]))\0/s) {
SetByteOrder(
'II'
);
my
$tagTbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
while
(
$$dataPt
=~ /((V00|A[NS][EW])\0.{28})/g) {
my
$dat
= $1;
$$et
{DOC_NUM} = ++
$$et
{DOC_COUNT};
if
($2 ne
'V00'
) {
my
$lat
=
abs
(GetFloat(\
$dat
, 4));
my
$lon
=
abs
(GetFloat(\
$dat
, 8));
my
$spd
= GetFloat(\
$dat
, 12) *
$knotsToKph
;
my
$trk
= GetFloat(\
$dat
, 16);
Image::ExifTool::QuickTime::ConvertLatLon(
$lat
,
$lon
);
$et
->HandleTag(
$tagTbl
,
GPSLatitude
=>
abs
(
$lat
) * (
substr
(
$dat
,1,1) eq
'S'
? -1 : 1));
$et
->HandleTag(
$tagTbl
,
GPSLongitude
=>
abs
(
$lon
) * (
substr
(
$dat
,2,1) eq
'W'
? -1 : 1));
$et
->HandleTag(
$tagTbl
,
GPSSpeed
=>
$spd
);
$et
->HandleTag(
$tagTbl
,
GPSSpeedRef
=>
'K'
);
$et
->HandleTag(
$tagTbl
,
GPSTrack
=>
$trk
);
$et
->HandleTag(
$tagTbl
,
GPSTrackRef
=>
'T'
);
}
my
@acc
=
unpack
(
'x20V3'
,
$dat
);
map
{
$_
=
$_
- 4294967296
if
$_
>= 0x80000000 }
@acc
;
$et
->HandleTag(
$tagTbl
,
Accelerometer
=>
"@acc"
);
}
SetByteOrder(
'MM'
);
$$et
{FoundGoodGPS} = 1;
$more
= 1;
}
elsif
(
$$dataPt
=~ /^\$(GPSINFO|GSNRINFO),/) {
$$dataPt
=~
tr
/\x0d/\x0a/;
$$dataPt
=~
tr
/\0//d;
my
$tagTbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
my
@lines
=
split
/\x0a/,
$$dataPt
;
my
(
$line
,
$lastTime
);
foreach
$line
(
@lines
) {
if
(
$line
=~ /^\
$GPSINFO
/) {
my
@a
=
split
/,/,
$lines
[0];
next
unless
@a
> 7;
next
if
$lastTime
and
$a
[2] eq
$lastTime
;
$lastTime
=
$a
[2];
$$et
{DOC_NUM} = ++
$$et
{DOC_COUNT};
$a
[2] =~
tr
/./:/;
my
(
$lat
,
$lon
) =
@a
[3,4];
Image::ExifTool::QuickTime::ConvertLatLon(
$lat
,
$lon
);
$et
->HandleTag(
$tagTbl
,
GPSDateTime
=>
$a
[2]);
$et
->HandleTag(
$tagTbl
,
GPSLatitude
=>
$lat
);
$et
->HandleTag(
$tagTbl
,
GPSLongitude
=>
$lon
);
$et
->HandleTag(
$tagTbl
,
GPSSpeed
=>
$a
[5]);
$et
->HandleTag(
$tagTbl
,
GPSSpeedRef
=>
'K'
);
$et
->HandleTag(
$tagTbl
,
GPSTrack
=>
$a
[7]);
$et
->HandleTag(
$tagTbl
,
GPSTrackRef
=>
'T'
);
}
elsif
(
$line
=~ /^\
$GSNRINFO
/) {
my
@a
=
split
/,/,
$line
;
shift
@a
;
$et
->HandleTag(
$tagTbl
,
Accelerometer
=>
"@a"
);
}
}
$more
= 1;
}
elsif
(
$$dataPt
=~ /\
$GPRMC
,/) {
my
$tagTbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
while
(
$$dataPt
=~ /\$[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(.{2})(\d{2}\.\d+),([NS]),(.{3})(\d{2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/g and
$13 <= 31 and $14 <= 12 and $15 <= 99)
{
$$et
{DOC_NUM} = ++
$$et
{DOC_COUNT};
my
$year
= $15 + ($15 >= 70 ? 1900 : 2000);
$et
->HandleTag(
$tagTbl
,
GPSDateTime
=>
sprintf
(
'%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ'
,
$year
, $14, $13, $1, $2, $3));
$et
->HandleTag(
$tagTbl
,
GPSSpeed
=> $11 *
$knotsToKph
)
if
length
$11;
$et
->HandleTag(
$tagTbl
,
GPSTrack
=> $12)
if
length
$12;
my
@chars
=
unpack
(
'C*'
, $5 . $8);
my
@xor
= (0x0e,0x0e,0x00,0x05,0x03);
my
$bad
;
foreach
(
@chars
) {
$_
^=
shift
(
@xor
);
$bad
= 1
if
$_
< 0x30 or
$_
> 0x39;
}
if
(
$bad
) {
$et
->Warn(
'Error decrypting GPS degrees'
);
}
else
{
my
$la
=
pack
(
'C*'
,
@chars
[0,1]);
my
$lo
=
pack
(
'C*'
,
@chars
[2,3,4]);
$et
->Warn(
'Decryption of this GPS is highly experimental. More testing samples are required'
);
$et
->HandleTag(
$tagTbl
,
GPSLatitude
=> ((
$la
|| 0) + (($6-85.95194)/2.43051724137931+42.2568)/60) * ($7 eq
'N'
? 1 : -1));
$et
->HandleTag(
$tagTbl
,
GPSLongitude
=> ((
$lo
|| 0) + (($9-70.14674)/1.460987654320988+9.2028)/60) * ($10 eq
'E'
? 1 : -1));
}
}
}
elsif
(
$$dataPt
=~ /\
$GSENSORD
,\s*(\d+),\s*(\d+),\s*(\d+),/) {
my
$tagTbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
$$et
{DOC_NUM} =
$$et
{DOC_COUNT};
$et
->HandleTag(
$tagTbl
,
Accelerometer
=>
"$1 $2 $3"
);
}
elsif
(
$$dataPt
=~ /^.{44}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s and
length
(
$$dataPt
) >= 84) {
SetByteOrder(
'II'
);
my
$tagTbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
my
$lat
=
abs
(GetFloat(
$dataPt
, 48));
my
$lon
=
abs
(GetFloat(
$dataPt
, 56));
my
$spd
= GetFloat(
$dataPt
, 64);
my
$trk
= GetFloat(
$dataPt
, 68);
$et
->Warn(
'GPSLatitude/Longitude encryption is not yet known, so these will be wrong'
);
$$et
{DOC_NUM} = ++
$$et
{DOC_COUNT};
my
@date
=
unpack
(
'x32V3x28V3'
,
$$dataPt
);
$date
[3] += 2000;
$et
->HandleTag(
$tagTbl
,
GPSDateTime
=>
sprintf
(
'%.4d:%.2d:%.2d %.2d:%.2d:%.2d'
,
@date
[3..5,0..2]));
$et
->HandleTag(
$tagTbl
,
GPSLatitude
=>
abs
(
$lat
) * ($1 eq
'S'
? -1 : 1));
$et
->HandleTag(
$tagTbl
,
GPSLongitude
=>
abs
(
$lon
) * ($2 eq
'W'
? -1 : 1));
$et
->HandleTag(
$tagTbl
,
GPSSpeed
=>
$spd
);
$et
->HandleTag(
$tagTbl
,
GPSSpeedRef
=>
'K'
);
$et
->HandleTag(
$tagTbl
,
GPSTrack
=>
$trk
);
$et
->HandleTag(
$tagTbl
,
GPSTrackRef
=>
'T'
);
SetByteOrder(
'MM'
);
$more
= 1;
}
elsif
(
length
(
$$dataPt
) >= 64 and
substr
(
$$dataPt
, 32, 2) eq
'$S'
) {
my
$tagTbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
my
(
$n
,
$last
) = (32,
"\0"
);
for
(
my
$i
=32;
$i
<
length
(
$$dataPt
)-32;
$i
+=32) {
last
unless
substr
(
$$dataPt
,
$n
, 2) eq
'$S'
;
my
$dateTime
=
substr
(
$$dataPt
,
$i
+6, 8);
$last
gt
$dateTime
and
$n
=
$i
,
last
;
$last
=
$dateTime
;
}
for
(
my
$i
=32;
$i
<
length
(
$$dataPt
)-32;
$i
+=32,
$n
+=32) {
$n
= 32
if
$n
>
length
(
$$dataPt
)-32;
last
unless
substr
(
$$dataPt
,
$n
, 2) eq
'$S'
;
my
@a
=
unpack
(
"x${n}nnnnCCCCnCNNC"
,
$$dataPt
);
$a
[8] /= 10;
$a
[2] += (36000 - 65536)
if
$a
[2] & 0x8000;
$$et
{DOC_NUM} = ++
$$et
{DOC_COUNT};
$et
->HandleTag(
$tagTbl
,
GPSDateTime
=>
sprintf
(
'%.4d:%.2d:%.2d %.2d:%.2d:%04.1fZ'
,
@a
[3..8]));
$et
->HandleTag(
$tagTbl
,
GPSLatitude
=>
$a
[10] * 1e-7);
$et
->HandleTag(
$tagTbl
,
GPSLongitude
=>
$a
[11] * 1e-7);
$et
->HandleTag(
$tagTbl
,
GPSSpeed
=>
$a
[1] * 0.036);
$et
->HandleTag(
$tagTbl
,
GPSTrack
=>
$a
[2] / 100);
}
$$et
{FoundGoodGPS} = 1;
$more
= 1;
}
elsif
(
$$dataPt
=~ /^skip.{4}LIGOGPSINFO\0/s) {
my
$tbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
my
%dirInfo
= (
DataPt
=>
$dataPt
,
DirStart
=> 8,
DirName
=>
sprintf
(
'Ligo0x%.4x'
,
$pid
));
Image::ExifTool::LigoGPS::ProcessLigoGPS(
$et
, \
%dirInfo
,
$tbl
, 1);
$$et
{FoundGoodGPS} = 1;
}
elsif
(
$$et
{FoundGoodGPS}) {
$more
= 1;
}
delete
$$et
{DOC_NUM};
}
return
$more
;
}
sub
ProcessM2TS($$)
{
my
(
$et
,
$dirInfo
) =
@_
;
my
$raf
=
$$dirInfo
{RAF};
my
(
$buff
,
$pLen
,
$upkPrefix
,
$j
,
$fileType
,
$eof
);
my
(
%pmt
,
%pidType
,
%data
,
%sectLen
,
%packLen
,
%fromStart
);
my
(
$startTime
,
$endTime
,
$fwdTime
,
$backScan
,
$maxBack
);
my
$verbose
=
$et
->Options(
'Verbose'
);
my
$out
=
$et
->Options(
'TextOut'
);
return
0
unless
$raf
->Read(
$buff
, 8) == 8;
return
0
unless
$buff
=~ /^(....)?\x47/s;
unless
($1) {
$pLen
= 188;
$fileType
=
'M2T'
;
$upkPrefix
=
'N'
;
}
else
{
$pLen
= 192;
$upkPrefix
=
'x4N'
;
}
my
$prePos
=
$pLen
- 188;
my
$readSize
= 64 *
$pLen
;
$raf
->Seek(0,0);
$raf
->Read(
$buff
,
$readSize
) >=
$pLen
* 4 or
return
0;
for
(
$j
=1;
$j
<4; ++
$j
) {
return
0
unless
substr
(
$buff
,
$prePos
+
$pLen
*
$j
, 1) eq
'G'
;
}
$et
->SetFileType(
$fileType
);
SetByteOrder(
'MM'
);
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::M2TS::Main'
);
my
%pidName
= (
0
=>
'Program Association Table'
,
1
=>
'Conditional Access Table'
,
2
=>
'Transport Stream Description Table'
,
0x1fff
=>
'Null Packet'
,
);
my
%didPID
= (
1
=> 0,
2
=> 0,
0x1fff
=> 0 );
my
%needPID
= (
0
=> 1 );
my
%gpsPID
= (
0x0300
=> 1,
0x01e4
=> 1,
0x0e1b
=> 1,
0x0e1a
=> 1,
);
my
$pEnd
= 0;
if
((
$et
->Options(
'ExtractEmbedded'
) || 0) > 2) {
foreach
(
keys
%gpsPID
) {
$needPID
{
$_
} = 1;
$pidType
{
$_
} = -1;
$pidName
{
$_
} =
'unregistered dashcam GPS'
;
}
}
for
(;;) {
unless
(
%needPID
) {
last
unless
defined
$startTime
;
unless
(
defined
$backScan
) {
my
$saveTime
=
$endTime
;
undef
$endTime
;
last
if
$et
->Options(
'FastScan'
);
$verbose
and
print
$out
"[Starting backscan for last PCR]\n"
;
my
$fwdPos
=
$raf
->Tell() -
length
(
$buff
) +
$pEnd
;
$raf
->Seek(0, 2) or
last
;
my
$fsize
=
$raf
->Tell();
$backScan
=
int
(
$fsize
/
$pLen
) *
$pLen
-
$fsize
;
$maxBack
=
$fwdPos
-
$fsize
;
my
$nMax
=
int
(512000 /
$pLen
);
if
(
$nMax
<
int
(-
$maxBack
/
$pLen
)) {
$maxBack
=
$backScan
-
$nMax
*
$pLen
;
}
else
{
$fwdTime
=
$saveTime
;
}
$pEnd
= 0;
}
}
my
$pos
;
if
(
defined
$backScan
) {
last
if
defined
$endTime
;
$pos
=
$pEnd
=
$pEnd
- 2 *
$pLen
;
if
(
$pos
< 0) {
last
if
$backScan
<=
$maxBack
;
my
$buffLen
=
$backScan
-
$maxBack
;
$buffLen
=
$readSize
if
$buffLen
>
$readSize
;
$backScan
-=
$buffLen
;
$raf
->Seek(
$backScan
, 2) or
last
;
$raf
->Read(
$buff
,
$buffLen
) ==
$buffLen
or
last
;
$pos
=
$pEnd
=
$buffLen
-
$pLen
;
}
}
else
{
$pos
=
$pEnd
;
if
(
$pos
+
$pLen
>
length
$buff
) {
$raf
->Read(
$buff
,
$readSize
) >=
$pLen
or
$eof
= 1,
last
;
$pos
=
$pEnd
= 0;
}
}
$pEnd
+=
$pLen
;
$pos
+=
$prePos
;
my
$prefix
=
unpack
(
"x${pos}N"
,
$buff
);
unless
((
$prefix
& 0xff000000) == 0x47000000) {
$et
->Warn(
'M2TS synchronization error'
)
unless
defined
$backScan
;
last
;
}
my
$payload_unit_start_indicator
=
$prefix
& 0x00400000;
my
$pid
=(
$prefix
& 0x001fff00) >> 8;
my
$adaptation_field_exists
=
$prefix
& 0x00000020;
my
$payload_data_exists
=
$prefix
& 0x00000010;
if
(
$verbose
> 1) {
my
$i
= (
$raf
->Tell() -
length
(
$buff
) +
$pEnd
) /
$pLen
- 1;
print
$out
"Transport packet $i:\n"
;
$et
->VerboseDump(\
$buff
,
Len
=>
$pLen
,
Addr
=>
$i
*
$pLen
,
Start
=>
$pos
-
$prePos
);
my
$str
=
$pidName
{
$pid
} ?
" ($pidName{$pid})"
:
' <not in Program Map Table!>'
;
printf
$out
" Timecode: 0x%.4x\n"
, Get32u(\
$buff
,
$pos
-
$prePos
)
if
$pLen
== 192;
printf
$out
" Packet ID: 0x%.4x$str\n"
,
$pid
;
printf
$out
" Start Flag: %s\n"
,
$payload_unit_start_indicator
?
'Yes'
:
'No'
;
}
$pos
+= 4;
if
(
$adaptation_field_exists
) {
my
$len
= Get8u(\
$buff
,
$pos
++);
$pos
+
$len
>
$pEnd
and
$et
->Warn(
'Invalid adaptation field length'
),
last
;
if
(
$len
> 6) {
my
$flags
= Get8u(\
$buff
,
$pos
);
if
(
$flags
& 0x10) {
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
;
}
next
unless
$payload_data_exists
and not
defined
$backScan
;
if
(
$pid
== 0 or
defined
$pmt
{
$pid
})
{
my
$buf2
;
if
(
$payload_unit_start_indicator
) {
my
$pointer_field
= Get8u(\
$buff
,
$pos
);
$pos
+= 1 +
$pointer_field
;
$pos
>=
$pEnd
and
$et
->Warn(
'Bad pointer field'
),
last
;
$buf2
=
substr
(
$buff
,
$pEnd
-
$pLen
,
$pLen
);
$pos
-=
$pEnd
-
$pLen
;
}
else
{
next
unless
$sectLen
{
$pid
};
my
$more
=
$sectLen
{
$pid
} -
length
(
$data
{
$pid
});
my
$size
=
$pLen
-
$pos
;
$size
=
$more
if
$size
>
$more
;
$data
{
$pid
} .=
substr
(
$buff
,
$pos
,
$size
);
next
unless
$size
==
$more
;
$buf2
=
$data
{
$pid
};
$pos
= 0;
delete
$data
{
$pid
};
delete
$fromStart
{
$pid
};
delete
$sectLen
{
$pid
};
}
my
$slen
=
length
(
$buf2
);
$pos
+ 8 >
$slen
and
$et
->Warn(
'Truncated payload'
),
last
;
my
$table_id
= Get8u(\
$buf2
,
$pos
);
my
$name
= (
$tableID
{
$table_id
} ||
sprintf
(
'Unknown (0x%x)'
,
$table_id
)) .
' Table'
;
my
$expectedID
=
$pid
? 0x02 : 0x00;
unless
(
$table_id
==
$expectedID
) {
$verbose
> 1 and
print
$out
" (skipping $name)\n"
;
delete
$needPID
{
$pid
};
$didPID
{
$pid
} = 1;
next
;
}
my
$section_syntax_indicator
= Get8u(\
$buf2
,
$pos
+ 1) & 0xc0;
$section_syntax_indicator
== 0x80 or
$et
->Warn(
"Bad $name"
),
last
;
my
$section_length
= Get16u(\
$buf2
,
$pos
+ 1) & 0x0fff;
$section_length
> 1021 and
$et
->Warn(
"Invalid $name length"
),
last
;
if
(
$slen
<
$section_length
+ 3) {
$data
{
$pid
} =
substr
(
$buf2
,
$pos
);
$sectLen
{
$pid
} =
$section_length
+ 3;
next
;
}
my
$program_number
= Get16u(\
$buf2
,
$pos
+ 3);
my
$section_number
= Get8u(\
$buf2
,
$pos
+ 6);
my
$last_section_number
= Get8u(\
$buf2
,
$pos
+ 7);
if
(
$verbose
> 1) {
print
$out
" $name length: $section_length\n"
;
print
$out
" Program No: $program_number\n"
if
$pid
;
printf
$out
" Stream ID: 0x%x\n"
,
$program_number
if
not
$pid
;
print
$out
" Section No: $section_number\n"
;
print
$out
" Last Sect.: $last_section_number\n"
;
}
my
$end
=
$pos
+
$section_length
+ 3 - 4;
$pos
+= 8;
if
(
$pid
== 0) {
while
(
$pos
<=
$end
- 4) {
my
$program_number
= Get16u(\
$buf2
,
$pos
);
my
$program_map_PID
= Get16u(\
$buf2
,
$pos
+ 2) & 0x1fff;
$pmt
{
$program_map_PID
} =
$program_number
;
my
$str
=
"Program $program_number Map"
;
$pidName
{
$program_map_PID
} =
$str
;
$needPID
{
$program_map_PID
} = 1
unless
$didPID
{
$program_map_PID
};
$verbose
and
printf
$out
" PID(0x%.4x) --> $str\n"
,
$program_map_PID
;
$pos
+= 4;
}
}
else
{
$pos
+ 4 >
$slen
and
$et
->Warn(
'Truncated PMT'
),
last
;
my
$pcr_pid
= Get16u(\
$buf2
,
$pos
) & 0x1fff;
my
$program_info_length
= Get16u(\
$buf2
,
$pos
+ 2) & 0x0fff;
my
$str
=
"Program $program_number Clock Reference"
;
$pidName
{
$pcr_pid
} =
$str
;
$verbose
and
printf
$out
" PID(0x%.4x) --> $str\n"
,
$pcr_pid
;
$pos
+= 4;
$pos
+
$program_info_length
>
$slen
and
$et
->Warn(
'Truncated program info'
),
last
;
if
(
$verbose
> 1) {
for
(
$j
=0;
$j
<
$program_info_length
-2; ) {
my
$descriptor_tag
= Get8u(\
$buf2
,
$pos
+
$j
);
my
$descriptor_length
= Get8u(\
$buf2
,
$pos
+
$j
+ 1);
$j
+= 2;
last
if
$j
+
$descriptor_length
>
$program_info_length
;
my
$desc
=
substr
(
$buf2
,
$pos
+
$j
,
$descriptor_length
);
$j
+=
$descriptor_length
;
$desc
=~ s/([\x00-\x1f\x7f-\xff])/
sprintf
(
"\\x%.2x"
,
ord
$1)/eg;
printf
$out
" Program Descriptor: Type=0x%.2x \"$desc\"\n"
,
$descriptor_tag
;
}}
$pos
+=
$program_info_length
;
while
(
$pos
<=
$end
- 5) {
my
$stream_type
= Get8u(\
$buf2
,
$pos
);
my
$elementary_pid
= Get16u(\
$buf2
,
$pos
+ 1) & 0x1fff;
my
$es_info_length
= Get16u(\
$buf2
,
$pos
+ 3) & 0x0fff;
my
$str
=
$streamType
{
$stream_type
};
$str
or
$str
= (
$stream_type
< 0x7f ?
'Reserved'
:
'Private'
);
$str
=
sprintf
(
'%s (0x%.2x)'
,
$str
,
$stream_type
);
$str
=
"Program $program_number $str"
;
$verbose
and
printf
$out
" PID(0x%.4x) --> $str\n"
,
$elementary_pid
;
if
(
$str
=~ /(Audio|Video)/) {
unless
(
$pidName
{
$elementary_pid
}) {
$et
->HandleTag(
$tagTablePtr
, $1 .
'StreamType'
,
$stream_type
)
}
$needPID
{
$elementary_pid
} = 1
unless
$didPID
{
$elementary_pid
};
}
$pidName
{
$elementary_pid
} =
$str
;
$pidType
{
$elementary_pid
} =
$stream_type
;
$pos
+= 5;
$pos
+
$es_info_length
>
$slen
and
$et
->Warn(
'Truncated ES info'
),
$pos
=
$end
,
last
;
for
(
$j
=0;
$j
<
$es_info_length
-2; ) {
my
$descriptor_tag
= Get8u(\
$buf2
,
$pos
+
$j
);
my
$descriptor_length
= Get8u(\
$buf2
,
$pos
+
$j
+ 1);
$j
+= 2;
last
if
$j
+
$descriptor_length
>
$es_info_length
;
my
$desc
=
substr
(
$buf2
,
$pos
+
$j
,
$descriptor_length
);
$j
+=
$descriptor_length
;
if
(
$verbose
> 1) {
my
$dstr
=
$desc
;
$dstr
=~ s/([\x00-\x1f\x7f-\xff])/
sprintf
(
"\\x%.2x"
,
ord
$1)/eg;
printf
$out
" ES Descriptor: Type=0x%.2x \"$dstr\"\n"
,
$descriptor_tag
;
}
unless
(
$didPID
{
$pid
}) {
if
(
$descriptor_tag
== 0x81) {
ParseAC3Descriptor(
$et
, \
$desc
);
}
}
}
$pos
+=
$es_info_length
;
}
}
}
elsif
(not
defined
$didPID
{
$pid
}) {
if
(
$payload_unit_start_indicator
) {
if
(
defined
$data
{
$pid
}) {
my
$more
= ParsePID(
$et
,
$pid
,
$pidType
{
$pid
},
$pidName
{
$pid
}, \
$data
{
$pid
});
delete
$data
{
$pid
};
delete
$fromStart
{
$pid
};
unless
(
$more
) {
delete
$needPID
{
$pid
};
$didPID
{
$pid
} = 1;
next
;
}
$needPID
{
$pid
} = -1;
}
next
if
$pos
+ 6 >
$pEnd
;
my
$start_code
= Get32u(\
$buff
,
$pos
);
next
unless
(
$start_code
& 0xffffff00) == 0x00000100;
my
$stream_id
=
$start_code
& 0xff;
my
$pes_packet_length
= Get16u(\
$buff
,
$pos
+ 4);
if
(
$verbose
> 1) {
printf
$out
" Stream ID: 0x%.2x\n"
,
$stream_id
;
print
$out
" Packet Len: $pes_packet_length\n"
;
}
$pos
+= 6;
unless
(
$noSyntax
{
$stream_id
}) {
next
if
$pos
+ 3 >
$pEnd
;
my
$syntax
= Get8u(\
$buff
,
$pos
) & 0xc0;
$syntax
== 0x80 or
$et
->Warn(
'Bad PES syntax'
),
next
;
my
$pes_header_data_length
= Get8u(\
$buff
,
$pos
+ 2);
$pos
+= 3 +
$pes_header_data_length
;
next
if
$pos
>=
$pEnd
;
}
$data
{
$pid
} =
substr
(
$buff
,
$pos
,
$pEnd
-
$pos
);
$fromStart
{
$pid
} = 1;
if
(
$pes_packet_length
> 8) {
$packLen
{
$pid
} =
$pes_packet_length
- 8;
}
else
{
delete
$packLen
{
$pid
};
}
}
else
{
unless
(
defined
$data
{
$pid
}) {
next
unless
$gpsPID
{
$pid
};
$data
{
$pid
} =
''
;
}
$data
{
$pid
} .=
substr
(
$buff
,
$pos
,
$pEnd
-
$pos
);
}
my
$saveLen
;
if
(not
$pidType
{
$pid
} or
$pidType
{
$pid
} == 0x1b) {
$saveLen
= 1024;
}
elsif
(
$pidType
{
$pid
} == 0x15) {
$saveLen
= 1024;
$saveLen
=
$packLen
{
$pid
}
if
defined
$packLen
{
$pid
} and
$saveLen
>
$packLen
{
$pid
};
}
else
{
$saveLen
= 256;
}
if
(
length
(
$data
{
$pid
}) >=
$saveLen
) {
my
$more
= ParsePID(
$et
,
$pid
,
$pidType
{
$pid
},
$pidName
{
$pid
}, \
$data
{
$pid
});
next
if
$more
< 0;
$more
= 1
if
not
$more
and not
$fromStart
{
$pid
};
delete
$data
{
$pid
};
delete
$fromStart
{
$pid
};
$more
and
$needPID
{
$pid
} = -1,
next
;
delete
$needPID
{
$pid
};
$didPID
{
$pid
} = 1;
}
next
;
}
if
(
$needPID
{
$pid
}) {
delete
$needPID
{
$pid
};
$didPID
{
$pid
} = 1;
}
}
$endTime
=
$fwdTime
unless
defined
$endTime
;
if
(
defined
$startTime
and
defined
$endTime
) {
$endTime
+= 0x80000000 * 1200
if
$startTime
>
$endTime
;
$et
->HandleTag(
$tagTablePtr
,
'Duration'
,
$endTime
-
$startTime
);
}
if
(
$verbose
) {
my
@need
;
foreach
(
keys
%needPID
) {
push
@need
,
sprintf
(
'0x%.2x'
,
$_
)
if
$needPID
{
$_
} > 0;
}
if
(
@need
) {
@need
=
sort
@need
;
print
$out
"End of file. Missing PID(s): @need\n"
;
}
else
{
my
$what
=
$eof
?
'of file'
:
'scan'
;
print
$out
"End $what. All PID's parsed.\n"
;
}
}
my
$pid
;
foreach
$pid
(
sort
keys
%data
) {
ParsePID(
$et
,
$pid
,
$pidType
{
$pid
},
$pidName
{
$pid
}, \
$data
{
$pid
});
delete
$data
{
$pid
};
}
if
(
$et
->Options(
'ExtractEmbedded'
) and
$raf
->Seek(-8, 2) and
$raf
->Read(
$buff
, 8) == 8 and
$buff
=~ /^&&&&/)
{
my
$len
=
unpack
(
'x4N'
,
$buff
);
if
(
$len
<
$raf
->Tell() and
$raf
->Seek(-
$len
, 2) and
$raf
->Read(
$buff
,
$len
) ==
$len
) {
my
$tbl
= GetTagTable(
'Image::ExifTool::QuickTime::Stream'
);
my
%dirInfo
= (
DataPt
=> \
$buff
,
DirStart
=> 8,
DirName
=>
'LigoTrailer'
);
Image::ExifTool::LigoGPS::ProcessLigoGPS(
$et
, \
%dirInfo
,
$tbl
);
}
}
return
1;
}
1;
Hide Show 50 lines of Pod