$VERSION
=
'1.03'
;
sub
ReadBencode($$);
sub
ExtractTags($$$;$$@);
%Image::ExifTool::Torrent::Main
= (
GROUPS
=> {
2
=>
'Document'
},
NOTES
=>
q{
Below are tags commonly found in BitTorrent files. As well as these tags,
any other existing tags will be extracted. For convenience, list items are
expanded into individual tags with an index in the tag name, but only the
tags with index "1" are listed in the tables below. See
specification.
}
,
'announce'
=> { },
'announce-list'
=> {
Name
=>
'AnnounceList1'
},
'comment'
=> { },
'created by'
=> {
Name
=>
'Creator'
},
'creation date'
=> {
Name
=>
'CreateDate'
,
Groups
=> {
2
=>
'Time'
},
ValueConv
=>
'ConvertUnixTime($val,1)'
,
PrintConv
=>
'$self->ConvertDateTime($val)'
,
},
'encoding'
=> { },
'info'
=> {
SubDirectory
=> {
TagTable
=>
'Image::ExifTool::Torrent::Info'
} },
'url-list'
=> {
Name
=>
'URLList1'
},
);
%Image::ExifTool::Torrent::Info
= (
GROUPS
=> {
2
=>
'Document'
},
'file-duration'
=> {
Name
=>
'File1Duration'
},
'file-media'
=> {
Name
=>
'File1Media'
},
'files'
=> {
SubDirectory
=> {
TagTable
=>
'Image::ExifTool::Torrent::Files'
} },
'length'
=> { },
'md5sum'
=> {
Name
=>
'MD5Sum'
},
'name'
=> { },
'name.utf-8'
=> {
Name
=>
'NameUTF-8'
},
'piece length'
=> {
Name
=>
'PieceLength'
},
'pieces'
=> {
Name
=>
'Pieces'
,
Notes
=>
'concatenation of 20-byte SHA-1 digests for each piece'
,
},
'private'
=> { },
'profiles'
=> {
SubDirectory
=> {
TagTable
=>
'Image::ExifTool::Torrent::Profiles'
} },
);
%Image::ExifTool::Torrent::Profiles
= (
GROUPS
=> {
2
=>
'Document'
},
'width'
=> {
Name
=>
'Profile1Width'
},
'height'
=> {
Name
=>
'Profile1Height'
},
'acodec'
=> {
Name
=>
'Profile1AudioCodec'
},
'vcodec'
=> {
Name
=>
'Profile1VideoCodec'
},
);
%Image::ExifTool::Torrent::Files
= (
GROUPS
=> {
2
=>
'Document'
},
'length'
=> {
Name
=>
'File1Length'
,
PrintConv
=>
'ConvertFileSize($val)'
},
'md5sum'
=> {
Name
=>
'File1MD5Sum'
},
'path'
=> {
Name
=>
'File1Path'
,
JoinPath
=> 1 },
'path.utf-8'
=> {
Name
=>
'File1PathUTF-8'
,
JoinPath
=> 1 },
);
sub
ReadMore($$)
{
my
(
$raf
,
$dataPt
) =
@_
;
my
$buf2
;
my
$n
=
$raf
->Read(
$buf2
, 65536);
$$raf
{BencodeEOF} = 1
if
$n
!= 65536;
$$dataPt
=
substr
(
$$dataPt
,
pos
(
$$dataPt
)) .
$buf2
if
$n
;
return
$n
;
}
sub
ReadBencode($$)
{
my
(
$raf
,
$dataPt
) =
@_
;
my
$pos
=
pos
(
$$dataPt
);
return
undef
unless
defined
$pos
;
my
$remaining
=
length
(
$$dataPt
) -
$pos
;
ReadMore(
$raf
,
$dataPt
)
if
$remaining
< 64 and not
$$raf
{BencodeEOF};
$$dataPt
=~ /(.)/sg or
return
undef
;
my
$val
;
my
$tok
= $1;
if
(
$tok
eq
'i'
) {
$$dataPt
=~ /\G(-?\d+)e/g or
return
$val
;
$val
= $1;
}
elsif
(
$tok
eq
'd'
) {
$val
= { };
for
(;;) {
my
$k
= ReadBencode(
$raf
,
$dataPt
);
last
unless
defined
$k
;
if
(
ref
$k
) {
ref
$k
ne
'SCALAR'
and
$$raf
{BencodeError} =
'Bad dictionary key'
,
last
;
$k
=
$$k
;
}
my
$v
= ReadBencode(
$raf
,
$dataPt
);
last
unless
defined
$v
;
$$val
{
$k
} =
$v
;
}
}
elsif
(
$tok
eq
'l'
) {
$val
= [ ];
for
(;;) {
my
$v
= ReadBencode(
$raf
,
$dataPt
);
last
unless
defined
$v
;
push
@$val
,
$v
;
}
}
elsif
(
$tok
eq
'e'
) {
}
elsif
(
$tok
=~ /^\d$/ and
$$dataPt
=~ /\G(\d*):/g) {
my
$len
=
$tok
. $1;
my
$more
=
$len
- (
length
(
$$dataPt
) -
pos
(
$$dataPt
));
my
$value
;
if
(
$more
<= 0) {
$value
=
substr
(
$$dataPt
,
pos
(
$$dataPt
),
$len
);
pos
(
$$dataPt
) =
pos
(
$$dataPt
) +
$len
;
}
elsif
(
$more
> 10000000) {
$val
= \
"(Binary data $len bytes)"
if
$raf
->Seek(
$more
, 1);
}
else
{
my
$buff
;
my
$n
=
$raf
->Read(
$buff
,
$more
);
if
(
$n
==
$more
) {
$value
=
substr
(
$$dataPt
,
pos
(
$$dataPt
)) .
$buff
;
$$dataPt
=
''
;
pos
(
$$dataPt
) = 0;
}
}
if
(
defined
$value
) {
if
(
length
(
$value
) > 256 or
$value
=~ /[^\t\x20-\x7e]/) {
$val
= \
$value
;
}
else
{
$val
=
$value
;
}
}
elsif
(not
defined
$val
) {
$$raf
{BencodeError} =
'Truncated byte string'
;
}
}
else
{
$$raf
{BencodeError} =
'Bad format'
;
}
return
$val
;
}
sub
ExtractTags($$$;$$@)
{
my
(
$et
,
$hashPtr
,
$tagTablePtr
,
$baseID
,
$baseName
,
@index
) =
@_
;
my
$count
= 0;
my
$tag
;
foreach
$tag
(
sort
keys
%$hashPtr
) {
my
$val
=
$$hashPtr
{
$tag
};
my
(
$i
,
$j
,
@more
);
for
(;
defined
$val
;
$val
=
shift
@more
) {
my
$id
=
defined
$baseID
?
"$baseID/$tag"
:
$tag
;
unless
(
$$tagTablePtr
{
$id
}) {
my
$name
=
ucfirst
$tag
;
$name
=~ s/[^-_a-zA-Z0-9]+(.?)/\U$1/g;
$name
=
"Tag$name"
if
length
(
$name
) < 2 or
$name
!~ /^[A-Z]/;
$name
=
$baseName
.
$name
if
defined
$baseName
;
AddTagToTable(
$tagTablePtr
,
$id
, {
Name
=>
$name
});
$et
->VPrint(0,
" [adding $id '${name}']\n"
);
}
my
$tagInfo
=
$et
->GetTagInfo(
$tagTablePtr
,
$id
) or
next
;
if
(
ref
$val
eq
'ARRAY'
) {
if
(
$$tagInfo
{JoinPath}) {
$val
=
join
'/'
,
@$val
;
}
else
{
push
@more
,
@$val
;
next
if
ref
$more
[0] eq
'ARRAY'
;
$val
=
shift
@more
;
$i
or
$i
= 0,
push
(
@index
,
$i
);
}
}
$index
[-1] = ++
$i
if
defined
$i
;
if
(
@index
) {
$id
.=
join
'_'
,
@index
;
unless
(
$$tagTablePtr
{
$id
}) {
my
$name
=
$$tagInfo
{Name};
my
$n
= (
$name
=~
tr
/1/
for
(
$j
=0;
$j
<
$n
; ++
$j
) {
my
$idx
=
$index
[
$j
] ||
''
;
$name
=~ s/
}
for
(;
$j
<
@index
; ++
$j
) {
$name
.=
'_'
if
$name
=~ /\d$/;
$name
.=
$index
[
$j
];
}
AddTagToTable(
$tagTablePtr
,
$id
, {
%$tagInfo
,
Name
=>
$name
});
}
$tagInfo
=
$et
->GetTagInfo(
$tagTablePtr
,
$id
) or
next
;
}
if
(
ref
$val
eq
'HASH'
) {
my
(
$table
,
$rootID
,
$rootName
);
if
(
$$tagInfo
{SubDirectory}) {
$table
= GetTagTable(
$$tagInfo
{SubDirectory}{TagTable});
}
else
{
$table
=
$tagTablePtr
;
$rootID
=
$id
;
$rootName
=
$$tagInfo
{Name};
}
$count
+= ExtractTags(
$et
,
$val
,
$table
,
$rootID
,
$rootName
,
@index
);
}
else
{
$et
->HandleTag(
$tagTablePtr
,
$id
,
$val
);
++
$count
;
}
}
pop
@index
if
defined
$i
;
}
return
$count
;
}
sub
ProcessTorrent($$)
{
my
(
$et
,
$dirInfo
) =
@_
;
my
$success
= 0;
my
$raf
=
$$dirInfo
{RAF};
my
$buff
=
''
;
pos
(
$buff
) = 0;
my
$dict
= ReadBencode(
$raf
, \
$buff
);
my
$err
=
$$raf
{BencodeError};
$et
->Warn(
"Bencode error: $err"
)
if
$err
;
if
(
ref
$dict
eq
'HASH'
and
$$dict
{announce}) {
$et
->SetFileType();
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::Torrent::Main'
);
ExtractTags(
$et
,
$dict
,
$tagTablePtr
) and
$success
= 1;
}
return
$success
;
}
1;