123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- #------------------------------------------------------------------------------
- # File: DjVu.pm
- #
- # Description: Read DjVu archive meta information
- #
- # Revisions: 09/25/2008 - P. Harvey Created
- #
- # References: 1) http://djvu.sourceforge.net/ (DjVu v3 specification, Nov 2005)
- # 2) http://www.djvu.org/
- #
- # Notes: DjVu files are recognized and the IFF structure is processed
- # by Image::ExifTool::AIFF
- #------------------------------------------------------------------------------
- package Image::ExifTool::DjVu;
- use strict;
- use vars qw($VERSION);
- use Image::ExifTool qw(:DataAccess :Utils);
- $VERSION = '1.05';
- sub ParseAnt($);
- sub ProcessAnt($$$);
- sub ProcessMeta($$$);
- sub ProcessBZZ($$$);
- # DjVu chunks that we parse (ref 4)
- %Image::ExifTool::DjVu::Main = (
- GROUPS => { 2 => 'Image' },
- NOTES => q{
- Information is extracted from the following chunks in DjVu images. See
- L<http://www.djvu.org/> for the DjVu specification.
- },
- INFO => {
- SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Info' },
- },
- FORM => {
- TypeOnly => 1, # extract chunk type only, then descend into chunk
- SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Form' },
- },
- ANTa => {
- SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Ant' },
- },
- ANTz => {
- Name => 'CompressedAnnotation',
- SubDirectory => {
- TagTable => 'Image::ExifTool::DjVu::Ant',
- ProcessProc => \&ProcessBZZ,
- }
- },
- INCL => 'IncludedFileID',
- );
- # information in the DjVu INFO chunk
- %Image::ExifTool::DjVu::Info = (
- PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
- GROUPS => { 2 => 'Image' },
- FORMAT => 'int8u',
- PRIORITY => 0, # first INFO block takes priority
- 0 => {
- Name => 'ImageWidth',
- Format => 'int16u',
- },
- 2 => {
- Name => 'ImageHeight',
- Format => 'int16u',
- },
- 4 => {
- Name => 'DjVuVersion',
- Description => 'DjVu Version',
- Format => 'int8u[2]',
- # (this may be just one byte as with version 0.16)
- ValueConv => '$val=~/(\d+) (\d+)/ ? "$2.$1" : "0.$val"',
- },
- 6 => {
- Name => 'SpatialResolution',
- Format => 'int16u',
- ValueConv => '(($val & 0xff)<<8) + ($val>>8)', # (little-endian!)
- },
- 8 => {
- Name => 'Gamma',
- ValueConv => '$val / 10',
- },
- 9 => {
- Name => 'Orientation',
- Mask => 0x07, # (upper 5 bits reserved)
- PrintConv => {
- 1 => 'Horizontal (normal)',
- 2 => 'Rotate 180',
- 5 => 'Rotate 90 CW',
- 6 => 'Rotate 270 CW',
- },
- },
- );
- # information in the DjVu FORM chunk
- %Image::ExifTool::DjVu::Form = (
- PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
- GROUPS => { 2 => 'Image' },
- 0 => {
- Name => 'SubfileType',
- Format => 'undef[4]',
- Priority => 0,
- PrintConv => {
- DJVU => 'Single-page image',
- DJVM => 'Multi-page document',
- PM44 => 'Color IW44',
- BM44 => 'Grayscale IW44',
- DJVI => 'Shared component',
- THUM => 'Thumbnail image',
- },
- },
- );
- # tags found in the DjVu annotation chunk (ANTz or ANTa)
- %Image::ExifTool::DjVu::Ant = (
- PROCESS_PROC => \&Image::ExifTool::DjVu::ProcessAnt,
- GROUPS => { 2 => 'Image' },
- NOTES => 'Information extracted from annotation chunks.',
- # Note: For speed, ProcessAnt() pre-scans for known tag ID's, so if any
- # new tags are added here they must also be added to the pre-scan check
- metadata => {
- SubDirectory => { TagTable => 'Image::ExifTool::DjVu::Meta' }
- },
- xmp => {
- Name => 'XMP',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' }
- },
- );
- # tags found in the DjVu annotation metadata
- %Image::ExifTool::DjVu::Meta = (
- PROCESS_PROC => \&Image::ExifTool::DjVu::ProcessMeta,
- GROUPS => { 1 => 'DjVu-Meta', 2 => 'Image' },
- NOTES => q{
- This table lists the standard DjVu metadata tags, but ExifTool will extract
- any tags that exist even if they don't appear here. The DjVu v3
- documentation endorses tags borrowed from two standards: 1) BibTeX
- bibliography system tags (all lowercase Tag ID's in the table below), and 2)
- PDF DocInfo tags (capitalized Tag ID's).
- },
- # BibTeX tags (ref http://en.wikipedia.org/wiki/BibTeX)
- address => { Groups => { 2 => 'Location' } },
- annote => { Name => 'Annotation' },
- author => { Groups => { 2 => 'Author' } },
- booktitle => { Name => 'BookTitle' },
- chapter => { },
- crossref => { Name => 'CrossRef' },
- edition => { },
- eprint => { Name => 'EPrint' },
- howpublished=> { Name => 'HowPublished' },
- institution => { },
- journal => { },
- key => { },
- month => { Groups => { 2 => 'Time' } },
- note => { },
- number => { },
- organization=> { },
- pages => { },
- publisher => { },
- school => { },
- series => { },
- title => { },
- type => { },
- url => { Name => 'URL' },
- volume => { },
- year => { Groups => { 2 => 'Time' } },
- # PDF tags (same as Image::ExifTool::PDF::Info)
- Title => { },
- Author => { Groups => { 2 => 'Author' } },
- Subject => { },
- Keywords => { },
- Creator => { },
- Producer => { },
- CreationDate => {
- Name => 'CreateDate',
- Groups => { 2 => 'Time' },
- # RFC 3339 date/time format
- ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)',
- PrintConv => '$self->ConvertDateTime($val)',
- },
- ModDate => {
- Name => 'ModifyDate',
- Groups => { 2 => 'Time' },
- ValueConv => 'require Image::ExifTool::XMP; Image::ExifTool::XMP::ConvertXMPDate($val)',
- PrintConv => '$self->ConvertDateTime($val)',
- },
- Trapped => {
- # remove leading '/' from '/True' or '/False'
- ValueConv => '$val=~s{^/}{}; $val',
- },
- );
- #------------------------------------------------------------------------------
- # Parse DjVu annotation "s-expression" syntax (recursively)
- # Inputs: 0) data ref (with pos($$dataPt) set to start of annotation)
- # Returns: reference to list of tokens/references, or undef if no tokens,
- # and the position in $$dataPt is set to end of last token
- # Notes: The DjVu annotation syntax is not well documented, so I make
- # a number of assumptions here!
- sub ParseAnt($)
- {
- my $dataPt = shift;
- my (@toks, $tok, $more);
- # (the DjVu annotation syntax really sucks, and requires that every
- # single token be parsed in order to properly scan through the items)
- Tok: for (;;) {
- # find the next token
- last unless $$dataPt =~ /(\S)/sg; # get next non-space character
- if ($1 eq '(') { # start of list
- $tok = ParseAnt($dataPt);
- } elsif ($1 eq ')') { # end of list
- $more = 1;
- last;
- } elsif ($1 eq '"') { # quoted string
- $tok = '';
- for (;;) {
- # get string up to the next quotation mark
- # this doesn't work in perl 5.6.2! grrrr
- # last Tok unless $$dataPt =~ /(.*?)"/sg;
- # $tok .= $1;
- my $pos = pos($$dataPt);
- last Tok unless $$dataPt =~ /"/sg;
- $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
- # we're good unless quote was escaped by odd number of backslashes
- last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
- $tok .= '"'; # quote is part of the string
- }
- # convert C escape sequences (allowed in quoted text)
- $tok = eval qq{"$tok"};
- } else { # key name
- pos($$dataPt) = pos($$dataPt) - 1;
- # allow anything in key but whitespace, braces and double quotes
- # (this is one of those assumptions I mentioned)
- $$dataPt =~ /([^\s()"]+)/sg;
- $tok = $1;
- }
- push @toks, $tok if defined $tok;
- }
- # prevent further parsing unless more after this
- pos($$dataPt) = length $$dataPt unless $more;
- return @toks ? \@toks : undef;
- }
- #------------------------------------------------------------------------------
- # Process DjVu annotation chunk (ANTa or decoded ANTz)
- # Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref
- # Returns: 1 on success
- sub ProcessAnt($$$)
- {
- my ($et, $dirInfo, $tagTablePtr) = @_;
- my $dataPt = $$dirInfo{DataPt};
- # quick pre-scan to check for metadata or XMP
- return 1 unless $$dataPt =~ /\(\s*(metadata|xmp)[\s("]/s;
- # parse annotations into a tree structure
- pos($$dataPt) = 0;
- my $toks = ParseAnt($dataPt) or return 0;
- # process annotations individually
- my $ant;
- foreach $ant (@$toks) {
- next unless ref $ant eq 'ARRAY' and @$ant >= 2;
- my $tag = shift @$ant;
- next if ref $tag or not defined $$tagTablePtr{$tag};
- if ($tag eq 'metadata') {
- # ProcessMeta() takes array reference
- $et->HandleTag($tagTablePtr, $tag, $ant);
- } else {
- next if ref $$ant[0]; # only process simple values
- $et->HandleTag($tagTablePtr, $tag, $$ant[0]);
- }
- }
- return 1;
- }
- #------------------------------------------------------------------------------
- # Process DjVu metadata
- # Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref
- # Returns: 1 on success
- # Notes: input dirInfo DataPt is a reference to a list of pre-parsed metadata entries
- sub ProcessMeta($$$)
- {
- my ($et, $dirInfo, $tagTablePtr) = @_;
- my $dataPt = $$dirInfo{DataPt};
- return 0 unless ref $$dataPt eq 'ARRAY';
- $et->VerboseDir('Metadata', scalar @$$dataPt);
- my ($item, $err);
- foreach $item (@$$dataPt) {
- # make sure item is a simple tag/value pair
- $err=1, next unless ref $item eq 'ARRAY' and @$item >= 2 and
- not ref $$item[0] and not ref $$item[1];
- # add any new tags to the table
- unless ($$tagTablePtr{$$item[0]}) {
- my $name = $$item[0];
- $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters
- length $name or $err = 1, next;
- AddTagToTable($tagTablePtr, $$item[0], { Name => ucfirst($name) });
- }
- $et->HandleTag($tagTablePtr, $$item[0], $$item[1]);
- }
- $err and $et->Warn('Ignored invalid metadata entry(s)');
- return 1;
- }
- #------------------------------------------------------------------------------
- # Process BZZ-compressed data (in DjVu images)
- # Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref
- # Returns: 1 on success
- sub ProcessBZZ($$$)
- {
- my ($et, $dirInfo, $tagTablePtr) = @_;
- require Image::ExifTool::BZZ;
- my $buff = Image::ExifTool::BZZ::Decode($$dirInfo{DataPt});
- unless (defined $buff) {
- $et->Warn("Error decoding $$dirInfo{DirName}");
- return 0;
- }
- my $verbose = $et->Options('Verbose');
- if ($verbose >= 3) {
- # dump the decoded data in very verbose mode
- $et->VerboseDir("Decoded $$dirInfo{DirName}", 0, length $buff);
- $et->VerboseDump(\$buff);
- }
- $$dirInfo{DataPt} = \$buff;
- $$dirInfo{DataLen} = $$dirInfo{DirLen} = length $buff;
- # process the data using the default process proc for this table
- my $processProc = $$tagTablePtr{PROCESS_PROC} or return 0;
- return &$processProc($et, $dirInfo, $tagTablePtr);
- }
- 1; # end
- __END__
- =head1 NAME
- Image::ExifTool::DjVu - Read DjVu meta information
- =head1 SYNOPSIS
- This module is used by Image::ExifTool
- =head1 DESCRIPTION
- This module contains definitions required by Image::ExifTool to extract meta
- information from DjVu images. Parsing of the DjVu IFF structure is done by
- Image::ExifTool::AIFF.
- =head1 AUTHOR
- Copyright 2003-2016, Phil Harvey (phil at owl.phy.queensu.ca)
- This library is free software; you can redistribute it and/or modify it
- under the same terms as Perl itself.
- =head1 REFERENCES
- =over 4
- =item L<http://djvu.sourceforge.net/>
- =item L<http://www.djvu.org/>
- =back
- =head1 SEE ALSO
- L<Image::ExifTool::TagNames/DjVu Tags>,
- L<Image::ExifTool::AIFF(3pm)|Image::ExifTool::AIFF>,
- L<Image::ExifTool(3pm)|Image::ExifTool>
- =cut
|