libmobi
C library for handling MOBI format ebook documents
mobitool.c

Program for testing libmobi library

Copyright (c) 2016 Bartek Fabiszewski http://www.fabiszewski.net

Licensed under LGPL, either version 3, or any later. See http://www.gnu.org/licenses/

#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#include <direct.h>
#include "win32/getopt.h"
#else
#include <unistd.h>
#endif
#include <ctype.h>
#include <time.h>
#include <errno.h>
/* include libmobi header */
#include <mobi.h>
#include "common.h"
#ifdef HAVE_CONFIG_H
# include "../config.h"
#endif
/* miniz file is needed for EPUB creation */
#ifdef USE_XMLWRITER
# define MINIZ_HEADER_FILE_ONLY
# define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
# include "../src/miniz.c"
#endif
#ifdef HAVE_SYS_RESOURCE_H
/* rusage */
# include <sys/resource.h>
# define PRINT_RUSAGE_ARG "u"
#else
# define PRINT_RUSAGE_ARG ""
#endif
/* encryption */
#ifdef USE_ENCRYPTION
# define PRINT_ENC_USG " [-p pid] [-P serial]"
# define PRINT_ENC_ARG "p:P:"
#else
# define PRINT_ENC_USG ""
# define PRINT_ENC_ARG ""
#endif
/* xmlwriter */
#ifdef USE_XMLWRITER
# define PRINT_EPUB_ARG "e"
#else
# define PRINT_EPUB_ARG ""
#endif
#define EPUB_CONTAINER "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n\
<rootfiles>\n\
<rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n\
</rootfiles>\n\
</container>"
#define EPUB_MIMETYPE "application/epub+zip"
/* command line options */
int dump_cover_opt = 0;
int dump_rawml_opt = 0;
int create_epub_opt = 0;
int print_extended_meta_opt = 0;
int print_rec_meta_opt = 0;
int dump_rec_opt = 0;
int parse_kf7_opt = 0;
int dump_parts_opt = 0;
int print_rusage_opt = 0;
int outdir_opt = 0;
int extract_source_opt = 0;
#ifdef USE_ENCRYPTION
int setpid_opt = 0;
int setserial_opt = 0;
#endif
/* options values */
char outdir[FILENAME_MAX];
#ifdef USE_ENCRYPTION
char *pid = NULL;
char *serial = NULL;
#endif
void print_meta(const MOBIData *m) {
/* Full name stored at offset given in MOBI header */
if (m->mh && m->mh->full_name) {
char full_name[FULLNAME_MAX + 1];
if (mobi_get_fullname(m, full_name, FULLNAME_MAX) == MOBI_SUCCESS) {
printf("\nFull name: %s\n", full_name);
}
}
/* Palm database header */
if (m->ph) {
printf("\nPalm doc header:\n");
printf("name: %s\n", m->ph->name);
printf("attributes: %hu\n", m->ph->attributes);
printf("version: %hu\n", m->ph->version);
struct tm * timeinfo = mobi_pdbtime_to_time(m->ph->ctime);
printf("ctime: %s", asctime(timeinfo));
timeinfo = mobi_pdbtime_to_time(m->ph->mtime);
printf("mtime: %s", asctime(timeinfo));
timeinfo = mobi_pdbtime_to_time(m->ph->btime);
printf("btime: %s", asctime(timeinfo));
printf("mod_num: %u\n", m->ph->mod_num);
printf("appinfo_offset: %u\n", m->ph->appinfo_offset);
printf("sortinfo_offset: %u\n", m->ph->sortinfo_offset);
printf("type: %s\n", m->ph->type);
printf("creator: %s\n", m->ph->creator);
printf("uid: %u\n", m->ph->uid);
printf("next_rec: %u\n", m->ph->next_rec);
printf("rec_count: %u\n", m->ph->rec_count);
}
/* Record 0 header */
if (m->rh) {
printf("\nRecord 0 header:\n");
printf("compresion type: %u\n", m->rh->compression_type);
printf("text length: %u\n", m->rh->text_length);
printf("text record count: %u\n", m->rh->text_record_count);
printf("text record size: %u\n", m->rh->text_record_size);
printf("encryption type: %u\n", m->rh->encryption_type);
printf("unknown: %u\n", m->rh->unknown1);
}
/* Mobi header */
if (m->mh) {
printf("\nMOBI header:\n");
printf("identifier: %s\n", m->mh->mobi_magic);
if(m->mh->header_length) { printf("header length: %u\n", *m->mh->header_length); }
if(m->mh->mobi_type) { printf("mobi type: %u\n", *m->mh->mobi_type); }
if(m->mh->text_encoding) { printf("text encoding: %u\n", *m->mh->text_encoding); }
if(m->mh->uid) { printf("unique id: %u\n", *m->mh->uid); }
if(m->mh->version) { printf("file version: %u\n", *m->mh->version); }
if(m->mh->orth_index) { printf("orth index: %u\n", *m->mh->orth_index); }
if(m->mh->infl_index) { printf("infl index: %u\n", *m->mh->infl_index); }
if(m->mh->names_index) { printf("names index: %u\n", *m->mh->names_index); }
if(m->mh->keys_index) { printf("keys index: %u\n", *m->mh->keys_index); }
if(m->mh->extra0_index) { printf("extra0 index: %u\n", *m->mh->extra0_index); }
if(m->mh->extra1_index) { printf("extra1 index: %u\n", *m->mh->extra1_index); }
if(m->mh->extra2_index) { printf("extra2 index: %u\n", *m->mh->extra2_index); }
if(m->mh->extra3_index) { printf("extra3 index: %u\n", *m->mh->extra3_index); }
if(m->mh->extra4_index) { printf("extra4 index: %u\n", *m->mh->extra4_index); }
if(m->mh->extra5_index) { printf("extra5 index: %u\n", *m->mh->extra5_index); }
if(m->mh->non_text_index) { printf("non text index: %u\n", *m->mh->non_text_index); }
if(m->mh->full_name_offset) { printf("full name offset: %u\n", *m->mh->full_name_offset); }
if(m->mh->full_name_length) { printf("full name length: %u\n", *m->mh->full_name_length); }
if(m->mh->locale) {
const char *locale_string = mobi_get_locale_string(*m->mh->locale);
if (locale_string) {
printf("locale: %s (%u)\n", locale_string, *m->mh->locale);
} else {
printf("locale: unknown (%u)\n", *m->mh->locale);
}
}
if(m->mh->dict_input_lang) {
const char *locale_string = mobi_get_locale_string(*m->mh->dict_input_lang);
if (locale_string) {
printf("dict input lang: %s (%u)\n", locale_string, *m->mh->dict_input_lang);
} else {
printf("dict input lang: unknown (%u)\n", *m->mh->dict_input_lang);
}
}
if(m->mh->dict_output_lang) {
const char *locale_string = mobi_get_locale_string(*m->mh->dict_output_lang);
if (locale_string) {
printf("dict output lang: %s (%u)\n", locale_string, *m->mh->dict_output_lang);
} else {
printf("dict output lang: unknown (%u)\n", *m->mh->dict_output_lang);
}
}
if(m->mh->min_version) { printf("minimal version: %u\n", *m->mh->min_version); }
if(m->mh->image_index) { printf("first image index: %u\n", *m->mh->image_index); }
if(m->mh->huff_rec_index) { printf("huffman record offset: %u\n", *m->mh->huff_rec_index); }
if(m->mh->huff_rec_count) { printf("huffman records count: %u\n", *m->mh->huff_rec_count); }
if(m->mh->datp_rec_index) { printf("DATP record offset: %u\n", *m->mh->datp_rec_index); }
if(m->mh->datp_rec_count) { printf("DATP records count: %u\n", *m->mh->datp_rec_count); }
if(m->mh->exth_flags) { printf("EXTH flags: %u\n", *m->mh->exth_flags); }
if(m->mh->unknown6) { printf("unknown: %u\n", *m->mh->unknown6); }
if(m->mh->drm_offset) { printf("drm offset: %u\n", *m->mh->drm_offset); }
if(m->mh->drm_count) { printf("drm count: %u\n", *m->mh->drm_count); }
if(m->mh->drm_size) { printf("drm size: %u\n", *m->mh->drm_size); }
if(m->mh->drm_flags) { printf("drm flags: %u\n", *m->mh->drm_flags); }
if(m->mh->first_text_index) { printf("first text index: %u\n", *m->mh->first_text_index); }
if(m->mh->last_text_index) { printf("last text index: %u\n", *m->mh->last_text_index); }
if(m->mh->fdst_index) { printf("FDST offset: %u\n", *m->mh->fdst_index); }
if(m->mh->fdst_section_count) { printf("FDST count: %u\n", *m->mh->fdst_section_count); }
if(m->mh->fcis_index) { printf("FCIS index: %u\n", *m->mh->fcis_index); }
if(m->mh->fcis_count) { printf("FCIS count: %u\n", *m->mh->fcis_count); }
if(m->mh->flis_index) { printf("FLIS index: %u\n", *m->mh->flis_index); }
if(m->mh->flis_count) { printf("FLIS count: %u\n", *m->mh->flis_count); }
if(m->mh->unknown10) { printf("unknown: %u\n", *m->mh->unknown10); }
if(m->mh->unknown11) { printf("unknown: %u\n", *m->mh->unknown11); }
if(m->mh->srcs_index) { printf("SRCS index: %u\n", *m->mh->srcs_index); }
if(m->mh->srcs_count) { printf("SRCS count: %u\n", *m->mh->srcs_count); }
if(m->mh->unknown12) { printf("unknown: %u\n", *m->mh->unknown12); }
if(m->mh->unknown13) { printf("unknown: %u\n", *m->mh->unknown13); }
if(m->mh->extra_flags) { printf("extra record flags: %u\n", *m->mh->extra_flags); }
if(m->mh->ncx_index) { printf("NCX offset: %u\n", *m->mh->ncx_index); }
if(m->mh->unknown14) { printf("unknown: %u\n", *m->mh->unknown14); }
if(m->mh->unknown15) { printf("unknown: %u\n", *m->mh->unknown15); }
if(m->mh->fragment_index) { printf("fragment index: %u\n", *m->mh->fragment_index); }
if(m->mh->skeleton_index) { printf("skeleton index: %u\n", *m->mh->skeleton_index); }
if(m->mh->datp_index) { printf("DATP index: %u\n", *m->mh->datp_index); }
if(m->mh->unknown16) { printf("unknown: %u\n", *m->mh->unknown16); }
if(m->mh->guide_index) { printf("guide index: %u\n", *m->mh->guide_index); }
if(m->mh->unknown17) { printf("unknown: %u\n", *m->mh->unknown17); }
if(m->mh->unknown18) { printf("unknown: %u\n", *m->mh->unknown18); }
if(m->mh->unknown19) { printf("unknown: %u\n", *m->mh->unknown19); }
if(m->mh->unknown20) { printf("unknown: %u\n", *m->mh->unknown20); }
}
}
void print_records_meta(const MOBIData *m) {
/* Linked list of MOBIPdbRecord structures holds records data and metadata */
const MOBIPdbRecord *currec = m->rec;
while (currec != NULL) {
printf("offset: %u\n", currec->offset);
printf("size: %zu\n", currec->size);
printf("attributes: %hhu\n", currec->attributes);
printf("uid: %u\n", currec->uid);
printf("\n");
currec = currec->next;
}
}
int dump_records(const MOBIData *m, const char *fullpath) {
char dirname[FILENAME_MAX];
char basename[FILENAME_MAX];
split_fullpath(fullpath, dirname, basename);
char newdir[FILENAME_MAX];
if (outdir_opt) {
snprintf(newdir, sizeof(newdir), "%s%s_records", outdir, basename);
} else {
snprintf(newdir, sizeof(newdir), "%s%s_records", dirname, basename);
}
printf("Saving records to %s\n", newdir);
errno = 0;
if (mt_mkdir(newdir) != 0 && errno != EEXIST) {
int errsv = errno;
printf("Creating directory failed (%s)\n", strerror(errsv));
return ERROR;
}
/* Linked list of MOBIPdbRecord structures holds records data and metadata */
const MOBIPdbRecord *currec = m->rec;
int i = 0;
while (currec != NULL) {
char name[FILENAME_MAX];
snprintf(name, sizeof(name), "%s%crecord_%i_uid_%i", newdir, separator, i++, currec->uid);
errno = 0;
FILE *file = fopen(name, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", name, strerror(errsv));
return ERROR;
}
fwrite(currec->data, 1, currec->size, file);
fclose(file);
currec = currec->next;
}
return SUCCESS;
}
int dump_rawml(const MOBIData *m, const char *fullpath) {
char dirname[FILENAME_MAX];
char basename[FILENAME_MAX];
split_fullpath(fullpath, dirname, basename);
char newdir[FILENAME_MAX];
if (outdir_opt) {
snprintf(newdir, sizeof(newdir), "%s%s.rawml", outdir, basename);
} else {
snprintf(newdir, sizeof(newdir), "%s%s.rawml", dirname, basename);
}
printf("Saving rawml to %s\n", newdir);
errno = 0;
FILE *file = fopen(newdir, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", newdir, strerror(errsv));
return ERROR;
}
const MOBI_RET mobi_ret = mobi_dump_rawml(m, file);
fclose(file);
if (mobi_ret != MOBI_SUCCESS) {
printf("Dumping rawml file failed (%s)\n", libmobi_msg(mobi_ret));
return ERROR;
}
return SUCCESS;
}
int dump_cover(const MOBIData *m, const char *fullpath) {
MOBIPdbRecord *record = NULL;
if (exth) {
uint32_t offset = mobi_decode_exthvalue(exth->data, exth->size);
size_t first_resource = mobi_get_first_resource_record(m);
size_t uid = first_resource + offset;
record = mobi_get_record_by_seqnumber(m, uid);
}
if (record == NULL || record->size < 4) {
printf("Cover not found\n");
return ERROR;
}
const unsigned char jpg_magic[] = "\xff\xd8\xff";
const unsigned char gif_magic[] = "\x47\x49\x46\x38";
const unsigned char png_magic[] = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
const unsigned char bmp_magic[] = "\x42\x4d";
char ext[4] = "raw";
if (memcmp(record->data, jpg_magic, 3) == 0) {
snprintf(ext, sizeof(ext), "%s", "jpg");
} else if (memcmp(record->data, gif_magic, 4) == 0) {
snprintf(ext, sizeof(ext), "%s", "gif");
} else if (record->size >= 8 && memcmp(record->data, png_magic, 8) == 0) {
snprintf(ext, sizeof(ext), "%s", "png");
} else if (record->size >= 6 && memcmp(record->data, bmp_magic, 2) == 0) {
const size_t bmp_size = (uint32_t) record->data[2] | ((uint32_t) record->data[3] << 8) |
((uint32_t) record->data[4] << 16) | ((uint32_t) record->data[5] << 24);
if (record->size == bmp_size) {
snprintf(ext, sizeof(ext), "%s", "bmp");
}
}
char dirname[FILENAME_MAX];
char basename[FILENAME_MAX];
split_fullpath(fullpath, dirname, basename);
char cover_path[FILENAME_MAX];
if (outdir_opt) {
snprintf(cover_path, sizeof(cover_path), "%s%s_cover.%s", outdir, basename, ext);
} else {
snprintf(cover_path, sizeof(cover_path), "%s%s_cover.%s", dirname, basename, ext);
}
printf("Saving cover to %s\n", cover_path);
errno = 0;
FILE *file = fopen(cover_path, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", cover_path, strerror(errsv));
return ERROR;
}
size_t count = fwrite(record->data, 1, record->size, file);
if (count != record->size) {
int errsv = errno;
printf("Error writing to file: %s (%s)\n", cover_path, strerror(errsv));
fclose(file);
return ERROR;
}
fclose(file);
return SUCCESS;
}
int dump_rawml_parts(const MOBIRawml *rawml, const char *fullpath) {
if (rawml == NULL) {
printf("Rawml structure not initialized\n");
return ERROR;
}
char dirname[FILENAME_MAX];
char basename[FILENAME_MAX];
split_fullpath(fullpath, dirname, basename);
char newdir[FILENAME_MAX];
if (outdir_opt) {
snprintf(newdir, sizeof(newdir), "%s%s_markup", outdir, basename);
} else {
snprintf(newdir, sizeof(newdir), "%s%s_markup", dirname, basename);
}
printf("Saving markup to %s\n", newdir);
errno = 0;
if (mt_mkdir(newdir) != 0 && errno != EEXIST) {
int errsv = errno;
printf("Creating directory failed (%s)\n", strerror(errsv));
return ERROR;
}
if (create_epub_opt) {
/* create META_INF directory */
char opfdir[FILENAME_MAX];
snprintf(opfdir, sizeof(newdir), "%s%c%s", newdir, separator, "META-INF");
errno = 0;
if (mt_mkdir(opfdir) != 0 && errno != EEXIST) {
int errsv = errno;
printf("Creating META-INF directory failed (%s)\n", strerror(errsv));
return ERROR;
}
/* create container.xml */
char container[FILENAME_MAX];
snprintf(container, sizeof(container), "%s%c%s", opfdir, separator, "container.xml");
FILE *file = fopen(container, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", container, strerror(errsv));
return ERROR;
}
errno = 0;
fwrite(EPUB_CONTAINER, 1, sizeof(EPUB_CONTAINER) - 1, file);
if (ferror(file)) {
int errsv = errno;
printf("Error writing: %s (%s)\n", container, strerror(errsv));
fclose(file);
return ERROR;
}
fclose(file);
/* create mimetype file */
snprintf(container, sizeof(container), "%s%c%s", newdir, separator, "mimetype");
file = fopen(container, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", container, strerror(errsv));
return ERROR;
}
errno = 0;
fwrite(EPUB_MIMETYPE, 1, sizeof(EPUB_MIMETYPE) - 1, file);
if (ferror(file)) {
int errsv = errno;
printf("Error writing: %s (%s)\n", container, strerror(errsv));
fclose(file);
return ERROR;
}
fclose(file);
/* create OEBPS directory */
snprintf(opfdir, sizeof(opfdir), "%s%c%s", newdir, separator, "OEBPS");
errno = 0;
if (mt_mkdir(opfdir) != 0 && errno != EEXIST) {
int errsv = errno;
printf("Creating OEBPS directory failed (%s)\n", strerror(errsv));
return ERROR;
}
/* output everything else to OEBPS dir */
strcpy(newdir, opfdir);
}
char partname[FILENAME_MAX];
if (rawml->markup != NULL) {
/* Linked list of MOBIPart structures in rawml->markup holds main text files */
MOBIPart *curr = rawml->markup;
while (curr != NULL) {
snprintf(partname, sizeof(partname), "%s%cpart%05zu.%s", newdir, separator, curr->uid, file_meta.extension);
errno = 0;
FILE *file = fopen(partname, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", partname, strerror(errsv));
return ERROR;
}
printf("part%05zu.%s\n", curr->uid, file_meta.extension);
errno = 0;
fwrite(curr->data, 1, curr->size, file);
if (ferror(file)) {
int errsv = errno;
printf("Error writing: %s (%s)\n", partname, strerror(errsv));
fclose(file);
return ERROR;
}
fclose(file);
curr = curr->next;
}
}
if (rawml->flow != NULL) {
/* Linked list of MOBIPart structures in rawml->flow holds supplementary text files */
MOBIPart *curr = rawml->flow;
/* skip raw html file */
curr = curr->next;
while (curr != NULL) {
snprintf(partname, sizeof(partname), "%s%cflow%05zu.%s", newdir, separator, curr->uid, file_meta.extension);
errno = 0;
FILE *file = fopen(partname, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", partname, strerror(errsv));
return ERROR;
}
printf("flow%05zu.%s\n", curr->uid, file_meta.extension);
errno = 0;
fwrite(curr->data, 1, curr->size, file);
if (ferror(file)) {
int errsv = errno;
printf("Error writing: %s (%s)\n", partname, strerror(errsv));
fclose(file);
return ERROR;
}
fclose(file);
curr = curr->next;
}
}
if (rawml->resources != NULL) {
/* Linked list of MOBIPart structures in rawml->resources holds binary files, also opf files */
MOBIPart *curr = rawml->resources;
/* jpg, gif, png, bmp, font, audio, video also opf, ncx */
while (curr != NULL) {
if (curr->size > 0) {
if (create_epub_opt && file_meta.type == T_OPF) {
snprintf(partname, sizeof(partname), "%s%ccontent.opf", newdir, separator);
} else {
snprintf(partname, sizeof(partname), "%s%cresource%05zu.%s", newdir, separator, curr->uid, file_meta.extension);
}
errno = 0;
FILE *file = fopen(partname, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", partname, strerror(errsv));
return ERROR;
}
if (create_epub_opt && file_meta.type == T_OPF) {
printf("content.opf\n");
} else {
printf("resource%05zu.%s\n", curr->uid, file_meta.extension);
}
errno = 0;
fwrite(curr->data, 1, curr->size, file);
if (ferror(file)) {
int errsv = errno;
printf("Error writing: %s (%s)\n", partname, strerror(errsv));
fclose(file);
return ERROR;
}
fclose(file);
}
curr = curr->next;
}
}
return SUCCESS;
}
#ifdef USE_XMLWRITER
int create_epub(const MOBIRawml *rawml, const char *fullpath) {
if (rawml == NULL) {
printf("Rawml structure not initialized\n");
return ERROR;
}
char dirname[FILENAME_MAX];
char basename[FILENAME_MAX];
split_fullpath(fullpath, dirname, basename);
char zipfile[FILENAME_MAX];
if (outdir_opt) {
snprintf(zipfile, sizeof(zipfile), "%s%s.epub", outdir, basename);
} else {
snprintf(zipfile, sizeof(zipfile), "%s%s.epub", dirname, basename);
}
printf("Saving EPUB to %s\n", zipfile);
/* create zip (epub) archive */
mz_zip_archive zip;
memset(&zip, 0, sizeof(mz_zip_archive));
mz_bool mz_ret = mz_zip_writer_init_file(&zip, zipfile, 0);
if (!mz_ret) {
printf("Could not initialize zip archive\n");
return ERROR;
}
/* start adding files to archive */
mz_ret = mz_zip_writer_add_mem(&zip, "mimetype", EPUB_MIMETYPE, sizeof(EPUB_MIMETYPE) - 1, MZ_NO_COMPRESSION);
if (!mz_ret) {
printf("Could not add mimetype\n");
mz_zip_writer_end(&zip);
return ERROR;
}
mz_ret = mz_zip_writer_add_mem(&zip, "META-INF/container.xml", EPUB_CONTAINER, sizeof(EPUB_CONTAINER) - 1, (mz_uint)MZ_DEFAULT_COMPRESSION);
if (!mz_ret) {
printf("Could not add container.xml\n");
mz_zip_writer_end(&zip);
return ERROR;
}
char partname[FILENAME_MAX];
if (rawml->markup != NULL) {
/* Linked list of MOBIPart structures in rawml->markup holds main text files */
MOBIPart *curr = rawml->markup;
while (curr != NULL) {
snprintf(partname, sizeof(partname), "OEBPS/part%05zu.%s", curr->uid, file_meta.extension);
mz_ret = mz_zip_writer_add_mem(&zip, partname, curr->data, curr->size, (mz_uint) MZ_DEFAULT_COMPRESSION);
if (!mz_ret) {
printf("Could not add file to archive: %s\n", partname);
mz_zip_writer_end(&zip);
return ERROR;
}
curr = curr->next;
}
}
if (rawml->flow != NULL) {
/* Linked list of MOBIPart structures in rawml->flow holds supplementary text files */
MOBIPart *curr = rawml->flow;
/* skip raw html file */
curr = curr->next;
while (curr != NULL) {
snprintf(partname, sizeof(partname), "OEBPS/flow%05zu.%s", curr->uid, file_meta.extension);
mz_ret = mz_zip_writer_add_mem(&zip, partname, curr->data, curr->size, (mz_uint) MZ_DEFAULT_COMPRESSION);
if (!mz_ret) {
printf("Could not add file to archive: %s\n", partname);
mz_zip_writer_end(&zip);
return ERROR;
}
curr = curr->next;
}
}
if (rawml->resources != NULL) {
/* Linked list of MOBIPart structures in rawml->resources holds binary files, also opf files */
MOBIPart *curr = rawml->resources;
/* jpg, gif, png, bmp, font, audio, video, also opf, ncx */
while (curr != NULL) {
if (curr->size > 0) {
if (file_meta.type == T_OPF) {
snprintf(partname, sizeof(partname), "OEBPS/content.opf");
} else {
snprintf(partname, sizeof(partname), "OEBPS/resource%05zu.%s", curr->uid, file_meta.extension);
}
mz_ret = mz_zip_writer_add_mem(&zip, partname, curr->data, curr->size, (mz_uint) MZ_DEFAULT_COMPRESSION);
if (!mz_ret) {
printf("Could not add file to archive: %s\n", partname);
mz_zip_writer_end(&zip);
return ERROR;
}
}
curr = curr->next;
}
}
/* Finalize epub archive */
mz_ret = mz_zip_writer_finalize_archive(&zip);
if (!mz_ret) {
printf("Could not finalize zip archive\n");
mz_zip_writer_end(&zip);
return ERROR;
}
mz_ret = mz_zip_writer_end(&zip);
if (!mz_ret) {
printf("Could not finalize zip writer\n");
return ERROR;
}
return SUCCESS;
}
#endif
int dump_embedded_source(const MOBIData *m, const char *fullpath) {
/* Try to get embedded source */
unsigned char *data = NULL;
size_t size = 0;
MOBI_RET mobi_ret = mobi_get_embedded_source(&data, &size, m);
if (mobi_ret != MOBI_SUCCESS) {
printf("Extracting source from mobi failed (%s)\n", libmobi_msg(mobi_ret));
return ERROR;
}
if (data == NULL || size == 0 ) {
printf("Source archive not found\n");
return SUCCESS;
}
char dirname[FILENAME_MAX];
char basename[FILENAME_MAX];
split_fullpath(fullpath, dirname, basename);
char newdir[FILENAME_MAX];
if (outdir_opt) {
snprintf(newdir, sizeof(newdir), "%s%s_source", outdir, basename);
} else {
snprintf(newdir, sizeof(newdir), "%s%s_source", dirname, basename);
}
errno = 0;
if (mt_mkdir(newdir) != 0 && errno != EEXIST) {
int errsv = errno;
printf("Creating directory failed (%s)\n", strerror(errsv));
return ERROR;
}
char srcsname[FILENAME_MAX];
const unsigned char epub_magic[] = "mimetypeapplication/epub+zip";
const size_t em_offset = 30;
const size_t em_size = sizeof(epub_magic) - 1;
const char *ext;
if (size > em_offset + em_size && memcmp(data + em_offset, epub_magic, em_size) == 0) {
ext = "epub";
} else {
ext = "zip";
}
snprintf(srcsname, sizeof(srcsname), "%s%c%s_source.%s", newdir, separator, basename, ext);
printf("Saving source archive to %s\n", srcsname);
errno = 0;
FILE *file = fopen(srcsname, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", srcsname, strerror(errsv));
return ERROR;
}
errno = 0;
fwrite(data, 1, size, file);
if (ferror(file)) {
int errsv = errno;
printf("Error writing: %s (%s)\n", srcsname, strerror(errsv));
fclose(file);
return ERROR;
}
fclose(file);
/* Try to get embedded conversion log */
data = NULL;
size = 0;
mobi_ret = mobi_get_embedded_log(&data, &size, m);
if (mobi_ret != MOBI_SUCCESS) {
printf("Extracting conversion log from mobi failed (%s)\n", libmobi_msg(mobi_ret));
return ERROR;
}
if (data == NULL || size == 0 ) {
printf("Conversion log not found\n");
return SUCCESS;
}
snprintf(srcsname, sizeof(srcsname), "%s%c%s_source.txt", newdir, separator, basename);
printf("Saving conversion log to %s\n", srcsname);
errno = 0;
file = fopen(srcsname, "wb");
if (file == NULL) {
int errsv = errno;
printf("Could not open file for writing: %s (%s)\n", srcsname, strerror(errsv));
return ERROR;
}
errno = 0;
fwrite(data, 1, size, file);
if (ferror(file)) {
int errsv = errno;
printf("Error writing: %s (%s)\n", srcsname, strerror(errsv));
fclose(file);
return ERROR;
}
fclose(file);
return SUCCESS;
}
int loadfilename(const char *fullpath) {
MOBI_RET mobi_ret;
int ret = SUCCESS;
/* Initialize main MOBIData structure */
if (m == NULL) {
printf("Memory allocation failed\n");
return ERROR;
}
/* By default loader will parse KF8 part of hybrid KF7/KF8 file */
if (parse_kf7_opt) {
/* Force it to parse KF7 part */
}
errno = 0;
FILE *file = fopen(fullpath, "rb");
if (file == NULL) {
int errsv = errno;
printf("Error opening file: %s (%s)\n", fullpath, strerror(errsv));
return ERROR;
}
/* MOBIData structure will be filled with loaded document data and metadata */
mobi_ret = mobi_load_file(m, file);
fclose(file);
if (create_epub_opt && mobi_is_replica(m)) {
create_epub_opt = 0;
printf("\nWarning: Can't create EPUB format from Print Replica book (ignoring -e argument)\n\n");
}
/* Try to print basic metadata, even if further loading failed */
/* In case of some unsupported formats it may still print some useful info */
if (print_extended_meta_opt) { print_meta(m); }
if (mobi_ret != MOBI_SUCCESS) {
printf("Error while loading document (%s)\n", libmobi_msg(mobi_ret));
return ERROR;
}
if (!print_extended_meta_opt) {
}
if (print_extended_meta_opt) {
/* Try to print EXTH metadata */
}
#ifdef USE_ENCRYPTION
if (setpid_opt || setserial_opt) {
ret = set_decryption_key(m, serial, pid);
if (ret != SUCCESS) {
return ret;
};
}
#endif
if (print_rec_meta_opt) {
printf("\nPrinting records metadata...\n");
}
if (dump_rec_opt) {
printf("\nDumping raw records...\n");
ret = dump_records(m, fullpath);
}
if (dump_rawml_opt) {
printf("\nDumping rawml...\n");
ret = dump_rawml(m, fullpath);
} else if (dump_parts_opt || create_epub_opt) {
printf("\nReconstructing source resources...\n");
/* Initialize MOBIRawml structure */
/* This structure will be filled with parsed records data */
if (rawml == NULL) {
printf("Memory allocation failed\n");
return ERROR;
}
/* Parse rawml text and other data held in MOBIData structure into MOBIRawml structure */
mobi_ret = mobi_parse_rawml(rawml, m);
if (mobi_ret != MOBI_SUCCESS) {
printf("Parsing rawml failed (%s)\n", libmobi_msg(mobi_ret));
return ERROR;
}
if (create_epub_opt && !dump_parts_opt) {
#ifdef USE_XMLWRITER
printf("\nCreating EPUB...\n");
/* Create epub file */
ret = create_epub(rawml, fullpath);
if (ret != SUCCESS) {
printf("Creating EPUB failed\n");
}
#endif
} else {
printf("\nDumping resources...\n");
/* Save parts to files */
ret = dump_rawml_parts(rawml, fullpath);
if (ret != SUCCESS) {
printf("Dumping parts failed\n");
}
}
/* Free MOBIRawml structure */
}
if (extract_source_opt) {
ret = dump_embedded_source(m, fullpath);
}
if (dump_cover_opt) {
ret = dump_cover(m, fullpath);
}
/* Free MOBIData structure */
return ret;
}
void exit_with_usage(const char *progname) {
printf("usage: %s [-cd" PRINT_EPUB_ARG "imrs" PRINT_RUSAGE_ARG "vx7] [-o dir]" PRINT_ENC_USG " filename\n", progname);
printf(" without arguments prints document metadata and exits\n");
printf(" -c dump cover\n");
printf(" -d dump rawml text record\n");
#ifdef USE_XMLWRITER
printf(" -e create EPUB file (with -s will dump EPUB source)\n");
#endif
printf(" -i print detailed metadata\n");
printf(" -m print records metadata\n");
printf(" -o dir save output to dir folder\n");
#ifdef USE_ENCRYPTION
printf(" -p pid set pid for decryption\n");
printf(" -P serial set device serial for decryption\n");
#endif
printf(" -r dump raw records\n");
printf(" -s dump recreated source files\n");
#ifdef HAVE_SYS_RESOURCE_H
printf(" -u show rusage\n");
#endif
printf(" -v show version and exit\n");
printf(" -x extract conversion source and log (if present)\n");
printf(" -7 parse KF7 part of hybrid file (by default KF8 part is parsed)\n");
exit(SUCCESS);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
exit_with_usage(argv[0]);
}
opterr = 0;
int c;
while((c = getopt(argc, argv, "cd" PRINT_EPUB_ARG "imo:" PRINT_ENC_ARG "rs" PRINT_RUSAGE_ARG "vx7")) != -1)
switch(c) {
case 'c':
dump_cover_opt = 1;
break;
case 'd':
dump_rawml_opt = 1;
break;
#ifdef USE_XMLWRITER
case 'e':
create_epub_opt = 1;
break;
#endif
case 'i':
print_extended_meta_opt = 1;
break;
case 'm':
print_rec_meta_opt = 1;
break;
case 'o':
outdir_opt = 1;
size_t outdir_length = strlen(optarg);
if (outdir_length >= FILENAME_MAX - 1) {
printf("Output directory name too long\n");
return ERROR;
}
strncpy(outdir, optarg, FILENAME_MAX - 1);
if (!dir_exists(outdir)) {
printf("Output directory is not valid\n");
return ERROR;
}
if (optarg[outdir_length - 1] != separator) {
// append separator
if (outdir_length >= FILENAME_MAX - 2) {
printf("Output directory name too long\n");
return ERROR;
}
outdir[outdir_length++] = separator;
outdir[outdir_length] = '\0';
}
break;
#ifdef USE_ENCRYPTION
case 'p':
setpid_opt = 1;
pid = optarg;
break;
case 'P':
setserial_opt = 1;
serial = optarg;
break;
#endif
case 'r':
dump_rec_opt = 1;
break;
case 's':
dump_parts_opt = 1;
break;
#ifdef HAVE_SYS_RESOURCE_H
case 'u':
print_rusage_opt = 1;
break;
#endif
case 'v':
printf("mobitool build: " __DATE__ " " __TIME__ " (" COMPILER ")\n");
printf("libmobi: %s\n", mobi_version());
return 0;
case 'x':
extract_source_opt = 1;
break;
case '7':
parse_kf7_opt = 1;
break;
case '?':
#ifdef USE_ENCRYPTION
if (optopt == 'p') {
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
}
else
#endif
if (isprint(optopt)) {
fprintf(stderr, "Unknown option `-%c'\n", optopt);
}
else {
fprintf(stderr, "Unknown option character `\\x%x'\n", optopt);
}
exit_with_usage(argv[0]);
default:
exit_with_usage(argv[0]);
}
if (argc <= optind) {
printf("Missing filename\n");
exit_with_usage(argv[0]);
}
int ret = 0;
char filename[FILENAME_MAX];
strncpy(filename, argv[optind], FILENAME_MAX - 1);
filename[FILENAME_MAX - 1] = '\0';
ret = loadfilename(filename);
#ifdef HAVE_SYS_RESOURCE_H
if (print_rusage_opt) {
/* rusage */
struct rusage ru;
struct timeval utime;
struct timeval stime;
getrusage(RUSAGE_SELF, &ru);
utime = ru.ru_utime;
stime = ru.ru_stime;
printf("RUSAGE: ru_utime => %lld.%lld sec.; ru_stime => %lld.%lld sec.\n",
(long long) utime.tv_sec, (long long) utime.tv_usec,
(long long) stime.tv_sec, (long long) stime.tv_usec);
}
#endif
return ret;
}