#!/usr/bin/env perl

#    Copyright (C) 2007 Tommy Persson, tpe@ida.liu.se
#
#    mobi2html, Copyright (C) 2007 Tommy Persson, tpe@ida.liu.se
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.


use FindBin qw($RealBin);
use lib "$RealBin";

#
# Palm: Cover images and book works for version 3
# Palm: Cover images and book works for version 4 but 
#       page number indicator wrong
# Palm: Author does not work for version 3
# Palm: Author does not work for version 4
#
# Version 4 on Cybook still give me multi book message...
#
# Ring of fire, Author information not visile on Palm
#
# Old Curiosity Shop: Cover image: record-7307596
#                           start: record-7307264
#

#
# TODO:
#
# - add extended tags to existing tags, test with I, Robot
# - perldoc, document which packages are needed
# - add arbitrary meta information...
# - edit extended title
# 

#
# These packages need to be installed via CPAN or via distribution
# specific packages.
#

use Palm::PDB;
use Palm::Doc;
use Date::Format;  # for ctime call
use Getopt::Mixed;
use Image::Size;   # Used to recognize the first record that is an image

#
# The MobiHeader package file is in the distribution
#

use MobiHeader;


#
# 
#

use strict;

use vars qw ($opt_outfile $opt_title $opt_prefixtitle $opt_author 
	     $opt_addthumbnail);

Getopt::Mixed::getOptions ("outfile=s title=s prefixtitle=s author=s 
                            addthumbnail=s");

my %exth_item_ids = (1 => "drm_server_id",
		   2 => "drm_commerce_id",
		   3 => "drm_ebookbase_book_id",
		   100 => "Author",
		   101 => "Publisher",
		   102 => "Imprint",
		   104 => "ISBN",
		   105 => "Subject",
		   106 => "PublishingDate",
		   107 => "Review",
		   108 => "Contributor",
		   109 => "Rights",
		   110 => "SubjectCode",
		   111 => "Type",
		   112 => "Source",
		   113 => "ASIN",
		   114 => "VersionNumber",
		   115 => "Sample",
		   116 => "StartReading",
		   203 => "hasFakeCover");

my %donotprint = (300 => 1,
		  201 => 1,
		  202 => 1);


my $filename = shift;

my $pdb = new Palm::PDB;
$pdb->Load($filename);

my $name = $pdb->{"name"};
my $version = $pdb->{"version"};
my $type = $pdb->{"type"};
my $creator = $pdb->{"creator"};
my $seed = $pdb->{"uniqueIDseed"};
my $ctime = $pdb->{"ctime"};
my $mtime = $pdb->{"mtime"};
my $btime = $pdb->{"baktime"};
my $sctime = ctime ($ctime);
my $smtime = ctime ($mtime);
my $sbtime = ctime ($btime);

print "Name: $name\n";
print "Version: $version\n";
print "Type: $type\n";
print "Creator: $creator\n";
print "Seed: $seed\n";
print "Resdb: " . $pdb->{"attributes"}{"ResDB"} . "\n";
print "AppInfoDirty: " . $pdb->{"attributes"}{"AppInfoDirty"} . "\n";
print "ctime: $ctime - $sctime";
print "mtime: $mtime - $smtime";
print "baktime: $btime - $sbtime";


my @records = @{$pdb->{"records"}};

#
# Find the first record with an image
#

my $imgindex = 0;
foreach my $r (@records) {
    my $data = $r->{"data"};
    my ($x, $y) = imgsize (\$data);
    if (defined $x) {
	print "Image record index: $imgindex ($x x $y)\n";
	last;
    }
    $imgindex++;
}



my $r0 = $records[0];

my $ismobi = parse_record_0 ($r0->{"data"});
my $palmdocheader = substr ($r0->{"data"}, 0, 16);

my $lastid = $records[$#records]->{"id"};

print "LASTID: $lastid\n";

print "-----------------\n";

if (defined $opt_outfile) {
    if ($ismobi) {
	my $mobiheader = substr ($r0->{"data"}, 16);
	if (defined $opt_title) {
	    print STDERR "Setting new title: $opt_title\n";
	    $mobiheader = MobiHeader::set_extended_title ($mobiheader, $opt_title);
	}

	if (defined $opt_prefixtitle) {
	    my $title = MobiHeader::get_extended_title ($mobiheader);
	    $title = $opt_prefixtitle . $title;
	    print STDERR "Setting new title: $title\n";
	    $mobiheader = MobiHeader::set_extended_title ($mobiheader, $title);
	}

	$r0->{"data"} = $palmdocheader . $mobiheader;

    } else {
	my $mh = new MobiHeader;

	my $title = $filename;
	$title =~ s/\.prc$//;
	$title =~ s/\.pdb$//;
	$title =~ s/\.mobi$//;

	$mh->set_title ($title);
	$mh->set_title ($opt_title) if defined $opt_title;
	$mh->set_author ($opt_author) if defined $opt_author;
	$mh->set_image_record_index ($imgindex);

	$pdb->{"type"} = "BOOK";
	$pdb->{"creator"} = "MOBI";
	
	$r0->{"data"} = $palmdocheader . $mh->get_data ();
    }

    if (defined $opt_addthumbnail) {
	my $img = Palm::PDB->new_Record();
	$img->{"categori"} = 0;
	$img->{"attributes"}{"Dirty"} = 1;
	$img->{"id"} = $lastid+1;
	my $data = get_cover_image_data ($opt_addthumbnail);
	$img->{"data"} = $data;
	$pdb->append_Record ($img);
    }

    $pdb->Write ($opt_outfile);
}


sub parse_record_0 {
    my $rec = shift;
    my $res = 0;
    my $palmdocheader = substr ($rec, 0, 16);
    parse_palmdoc_header ($palmdocheader);
    if ($type eq "BOOK" and $creator eq "MOBI") {
	my $mobiheader = substr ($rec, 16);
	parse_mobi_header ($mobiheader);
	$res = 1;
    }
    return $res;
}


sub parse_palmdoc_header {
    my $data = shift;
    my ($version, $length, $nrecords, $recsize, $unknown) =
	unpack ("nxxNnnN", $data);
    print "PDHEADER  Version: $version\n";
    print "PDHEADER   Length: $length\n";
    print "PDHEADER NRecords: $nrecords\n";
    print "PDHEADER  Recsize: $recsize\n";
    print "PDHEADER  Unknown: $unknown\n";
}

sub parse_mobi_header {
    my $data = shift;
    my ($doctype, $length, $type, $codepage, $uniqueid, $ver) =
	unpack ("a4NNNNN", $data);
    my ($exthflg) = unpack ("N", substr ($data, 0x70));

    print "MOBIHEADER doctype: $doctype\n";
    print "MOBIHEADER  length: $length\n";
    print "MOBIHEADER    type: $type\n";
    print "MOBIHEADER   codep: $codepage\n";
    print "MOBIHEADER  uniqid: $uniqueid\n";
    print "MOBIHEADER     ver: $ver\n";
    print "MOBIHEADER exthflg: $exthflg\n";

    if ($exthflg & 0x40) {
	my $exth = substr ($data, $length);
	parse_mobi_exth ($exth);
    }
    my $title = MobiHeader::get_extended_title ($data);
    print "EXTENDEDTITLE: $title\n";
}

sub parse_mobi_exth {
    my $data = shift;
    my ($doctype, $len, $n_items) = unpack ("a4NN", $data);
    print "EXTH doctype: $doctype\n";
    print "EXTH  length: $len\n";
    print "EXTH n_items: $n_items\n";
    my $pos = 12;
    foreach (1..$n_items) {
	my ($id, $size) = unpack ("NN", substr ($data, $pos));
	my $contlen = $size-8;
	my ($id, $size, $content) = unpack ("NNa$contlen", substr ($data, $pos));
	my $hid = sprintf ("%x", $id);
	my $hsize = sprintf ("%x", $size);
	if (not $donotprint{$id}) {
	    print "ITEM: $id - $exth_item_ids{$id} - $content\n";
	}
	$pos += $size;
    }
}


sub get_cover_image_data {
    my $filename = shift;
    my $data = "";

    if (not -e $filename) {
	print STDERR "Image file does not exist: $filename\n";
	return $data;
    }

    open(IMG, $filename) or die "can't open $filename: $!";
    binmode(IMG);       # now DOS won't mangle binary input from GIF
    my $buff;
    while (read(IMG, $buff, 8 * 2**10)) {
	$data .= $buff;
    }
    return $data;
}


=pod

=head1 NAME

mobi2mobi - A program to manipulate mobi files

=head1 SYNOPSIS

mobi2mobi file.mobi

mobi2mobi --outfile out.mobi --prefixtitle "01-" file.mobi

=head1 DESCRIPTION

A program to manipulate MobiPocket files. Author and title can be set
and a cover image (thumb nail image for Gen3) can be added.

There are two kind of prc files used for electronic books. One is a
PalmDOC file which does not have a MOBI header but can contain HTML
code marked up with MobiPocket specific markup and it can be read by a
MobiPocket reader. For this format you cannot store meta information
in the header. The other format is MobiPocket and it has a MOBI header
and some additional data where you can store meta information and an
extended title.

This program can change the extended title for a MobiPocket file. It
can also automatically convert a PalmDOC file to a MobiPocket file and
set the title.

It can also add author information to a PalmDOC file by converting it
to a MobiPocket file and set the author meta information.

You can also add a prefix to a title in a MobiPocket file. This does
not work for PalmDOC files.

It is possible to add an image to the file. If there are no other
images in the file then the added image will be used as cover image
and thumb nail image for Gen3.

Just running the program on a mobifile without any flags will print
some information about the file.

Since there is no specification available for the MOBI header this
program might generate books that are not entirely correct. So keep the
original file...

=head1 OPTIONS

=over 4

=item B<--title TITLE>

Change the the title of the book.

=item B<--prefixtitle prefix>

Add a prefix to the title of the book.

=item B<--author AUTHOR>

Set the author of the book.

=item B<--addthumbnail IMAGEFILE>

Add an image that will be used as thumbnail image for Gen3 devices.
This will only work if the book contains no other images.

=item B<--outfile OUTFILE>

Name of the output file. No conversion is done if this is not
specified.


=back

=head1 EXAMPLES

   mobi2mobi file.mobi

   mobi2mobi --outfile out.mobi --prefixtitle "01-" file.mobi

   mobi2mobi --outfile out.mobi --title "Correction of title" file.mobi

   mobi2mobi --addthumbnail cover.jpg --outfile out.mobi file.mobi


=head1 TODO

   - Functionality to change and add author information to a
     MobiPocket file.

=head1 BUGS


=head1 AUTHOR

Tommy Persson (tpe@ida.liu.se)

=cut




