From b661a6cdc540fee2ad31571692f31ee5abbea20e Mon Sep 17 00:00:00 2001 From: Apprentice Alf Date: Tue, 20 Nov 2012 13:28:12 +0000 Subject: [PATCH] tools v5.4.1 --- Calibre_Plugins/Ignobleepub ReadMe.txt | 6 +- Calibre_Plugins/Ineptpdf ReadMe.txt | 6 +- Calibre_Plugins/K4MobiDeDRM ReadMe.txt | 6 +- .../K4MobiDeDRM_plugin/__init__.py | 47 +- Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py | 568 +++++++ .../K4MobiDeDRM_plugin/alfcrypto.dll | Bin 171 -> 70144 bytes .../K4MobiDeDRM_plugin/alfcrypto.py | 830 ++++------ .../K4MobiDeDRM_plugin/alfcrypto64.dll | Bin 70144 -> 52224 bytes .../K4MobiDeDRM_plugin/alfcrypto_src.zip | Bin 9377 -> 17393 bytes .../K4MobiDeDRM_plugin/cmbtc_v2.2.py | Bin 52224 -> 0 bytes Calibre_Plugins/K4MobiDeDRM_plugin/config.py | Bin 17393 -> 2018 bytes .../K4MobiDeDRM_plugin/convert2xml.py | 873 +++++++++- .../K4MobiDeDRM_plugin/flatxml2html.py | Bin 171 -> 29407 bytes .../K4MobiDeDRM_plugin/flatxml2svg.py | 1037 +++--------- Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py | 1448 ++++++++--------- .../K4MobiDeDRM_plugin/getk4pcpids.py | 309 +--- .../K4MobiDeDRM_plugin/k4mobidedrm_orig.py | 721 -------- .../K4MobiDeDRM_plugin/kgenpids.py | Bin 171 -> 8091 bytes .../K4MobiDeDRM_plugin/libalfcrypto.dylib | Bin 16182 -> 87160 bytes .../K4MobiDeDRM_plugin/libalfcrypto32.so | Bin 171 -> 23859 bytes .../K4MobiDeDRM_plugin/libalfcrypto64.so | Bin 8136 -> 33417 bytes .../K4MobiDeDRM_plugin/outputfix.py | 45 + Calibre_Plugins/K4MobiDeDRM_plugin/pbkdf2.py | Bin 23859 -> 2002 bytes .../plugin-import-name-k4mobidedrm.txt | Bin 33417 -> 0 bytes .../K4MobiDeDRM_plugin/scrolltextwidget.py | 481 +----- .../K4MobiDeDRM_plugin/stylexml2css.py | 303 +++- .../K4MobiDeDRM_plugin/subasyncio.py | Bin 171 -> 4968 bytes .../K4MobiDeDRM_plugin/topazextract.py | 522 +++++- Calibre_Plugins/ignobleepub_plugin.zip | Bin 34990 -> 32027 bytes .../Ignoble Epub DeDRM_Help.htm | 12 +- .../ignobleepub_plugin/__init__.py | 19 +- Calibre_Plugins/ignobleepub_plugin/config.py | 60 +- Calibre_Plugins/ignobleepub_plugin/dialogs.py | 160 ++ .../ignobleepub_plugin/utilities.py | 180 +- Calibre_Plugins/ineptpdf_plugin.zip | Bin 25701 -> 26199 bytes Calibre_Plugins/ineptpdf_plugin/__init__.py | 39 +- Calibre_Plugins/ineptpdf_plugin/ade_key.py | 0 Calibre_Plugins/ineptpdf_plugin/ineptkey.py | 457 ++++++ Calibre_Plugins/ineptpdf_plugin/outputfix.py | 45 + .../plugin-import-name-ineptpdf.txt | Bin 171 -> 0 bytes Calibre_Plugins/k4mobidedrm_plugin.zip | Bin 217328 -> 218296 bytes .../k4mobidedrm_plugin/k4mutils.py | 777 ++++++++- .../k4mobidedrm_plugin/k4pcutils.py | 738 +++------ .../k4mobidedrm_plugin/mobidedrm.py | Bin 87160 -> 19070 bytes DeDRM_Macintosh_Application/DeDRM ReadMe.rtf | 3 +- .../DeDRM.app/Contents/Info.plist | 6 +- .../Contents/Resources/Scripts/main.scpt | Bin 261494 -> 261580 bytes .../Contents/Resources/getk4pcpids.py | 11 +- .../Contents/Resources/k4mobidedrm.py | 23 +- .../DeDRM.app/Contents/Resources/k4mutils.py | 77 +- .../DeDRM.app/Contents/Resources/k4pcutils.py | 15 +- .../DeDRM.app/Contents/Resources/kgenpids.py | 6 +- .../DeDRM.app/Contents/Resources/mobidedrm.py | 4 +- .../Contents/Resources/topazextract.py | 12 +- .../DeDRM_App/DeDRM_lib/DeDRM_app.pyw | 2 +- .../DeDRM_App/DeDRM_lib/lib/getk4pcpids.py | 11 +- .../DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py | 23 +- .../DeDRM_App/DeDRM_lib/lib/k4mutils.py | 77 +- .../DeDRM_App/DeDRM_lib/lib/k4pcutils.py | 15 +- .../DeDRM_App/DeDRM_lib/lib/kgenpids.py | 6 +- .../DeDRM_App/DeDRM_lib/lib/mobidedrm.py | 4 +- .../DeDRM_App/DeDRM_lib/lib/topazextract.py | 12 +- DeDRM_Windows_Application/DeDRM_ReadMe.txt | 10 +- .../B&N_Download_Helper/BN-Dload.user.js | 26 + .../BN-Dload.user_ReadMe.txt | 22 + Other_Tools/KindleBooks/lib/getk4pcpids.py | 11 +- Other_Tools/KindleBooks/lib/k4mobidedrm.py | 23 +- Other_Tools/KindleBooks/lib/k4mutils.py | 77 +- Other_Tools/KindleBooks/lib/k4pcutils.py | 15 +- Other_Tools/KindleBooks/lib/kgenpids.py | 6 +- Other_Tools/KindleBooks/lib/mobidedrm.py | 4 +- Other_Tools/KindleBooks/lib/topazextract.py | 12 +- .../ReadMe_K4Android.txt | 0 .../kindle3.0.1.70.patch | 28 +- .../ReadMe_K4Android.txt | 8 + .../kindle3.7.0.108.patch | 62 +- ReadMe_First.txt | 38 +- Scuolabook_DRM/Scuolabook+DRM+Remover+1.0.zip | Bin 0 -> 364855 bytes ...uolabook_DRM_Remover_source_19_11_2012.zip | Bin 0 -> 399257 bytes Scuolabook_DRM/Scuolabook_ReadMe.txt | 10 + 80 files changed, 5564 insertions(+), 4784 deletions(-) delete mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/cmbtc_v2.2.py delete mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py create mode 100644 Calibre_Plugins/K4MobiDeDRM_plugin/outputfix.py create mode 100644 Calibre_Plugins/ignobleepub_plugin/dialogs.py delete mode 100644 Calibre_Plugins/ineptpdf_plugin/ade_key.py create mode 100644 Calibre_Plugins/ineptpdf_plugin/ineptkey.py create mode 100644 Calibre_Plugins/ineptpdf_plugin/outputfix.py create mode 100644 Other_Tools/B&N_Download_Helper/BN-Dload.user.js create mode 100644 Other_Tools/B&N_Download_Helper/BN-Dload.user_ReadMe.txt rename {Kindle_for_Android_Patches => Other_Tools/Kindle_for_Android_Patches}/kindle version 3.0.1.70/ReadMe_K4Android.txt (100%) rename {Kindle_for_Android_Patches => Other_Tools/Kindle_for_Android_Patches}/kindle version 3.0.1.70/kindle3.0.1.70.patch (99%) rename {Kindle_for_Android_Patches => Other_Tools/Kindle_for_Android_Patches}/kindle version 3.7.0.108/ReadMe_K4Android.txt (89%) rename {Kindle_for_Android_Patches => Other_Tools/Kindle_for_Android_Patches}/kindle version 3.7.0.108/kindle3.7.0.108.patch (97%) create mode 100644 Scuolabook_DRM/Scuolabook+DRM+Remover+1.0.zip create mode 100644 Scuolabook_DRM/Scuolabook_DRM_Remover_source_19_11_2012.zip create mode 100644 Scuolabook_DRM/Scuolabook_ReadMe.txt diff --git a/Calibre_Plugins/Ignobleepub ReadMe.txt b/Calibre_Plugins/Ignobleepub ReadMe.txt index 262ef40..3eb916b 100644 --- a/Calibre_Plugins/Ignobleepub ReadMe.txt +++ b/Calibre_Plugins/Ignobleepub ReadMe.txt @@ -1,4 +1,4 @@ -Ignoble Epub DeDRM - ignobleepub_v02.2_plugin.zip +Ignoble Epub DeDRM - ignobleepub_v02.4_plugin.zip All credit given to I♥Cabbages for the original standalone scripts. I had the much easier job of converting them to a calibre plugin. @@ -8,7 +8,7 @@ This plugin is meant to decrypt Barnes & Noble Epubs that are protected with Ado Installation: -Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_v02.2_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. +Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ignobleepub_v02.4_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. Configuration: @@ -64,4 +64,4 @@ Now copy the output from the terminal window. On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it. On Macintosh and Linux, just use the normal text select and copy commands. -Paste the information into a comment at my blog, describing your problem. \ No newline at end of file +Paste the information into a comment at my blog, describing your problem. diff --git a/Calibre_Plugins/Ineptpdf ReadMe.txt b/Calibre_Plugins/Ineptpdf ReadMe.txt index 9fcb58d..ab5a510 100644 --- a/Calibre_Plugins/Ineptpdf ReadMe.txt +++ b/Calibre_Plugins/Ineptpdf ReadMe.txt @@ -1,4 +1,4 @@ -Inept PDF Plugin - ineptpdf_v01.6_plugin.zip +Inept PDF Plugin - ineptpdf_v01.8_plugin.zip All credit given to I♥Cabbages for the original standalone scripts. I had the much easier job of converting them to a Calibre plugin. @@ -8,7 +8,7 @@ This plugin is meant to decrypt Adobe Digital Edition PDFs that are protected wi Installation: -Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptpdf_v01.6_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. +Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (ineptpdf_v01.8_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. Configuration: @@ -45,4 +45,4 @@ Now copy the output from the terminal window. On Windows, you must use the window menu (little icon at left of window bar) to select all the text and then to copy it. On Macintosh and Linux, just use the normal text select and copy commands. -Paste the information into a comment at my blog, describing your problem. \ No newline at end of file +Paste the information into a comment at my blog, describing your problem. diff --git a/Calibre_Plugins/K4MobiDeDRM ReadMe.txt b/Calibre_Plugins/K4MobiDeDRM ReadMe.txt index 022f8ab..d080d49 100644 --- a/Calibre_Plugins/K4MobiDeDRM ReadMe.txt +++ b/Calibre_Plugins/K4MobiDeDRM ReadMe.txt @@ -1,4 +1,4 @@ -K4MobiDeDRM_v04.6_plugin.zip +K4MobiDeDRM_v04.7_plugin.zip Credit given to The Dark Reverser for the original standalone script. Credit also to the many people who have updated and expanded that script since then. @@ -11,7 +11,7 @@ This plugin is meant to remove the DRM from .prc, .mobi, .azw, .azw1, .azw3, .az Installation: -Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (K4MobiDeDRM_v04.6_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. +Go to calibre's Preferences page. Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior". Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (K4MobiDeDRM_v04.7_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog. Make sure that you delete any old versions of the plugin. They might interfere with the operation of the new one. @@ -24,7 +24,7 @@ If you have an eInk Kindle enter the 16 character serial number (these all begin If you have Mobipocket books, enter your 8 or 10 digit PID in the Mobipocket PIDs field. If you have more than one PID, separate them with commas. -These configuration steps are not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books. +These configuration steps are not needed if you only want to decode "Kindle for PC" or "Kindle for Mac" books. Linux Systems Only: diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py b/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py index ba11adf..75f6d21 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/__init__.py @@ -13,6 +13,7 @@ from calibre.constants import iswindows, isosx import sys import os import re +import time from zipfile import ZipFile class K4DeDRM(FileTypePlugin): @@ -20,7 +21,7 @@ class K4DeDRM(FileTypePlugin): description = 'Removes DRM from eInk Kindle, Kindle 4 Mac and Kindle 4 PC ebooks, and from Mobipocket ebooks. Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, mdlnx, ApprenticeAlf, etc.' supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on author = 'DiapDealer, SomeUpdates, mdlnx, Apprentice Alf' # The author of this plugin - version = (0, 4, 6) # The version number of this plugin + version = (0, 4, 7) # The version number of this plugin file_types = set(['prc','mobi','azw','azw1','azw3','azw4','tpz']) # The file types that this plugin will be applied to on_import = True # Run this plugin during the import priority = 520 # run this plugin before earlier versions @@ -51,7 +52,7 @@ class K4DeDRM(FileTypePlugin): f.write(data) def run(self, path_to_ebook): - # add the alfcrypto directory to sys.path so alfcrypto.py + # add the alfcrypto directory to sys.path so alfcrypto.py # will be able to locate the custom lib(s) for CDLL import. sys.path.insert(0, self.alfdir) # Had to move these imports here so the custom libs can be @@ -73,8 +74,11 @@ class K4DeDRM(FileTypePlugin): pids = [] serials = [] kInfoFiles = [] + starttime = time.time() + print "K4MobiDeDRM plugin v{0:s}: Starting".format(plug_ver) + self.config() - + # Get supplied list of PIDs to try from plugin customization. pidstringlistt = self.pids_string.split(',') for pid in pidstringlistt: @@ -84,22 +88,22 @@ class K4DeDRM(FileTypePlugin): else: if len(pid) > 0: print "'%s' is not a valid Mobipocket PID." % pid - + # For linux, get PIDs by calling the right routines under WINE if sys.platform.startswith('linux'): k4 = False pids.extend(self.WINEgetPIDs(path_to_ebook)) - + # Get supplied list of Kindle serial numbers to try from plugin customization. serialstringlistt = self.serials_string.split(',') for serial in serialstringlistt: - serial = str(serial).strip() + serial = str(serial).replace(" ","") if len(serial) == 16 and serial[0] == 'B': serials.append(serial) else: if len(serial) > 0: print "'%s' is not a valid Kindle serial number." % serial - + # Load any kindle info files (*.info) included Calibre's config directory. try: print 'K4MobiDeDRM v%s: Calibre configuration directory = %s' % (plug_ver, config_dir) @@ -129,10 +133,11 @@ class K4DeDRM(FileTypePlugin): title = mb.getBookTitle() md1, md2 = mb.getPIDMetaInfo() - pidlst = kgenpids.getPidList(md1, md2, k4, pids, serials, kInfoFiles) + pids.extend(kgenpids.getPidList(md1, md2, k4, serials, kInfoFiles)) + print "K4MobiDeDRM plugin v{2:s}: Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids),plug_ver) try: - mb.processBook(pidlst) + mb.processBook(pids) except mobidedrm.DrmException, e: #if you reached here then no luck raise and exception @@ -142,7 +147,7 @@ class K4DeDRM(FileTypePlugin): d.show() d.raise_() d.exec_() - raise Exception("K4MobiDeDRM plugin v%s Error: %s" % (plug_ver, str(e))) + raise Exception("K4MobiDeDRM plugin v{1:s} Error: {2:s} after {0:.1f} seconds".format(time.time()-starttime,plug_ver,str(e))) except topazextract.TpzDRMError, e: #if you reached here then no luck raise and exception if is_ok_to_use_qt(): @@ -151,23 +156,25 @@ class K4DeDRM(FileTypePlugin): d.show() d.raise_() d.exec_() - raise Exception("K4MobiDeDRM plugin v%s Error: %s" % (plug_ver, str(e))) + raise Exception("K4MobiDeDRM plugin v{1:s} Error: {2:s} after {0:.1f} seconds".format(time.time()-starttime,plug_ver,str(e))) - print "Success!" + print "K4MobiDeDRM plugin v{1:s}: Successfully decrypted book after {0:.1f} seconds".format(time.time()-starttime,plug_ver) if mobi: if mb.getPrintReplica(): of = self.temporary_file(bookname+'.azw4') - print 'K4MobiDeDRM v%s: Print Replica format detected.' % plug_ver + print 'K4MobiDeDRM plugin v%s: Print Replica format detected.' % plug_ver elif mb.getMobiVersion() >= 8: - print 'K4MobiDeDRM v%s: Stand-alone KF8 format detected.' % plug_ver + print 'K4MobiDeDRM plugin v%s: Stand-alone KF8 format detected.' % plug_ver of = self.temporary_file(bookname+'.azw3') else: of = self.temporary_file(bookname+'.mobi') mb.getMobiFile(of.name) + print "K4MobiDeDRM plugin v{1:s}: Saved decrypted book after {0:.1f} seconds".format(time.time()-starttime,plug_ver) else: of = self.temporary_file(bookname+'.htmlz') mb.getHTMLZip(of.name) mb.cleanup() + print "K4MobiDeDRM plugin v{1:s}: Saved decrypted Topaz HTMLZ after {0:.1f} seconds".format(time.time()-starttime,plug_ver) return of.name def WINEgetPIDs(self, infile): @@ -191,7 +198,7 @@ class K4DeDRM(FileTypePlugin): + ' "' + outfile + '"' env = os.environ - + print "My wine_prefix from tweaks is ", self.wine_prefix if ("WINEPREFIX" in env): @@ -212,7 +219,7 @@ class K4DeDRM(FileTypePlugin): print "WINE subprocess error ", str(e) return [] print "WINE subprocess returned ", result - + WINEpids = [] if os.path.exists(outfile): try: @@ -242,14 +249,14 @@ class K4DeDRM(FileTypePlugin): # from the command line from calibre_plugins.k4mobidedrm.config import ConfigWidget return config.ConfigWidget() - + def config(self): from calibre_plugins.k4mobidedrm.config import prefs - + self.pids_string = prefs['pids'] self.serials_string = prefs['serials'] self.wine_prefix = prefs['WINEPREFIX'] - + def save_settings(self, config_widget): ''' Save the settings specified by the user with config_widget. @@ -263,4 +270,4 @@ class K4DeDRM(FileTypePlugin): for candidate in zf.namelist(): if candidate in names: ans[candidate] = zf.read(candidate) - return ans \ No newline at end of file + return ans diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py b/Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py index e69de29..5667511 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/aescbc.py @@ -0,0 +1,568 @@ +#! /usr/bin/env python + +""" + Routines for doing AES CBC in one file + + Modified by some_updates to extract + and combine only those parts needed for AES CBC + into one simple to add python file + + Original Version + Copyright (c) 2002 by Paul A. Lambert + Under: + CryptoPy Artisitic License Version 1.0 + See the wonderful pure python package cryptopy-1.2.5 + and read its LICENSE.txt for complete license details. +""" + +class CryptoError(Exception): + """ Base class for crypto exceptions """ + def __init__(self,errorMessage='Error!'): + self.message = errorMessage + def __str__(self): + return self.message + +class InitCryptoError(CryptoError): + """ Crypto errors during algorithm initialization """ +class BadKeySizeError(InitCryptoError): + """ Bad key size error """ +class EncryptError(CryptoError): + """ Error in encryption processing """ +class DecryptError(CryptoError): + """ Error in decryption processing """ +class DecryptNotBlockAlignedError(DecryptError): + """ Error in decryption processing """ + +def xorS(a,b): + """ XOR two strings """ + assert len(a)==len(b) + x = [] + for i in range(len(a)): + x.append( chr(ord(a[i])^ord(b[i]))) + return ''.join(x) + +def xor(a,b): + """ XOR two strings """ + x = [] + for i in range(min(len(a),len(b))): + x.append( chr(ord(a[i])^ord(b[i]))) + return ''.join(x) + +""" + Base 'BlockCipher' and Pad classes for cipher instances. + BlockCipher supports automatic padding and type conversion. The BlockCipher + class was written to make the actual algorithm code more readable and + not for performance. +""" + +class BlockCipher: + """ Block ciphers """ + def __init__(self): + self.reset() + + def reset(self): + self.resetEncrypt() + self.resetDecrypt() + def resetEncrypt(self): + self.encryptBlockCount = 0 + self.bytesToEncrypt = '' + def resetDecrypt(self): + self.decryptBlockCount = 0 + self.bytesToDecrypt = '' + + def encrypt(self, plainText, more = None): + """ Encrypt a string and return a binary string """ + self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt + numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize) + cipherText = '' + for i in range(numBlocks): + bStart = i*self.blockSize + ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize]) + self.encryptBlockCount += 1 + cipherText += ctBlock + if numExtraBytes > 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # no more data expected from caller + finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize) + if len(finalBytes) > 0: + ctBlock = self.encryptBlock(finalBytes) + self.encryptBlockCount += 1 + cipherText += ctBlock + self.resetEncrypt() + return cipherText + + def decrypt(self, cipherText, more = None): + """ Decrypt a string and return a string """ + self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt + + numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) + if more == None: # no more calls to decrypt, should have all the data + if numExtraBytes != 0: + raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' + + # hold back some bytes in case last decrypt has zero len + if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : + numBlocks -= 1 + numExtraBytes = self.blockSize + + plainText = '' + for i in range(numBlocks): + bStart = i*self.blockSize + ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize]) + self.decryptBlockCount += 1 + plainText += ptBlock + + if numExtraBytes > 0: # save any bytes that are not block aligned + self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + else: + self.bytesToEncrypt = '' + + if more == None: # last decrypt remove padding + plainText = self.padding.removePad(plainText, self.blockSize) + self.resetDecrypt() + return plainText + + +class Pad: + def __init__(self): + pass # eventually could put in calculation of min and max size extension + +class padWithPadLen(Pad): + """ Pad a binary string with the length of the padding """ + + def addPad(self, extraBytes, blockSize): + """ Add padding to a binary string to make it an even multiple + of the block size """ + blocks, numExtraBytes = divmod(len(extraBytes), blockSize) + padLength = blockSize - numExtraBytes + return extraBytes + padLength*chr(padLength) + + def removePad(self, paddedBinaryString, blockSize): + """ Remove padding from a binary string """ + if not(0 6 and i%Nk == 4 : + temp = [ Sbox[byte] for byte in temp ] # SubWord(temp) + w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] ) + return w + +Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!! + 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6, + 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91) + +#------------------------------------- +def AddRoundKey(algInstance, keyBlock): + """ XOR the algorithm state with a block of key material """ + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] ^= keyBlock[column][row] +#------------------------------------- + +def SubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = Sbox[algInstance.state[column][row]] + +def InvSubBytes(algInstance): + for column in range(algInstance.Nb): + for row in range(4): + algInstance.state[column][row] = InvSbox[algInstance.state[column][row]] + +Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, + 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, + 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, + 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, + 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, + 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, + 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, + 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, + 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, + 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, + 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, + 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, + 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, + 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, + 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, + 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, + 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, + 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, + 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, + 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, + 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, + 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, + 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, + 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, + 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, + 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, + 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, + 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, + 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, + 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, + 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, + 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) + +InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, + 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, + 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, + 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, + 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, + 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, + 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, + 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, + 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, + 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, + 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, + 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, + 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, + 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, + 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, + 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, + 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) + +#------------------------------------- +""" For each block size (Nb), the ShiftRow operation shifts row i + by the amount Ci. Note that row 0 is not shifted. + Nb C1 C2 C3 + ------------------- """ +shiftOffset = { 4 : ( 0, 1, 2, 3), + 5 : ( 0, 1, 2, 3), + 6 : ( 0, 1, 2, 3), + 7 : ( 0, 1, 2, 4), + 8 : ( 0, 1, 3, 4) } +def ShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] +def InvShiftRows(algInstance): + tmp = [0]*algInstance.Nb # list of size Nb + for r in range(1,4): # row 0 reamains unchanged and can be skipped + for c in range(algInstance.Nb): + tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] + for c in range(algInstance.Nb): + algInstance.state[c][r] = tmp[c] +#------------------------------------- +def MixColumns(a): + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3]) + Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3]) + Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + +def InvMixColumns(a): + """ Mix the four bytes of every column in a linear way + This is the opposite operation of Mixcolumn """ + Sprime = [0,0,0,0] + for j in range(a.Nb): # for each column + Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3]) + Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3]) + Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3]) + Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3]) + for i in range(4): + a.state[j][i] = Sprime[i] + +#------------------------------------- +def mul(a, b): + """ Multiply two elements of GF(2^m) + needed for MixColumn and InvMixColumn """ + if (a !=0 and b!=0): + return Alogtable[(Logtable[a] + Logtable[b])%255] + else: + return 0 + +Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, + 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, + 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, + 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, + 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, + 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, + 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, + 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, + 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, + 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, + 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, + 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, + 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, + 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, + 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, + 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7) + +Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, + 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, + 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, + 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, + 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, + 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, + 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, + 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, + 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, + 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1) + + + + +""" + AES Encryption Algorithm + The AES algorithm is just Rijndael algorithm restricted to the default + blockSize of 128 bits. +""" + +class AES(Rijndael): + """ The AES algorithm is the Rijndael block cipher restricted to block + sizes of 128 bits and key sizes of 128, 192 or 256 bits + """ + def __init__(self, key = None, padding = padWithPadLen(), keySize=16): + """ Initialize AES, keySize is in bytes """ + if not (keySize == 16 or keySize == 24 or keySize == 32) : + raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' + + Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) + + self.name = 'AES' + + +""" + CBC mode of encryption for block ciphers. + This algorithm mode wraps any BlockCipher to make a + Cipher Block Chaining mode. +""" +from random import Random # should change to crypto.random!!! + + +class CBC(BlockCipher): + """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode + algorithms. The initialization (IV) is automatic if set to None. Padding + is also automatic based on the Pad class used to initialize the algorithm + """ + def __init__(self, blockCipherInstance, padding = padWithPadLen()): + """ CBC algorithms are created by initializing with a BlockCipher instance """ + self.baseCipher = blockCipherInstance + self.name = self.baseCipher.name + '_CBC' + self.blockSize = self.baseCipher.blockSize + self.keySize = self.baseCipher.keySize + self.padding = padding + self.baseCipher.padding = noPadding() # baseCipher should NOT pad!! + self.r = Random() # for IV generation, currently uses + # mediocre standard distro version <---------------- + import time + newSeed = time.ctime()+str(self.r) # seed with instance location + self.r.seed(newSeed) # to make unique + self.reset() + + def setKey(self, key): + self.baseCipher.setKey(key) + + # Overload to reset both CBC state and the wrapped baseCipher + def resetEncrypt(self): + BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class) + self.baseCipher.resetEncrypt() # reset base cipher encrypt state + + def resetDecrypt(self): + BlockCipher.resetDecrypt(self) # reset CBC state (super class) + self.baseCipher.resetDecrypt() # reset base cipher decrypt state + + def encrypt(self, plainText, iv=None, more=None): + """ CBC encryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.encryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to encrypt' + + return BlockCipher.encrypt(self,plainText, more=more) + + def decrypt(self, cipherText, iv=None, more=None): + """ CBC decryption - overloads baseCipher to allow optional explicit IV + when iv=None, iv is auto generated! + """ + if self.decryptBlockCount == 0: + self.iv = iv + else: + assert(iv==None), 'IV used only on first call to decrypt' + + return BlockCipher.decrypt(self, cipherText, more=more) + + def encryptBlock(self, plainTextBlock): + """ CBC block encryption, IV is set with 'encrypt' """ + auto_IV = '' + if self.encryptBlockCount == 0: + if self.iv == None: + # generate IV and use + self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)]) + self.prior_encr_CT_block = self.iv + auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic + else: # application provided IV + assert(len(self.iv) == self.blockSize ),'IV must be same length as block' + self.prior_encr_CT_block = self.iv + """ encrypt the prior CT XORed with the PT """ + ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) ) + self.prior_encr_CT_block = ct + return auto_IV+ct + + def decryptBlock(self, encryptedBlock): + """ Decrypt a single block """ + + if self.decryptBlockCount == 0: # first call, process IV + if self.iv == None: # auto decrypt IV? + self.prior_CT_block = encryptedBlock + return '' + else: + assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption" + self.prior_CT_block = self.iv + + dct = self.baseCipher.decryptBlock(encryptedBlock) + """ XOR the prior decrypted CT with the prior CT """ + dct_XOR_priorCT = xor( self.prior_CT_block, dct ) + + self.prior_CT_block = encryptedBlock + + return dct_XOR_priorCT + + +""" + AES_CBC Encryption Algorithm +""" + +class AES_CBC(CBC): + """ AES encryption in CBC feedback mode """ + def __init__(self, key=None, padding=padWithPadLen(), keySize=16): + CBC.__init__( self, AES(key, noPadding(), keySize), padding) + self.name = 'AES_CBC' diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.dll b/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto.dll index 1b0ee71c84e93901faa8c1b41e325e32be46a06c..26d740ddb0ed3be82128b3fbe0e45471b0e04f4b 100644 GIT binary patch literal 70144 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 G!2bbGSZYrI literal 171 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aU~Fg_^W}KA;dKz1VrNojv*mItQOn; z09Al=%mHG4G}Vj 0: # save any bytes that are not block aligned - self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] + pointer_size = ctypes.sizeof(ctypes.c_voidp) + name_of_lib = None + if sys.platform.startswith('darwin'): + name_of_lib = 'libalfcrypto.dylib' + elif sys.platform.startswith('win'): + if pointer_size == 4: + name_of_lib = 'alfcrypto.dll' else: - self.bytesToEncrypt = '' - - if more == None: # no more data expected from caller - finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize) - if len(finalBytes) > 0: - ctBlock = self.encryptBlock(finalBytes) - self.encryptBlockCount += 1 - cipherText += ctBlock - self.resetEncrypt() - return cipherText - - def decrypt(self, cipherText, more = None): - """ Decrypt a string and return a string """ - self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt - - numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize) - if more == None: # no more calls to decrypt, should have all the data - if numExtraBytes != 0: - raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt' - - # hold back some bytes in case last decrypt has zero len - if (more != None) and (numExtraBytes == 0) and (numBlocks >0) : - numBlocks -= 1 - numExtraBytes = self.blockSize - - plainText = '' - for i in range(numBlocks): - bStart = i*self.blockSize - ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize]) - self.decryptBlockCount += 1 - plainText += ptBlock - - if numExtraBytes > 0: # save any bytes that are not block aligned - self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:] - else: - self.bytesToEncrypt = '' - - if more == None: # last decrypt remove padding - plainText = self.padding.removePad(plainText, self.blockSize) - self.resetDecrypt() - return plainText - - -class Pad: - def __init__(self): - pass # eventually could put in calculation of min and max size extension - -class padWithPadLen(Pad): - """ Pad a binary string with the length of the padding """ - - def addPad(self, extraBytes, blockSize): - """ Add padding to a binary string to make it an even multiple - of the block size """ - blocks, numExtraBytes = divmod(len(extraBytes), blockSize) - padLength = blockSize - numExtraBytes - return extraBytes + padLength*chr(padLength) - - def removePad(self, paddedBinaryString, blockSize): - """ Remove padding from a binary string """ - if not(0 6 and i%Nk == 4 : - temp = [ Sbox[byte] for byte in temp ] # SubWord(temp) - w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] ) - return w - -Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!! - 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6, - 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91) - -#------------------------------------- -def AddRoundKey(algInstance, keyBlock): - """ XOR the algorithm state with a block of key material """ - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] ^= keyBlock[column][row] -#------------------------------------- - -def SubBytes(algInstance): - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] = Sbox[algInstance.state[column][row]] - -def InvSubBytes(algInstance): - for column in range(algInstance.Nb): - for row in range(4): - algInstance.state[column][row] = InvSbox[algInstance.state[column][row]] - -Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5, - 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, - 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0, - 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, - 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc, - 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, - 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a, - 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, - 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0, - 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, - 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b, - 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, - 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85, - 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, - 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5, - 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, - 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17, - 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, - 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88, - 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, - 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c, - 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, - 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9, - 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, - 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6, - 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, - 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e, - 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, - 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94, - 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, - 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68, - 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16) - -InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, - 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, - 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, - 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, - 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, - 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, - 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, - 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, - 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, - 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, - 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, - 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, - 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, - 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, - 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, - 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, - 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, - 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, - 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, - 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, - 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, - 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, - 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, - 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, - 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, - 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, - 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, - 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, - 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, - 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, - 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, - 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d) - -#------------------------------------- -""" For each block size (Nb), the ShiftRow operation shifts row i - by the amount Ci. Note that row 0 is not shifted. - Nb C1 C2 C3 - ------------------- """ -shiftOffset = { 4 : ( 0, 1, 2, 3), - 5 : ( 0, 1, 2, 3), - 6 : ( 0, 1, 2, 3), - 7 : ( 0, 1, 2, 4), - 8 : ( 0, 1, 3, 4) } -def ShiftRows(algInstance): - tmp = [0]*algInstance.Nb # list of size Nb - for r in range(1,4): # row 0 reamains unchanged and can be skipped - for c in range(algInstance.Nb): - tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] - for c in range(algInstance.Nb): - algInstance.state[c][r] = tmp[c] -def InvShiftRows(algInstance): - tmp = [0]*algInstance.Nb # list of size Nb - for r in range(1,4): # row 0 reamains unchanged and can be skipped - for c in range(algInstance.Nb): - tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r] - for c in range(algInstance.Nb): - algInstance.state[c][r] = tmp[c] -#------------------------------------- -def MixColumns(a): - Sprime = [0,0,0,0] - for j in range(a.Nb): # for each column - Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3]) - Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3]) - Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3]) - Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3]) - for i in range(4): - a.state[j][i] = Sprime[i] - -def InvMixColumns(a): - """ Mix the four bytes of every column in a linear way - This is the opposite operation of Mixcolumn """ - Sprime = [0,0,0,0] - for j in range(a.Nb): # for each column - Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3]) - Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3]) - Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3]) - Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3]) - for i in range(4): - a.state[j][i] = Sprime[i] - -#------------------------------------- -def mul(a, b): - """ Multiply two elements of GF(2^m) - needed for MixColumn and InvMixColumn """ - if (a !=0 and b!=0): - return Alogtable[(Logtable[a] + Logtable[b])%255] + name_of_lib = 'alfcrypto64.dll' else: - return 0 - -Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, - 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, - 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, - 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, - 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, - 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, - 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, - 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, - 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, - 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, - 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, - 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, - 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, - 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, - 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, - 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7) - -Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, - 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, - 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, - 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, - 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, - 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, - 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, - 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, - 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, - 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, - 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, - 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, - 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, - 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, - 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, - 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1) - - - - -""" - AES Encryption Algorithm - The AES algorithm is just Rijndael algorithm restricted to the default - blockSize of 128 bits. -""" - -class AES(Rijndael): - """ The AES algorithm is the Rijndael block cipher restricted to block - sizes of 128 bits and key sizes of 128, 192 or 256 bits - """ - def __init__(self, key = None, padding = padWithPadLen(), keySize=16): - """ Initialize AES, keySize is in bytes """ - if not (keySize == 16 or keySize == 24 or keySize == 32) : - raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes' - - Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 ) - - self.name = 'AES' - - -""" - CBC mode of encryption for block ciphers. - This algorithm mode wraps any BlockCipher to make a - Cipher Block Chaining mode. -""" -from random import Random # should change to crypto.random!!! - - -class CBC(BlockCipher): - """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode - algorithms. The initialization (IV) is automatic if set to None. Padding - is also automatic based on the Pad class used to initialize the algorithm - """ - def __init__(self, blockCipherInstance, padding = padWithPadLen()): - """ CBC algorithms are created by initializing with a BlockCipher instance """ - self.baseCipher = blockCipherInstance - self.name = self.baseCipher.name + '_CBC' - self.blockSize = self.baseCipher.blockSize - self.keySize = self.baseCipher.keySize - self.padding = padding - self.baseCipher.padding = noPadding() # baseCipher should NOT pad!! - self.r = Random() # for IV generation, currently uses - # mediocre standard distro version <---------------- - import time - newSeed = time.ctime()+str(self.r) # seed with instance location - self.r.seed(newSeed) # to make unique - self.reset() - - def setKey(self, key): - self.baseCipher.setKey(key) - - # Overload to reset both CBC state and the wrapped baseCipher - def resetEncrypt(self): - BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class) - self.baseCipher.resetEncrypt() # reset base cipher encrypt state - - def resetDecrypt(self): - BlockCipher.resetDecrypt(self) # reset CBC state (super class) - self.baseCipher.resetDecrypt() # reset base cipher decrypt state - - def encrypt(self, plainText, iv=None, more=None): - """ CBC encryption - overloads baseCipher to allow optional explicit IV - when iv=None, iv is auto generated! - """ - if self.encryptBlockCount == 0: - self.iv = iv + if pointer_size == 4: + name_of_lib = 'libalfcrypto32.so' else: - assert(iv==None), 'IV used only on first call to encrypt' + name_of_lib = 'libalfcrypto64.so' + + libalfcrypto = sys.path[0] + os.sep + name_of_lib - return BlockCipher.encrypt(self,plainText, more=more) + if not os.path.isfile(libalfcrypto): + raise Exception('libalfcrypto not found') - def decrypt(self, cipherText, iv=None, more=None): - """ CBC decryption - overloads baseCipher to allow optional explicit IV - when iv=None, iv is auto generated! - """ - if self.decryptBlockCount == 0: - self.iv = iv - else: - assert(iv==None), 'IV used only on first call to decrypt' + libalfcrypto = CDLL(libalfcrypto) - return BlockCipher.decrypt(self, cipherText, more=more) - - def encryptBlock(self, plainTextBlock): - """ CBC block encryption, IV is set with 'encrypt' """ - auto_IV = '' - if self.encryptBlockCount == 0: - if self.iv == None: - # generate IV and use - self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)]) - self.prior_encr_CT_block = self.iv - auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic - else: # application provided IV - assert(len(self.iv) == self.blockSize ),'IV must be same length as block' - self.prior_encr_CT_block = self.iv - """ encrypt the prior CT XORed with the PT """ - ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) ) - self.prior_encr_CT_block = ct - return auto_IV+ct - - def decryptBlock(self, encryptedBlock): - """ Decrypt a single block """ - - if self.decryptBlockCount == 0: # first call, process IV - if self.iv == None: # auto decrypt IV? - self.prior_CT_block = encryptedBlock - return '' - else: - assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption" - self.prior_CT_block = self.iv - - dct = self.baseCipher.decryptBlock(encryptedBlock) - """ XOR the prior decrypted CT with the prior CT """ - dct_XOR_priorCT = xor( self.prior_CT_block, dct ) - - self.prior_CT_block = encryptedBlock - - return dct_XOR_priorCT + c_char_pp = POINTER(c_char_p) + c_int_p = POINTER(c_int) -""" - AES_CBC Encryption Algorithm -""" + def F(restype, name, argtypes): + func = getattr(libalfcrypto, name) + func.restype = restype + func.argtypes = argtypes + return func + + # aes cbc decryption + # + # struct aes_key_st { + # unsigned long rd_key[4 *(AES_MAXNR + 1)]; + # int rounds; + # }; + # + # typedef struct aes_key_st AES_KEY; + # + # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key); + # + # + # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + # const unsigned long length, const AES_KEY *key, + # unsigned char *ivec, const int enc); + + AES_MAXNR = 14 + + class AES_KEY(Structure): + _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)] + + AES_KEY_p = POINTER(AES_KEY) + AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, c_int]) + AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p]) + + + + # Pukall 1 Cipher + # unsigned char *PC1(const unsigned char *key, unsigned int klen, const unsigned char *src, + # unsigned char *dest, unsigned int len, int decryption); + + PC1 = F(c_char_p, 'PC1', [c_char_p, c_ulong, c_char_p, c_char_p, c_ulong, c_ulong]) + + # Topaz Encryption + # typedef struct _TpzCtx { + # unsigned int v[2]; + # } TpzCtx; + # + # void topazCryptoInit(TpzCtx *ctx, const unsigned char *key, int klen); + # void topazCryptoDecrypt(const TpzCtx *ctx, const unsigned char *in, unsigned char *out, int len); + + class TPZ_CTX(Structure): + _fields_ = [('v', c_long * 2)] + + TPZ_CTX_p = POINTER(TPZ_CTX) + topazCryptoInit = F(None, 'topazCryptoInit', [TPZ_CTX_p, c_char_p, c_ulong]) + topazCryptoDecrypt = F(None, 'topazCryptoDecrypt', [TPZ_CTX_p, c_char_p, c_char_p, c_ulong]) + + + class AES_CBC(object): + def __init__(self): + self._blocksize = 0 + self._keyctx = None + self._iv = 0 + + def set_decrypt_key(self, userkey, iv): + self._blocksize = len(userkey) + if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) : + raise Exception('AES CBC improper key used') + return + keyctx = self._keyctx = AES_KEY() + self._iv = iv + rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx) + if rv < 0: + raise Exception('Failed to initialize AES CBC key') + + def decrypt(self, data): + out = create_string_buffer(len(data)) + mutable_iv = create_string_buffer(self._iv, len(self._iv)) + rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, mutable_iv, 0) + if rv == 0: + raise Exception('AES CBC decryption failed') + return out.raw + + class Pukall_Cipher(object): + def __init__(self): + self.key = None + + def PC1(self, key, src, decryption=True): + self.key = key + out = create_string_buffer(len(src)) + de = 0 + if decryption: + de = 1 + rv = PC1(key, len(key), src, out, len(src), de) + return out.raw + + class Topaz_Cipher(object): + def __init__(self): + self._ctx = None + + def ctx_init(self, key): + tpz_ctx = self._ctx = TPZ_CTX() + topazCryptoInit(tpz_ctx, key, len(key)) + return tpz_ctx + + def decrypt(self, data, ctx=None): + if ctx == None: + ctx = self._ctx + out = create_string_buffer(len(data)) + topazCryptoDecrypt(ctx, data, out, len(data)) + return out.raw + + print "Using Library AlfCrypto DLL/DYLIB/SO" + return (AES_CBC, Pukall_Cipher, Topaz_Cipher) + + +def _load_python_alfcrypto(): + + import aescbc + + class Pukall_Cipher(object): + def __init__(self): + self.key = None + + def PC1(self, key, src, decryption=True): + sum1 = 0; + sum2 = 0; + keyXorVal = 0; + if len(key)!=16: + print "Bad key length!" + return None + wkey = [] + for i in xrange(8): + wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) + dst = "" + for i in xrange(len(src)): + temp1 = 0; + byteXorVal = 0; + for j in xrange(8): + temp1 ^= wkey[j] + sum2 = (sum2+j)*20021 + sum1 + sum1 = (temp1*346)&0xFFFF + sum2 = (sum2+sum1)&0xFFFF + temp1 = (temp1*20021+1)&0xFFFF + byteXorVal ^= temp1 ^ sum2 + curByte = ord(src[i]) + if not decryption: + keyXorVal = curByte * 257; + curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF + if decryption: + keyXorVal = curByte * 257; + for j in xrange(8): + wkey[j] ^= keyXorVal; + dst+=chr(curByte) + return dst + + class Topaz_Cipher(object): + def __init__(self): + self._ctx = None + + def ctx_init(self, key): + ctx1 = 0x0CAFFE19E + for keyChar in key: + keyByte = ord(keyChar) + ctx2 = ctx1 + ctx1 = ((((ctx1 >>2) * (ctx1 >>7))&0xFFFFFFFF) ^ (keyByte * keyByte * 0x0F902007)& 0xFFFFFFFF ) + self._ctx = [ctx1, ctx2] + return [ctx1,ctx2] + + def decrypt(self, data, ctx=None): + if ctx == None: + ctx = self._ctx + ctx1 = ctx[0] + ctx2 = ctx[1] + plainText = "" + for dataChar in data: + dataByte = ord(dataChar) + m = (dataByte ^ ((ctx1 >> 3) &0xFF) ^ ((ctx2<<3) & 0xFF)) &0xFF + ctx2 = ctx1 + ctx1 = (((ctx1 >> 2) * (ctx1 >> 7)) &0xFFFFFFFF) ^((m * m * 0x0F902007) &0xFFFFFFFF) + plainText += chr(m) + return plainText + + class AES_CBC(object): + def __init__(self): + self._key = None + self._iv = None + self.aes = None + + def set_decrypt_key(self, userkey, iv): + self._key = userkey + self._iv = iv + self.aes = aescbc.AES_CBC(userkey, aescbc.noPadding(), len(userkey)) + + def decrypt(self, data): + iv = self._iv + cleartext = self.aes.decrypt(iv + data) + return cleartext + + return (AES_CBC, Pukall_Cipher, Topaz_Cipher) + + +def _load_crypto(): + AES_CBC = Pukall_Cipher = Topaz_Cipher = None + cryptolist = (_load_libalfcrypto, _load_python_alfcrypto) + for loader in cryptolist: + try: + AES_CBC, Pukall_Cipher, Topaz_Cipher = loader() + break + except (ImportError, Exception): + pass + return AES_CBC, Pukall_Cipher, Topaz_Cipher + +AES_CBC, Pukall_Cipher, Topaz_Cipher = _load_crypto() + + +class KeyIVGen(object): + # this only exists in openssl so we will use pure python implementation instead + # PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1', + # [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p]) + def pbkdf2(self, passwd, salt, iter, keylen): + + def xorstr( a, b ): + if len(a) != len(b): + raise Exception("xorstr(): lengths differ") + return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b))) + + def prf( h, data ): + hm = h.copy() + hm.update( data ) + return hm.digest() + + def pbkdf2_F( h, salt, itercount, blocknum ): + U = prf( h, salt + pack('>i',blocknum ) ) + T = U + for i in range(2, itercount+1): + U = prf( h, U ) + T = xorstr( T, U ) + return T + + sha = hashlib.sha1 + digest_size = sha().digest_size + # l - number of output blocks to produce + l = keylen / digest_size + if keylen % digest_size != 0: + l += 1 + h = hmac.new( passwd, None, sha ) + T = "" + for i in range(1, l+1): + T += pbkdf2_F( h, salt, iter, i ) + return T[0: keylen] + -class AES_CBC(CBC): - """ AES encryption in CBC feedback mode """ - def __init__(self, key=None, padding=padWithPadLen(), keySize=16): - CBC.__init__( self, AES(key, noPadding(), keySize), padding) - self.name = 'AES_CBC' diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto64.dll b/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto64.dll index 26d740ddb0ed3be82128b3fbe0e45471b0e04f4b..7bef68eac0d0a751b0c7090f5b5427a1d5ab1a59 100644 GIT binary patch literal 52224 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 NdK>>N@IQ70{vUg3cj*8C literal 70144 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 G!2bbGSZYrI diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto_src.zip b/Calibre_Plugins/K4MobiDeDRM_plugin/alfcrypto_src.zip index e25a0c8227ff869069f615b6cd5ad985c1164a7e..269810cfa24541f8be10050e7192fb6211a45a98 100644 GIT binary patch literal 17393 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 VE;*}@u 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 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/cmbtc_v2.2.py b/Calibre_Plugins/K4MobiDeDRM_plugin/cmbtc_v2.2.py deleted file mode 100644 index 7bef68eac0d0a751b0c7090f5b5427a1d5ab1a59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52224 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 NdK>>N@IQ70{vUg3cj*8C diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/config.py b/Calibre_Plugins/K4MobiDeDRM_plugin/config.py index 269810cfa24541f8be10050e7192fb6211a45a98..98258788c86dbb40a3f2e773d185baad750d4cdf 100644 GIT binary patch literal 2018 zcmb_dU2EGg6n&n5#a;Gb2ZFmo8Kcmbt}SH!Skjhtqm&@q*NLbs8A)D}f4?h#$4%Eh zECgap_g)>HbM8&5RR*)|+#ZE<3p~q}wlII-$r9}Z=08u>=8A1q;a;v-gu?xDiRTHo zo|n4pm~C7FZnf1f?ZJTZn~E4=35^5|Mc{0z@- zul=#R?;To5FlB|XrpgSP*GE6+iSeJP)M)f#&!FZ^yt@4`y*Z!Vo?lFVl?85;M9=eL z!Hj{5?MlF)7IJ*-c|adJ92N^Mxm_#*gCeC3x9&c5Xt=xIZoBAE5Zaz4g7NOQL0fsH ztQ`vCpsEc}rdPKrUE~Ut4}p(U`3#p_CZyY4d2f(KMhX}RF1R)p;uX^@wxr4F1P;G+ z?`8spLDDD7NLZdiMou9QF$qM;m_*n)UxX}4YG#Ij_G5KQw|1`>+bvlO71Gu3abtth z9*=&j-}?}lL0FF{SNXI^l5NFlU3iF=yfmcMSut1fGg)vpJtHo^0`=G7=l{oEbLbv0 zZ}R5MKP0VchyfKZG;>jdmQ}2MaY?5 zz3r>|h#@g+nw#~+dfiq#LA9MaK>kzfKeBr-md2)37ThT4dfK_aGtn2{2HrA6`~=!^*HfxS$TOTrN$2VtuHYsXRknYOJn0#I1zZ_2PIO zYRm7$-`^E$kKP2W PsoPe2snh<%@{i#!ZkBjH literal 17393 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 VE;*= 0x80: + datax = (data & 0x7F) + while data >= 0x80 : + c = file.read(1) + if (len(c) == 0): + return None + data = ord(c) + datax = (datax <<7) + (data & 0x7F) + data = datax + + if flag: + data = -data + return data -class ConfigWidget(QWidget): +# returns a binary string that encodes a number into 7 bits +# most significant byte first which has the high bit set - def __init__(self): - QWidget.__init__(self) - self.l = QVBoxLayout() - self.setLayout(self.l) +def encodeNumber(number): + result = "" + negative = False + flag = 0 - self.serialLabel = QLabel('eInk Kindle Serial numbers (First character B, 16 characters, use commas if more than one)') - self.l.addWidget(self.serialLabel) + if number < 0 : + number = -number + 1 + negative = True - self.serials = QLineEdit(self) - self.serials.setText(prefs['serials']) - self.l.addWidget(self.serials) - self.serialLabel.setBuddy(self.serials) + while True: + byte = number & 0x7F + number = number >> 7 + byte += flag + result += chr(byte) + flag = 0x80 + if number == 0 : + if (byte == 0xFF and negative == False) : + result += chr(0x80) + break - self.pidLabel = QLabel('Mobipocket PIDs (8 or 10 characters, use commas if more than one)') - self.l.addWidget(self.pidLabel) + if negative: + result += chr(0xFF) - self.pids = QLineEdit(self) - self.pids.setText(prefs['pids']) - self.l.addWidget(self.pids) - self.pidLabel.setBuddy(self.serials) + return result[::-1] - self.wpLabel = QLabel('For Linux only: WINEPREFIX (enter absolute path)') - self.l.addWidget(self.wpLabel) - self.wineprefix = QLineEdit(self) - wineprefix = prefs['WINEPREFIX'] - if wineprefix is not None: - self.wineprefix.setText(wineprefix) + +# create / read a length prefixed string from the file + +def lengthPrefixString(data): + return encodeNumber(len(data))+data + +def readString(file): + stringLength = readEncodedNumber(file) + if (stringLength == None): + return "" + sv = file.read(stringLength) + if (len(sv) != stringLength): + return "" + return unpack(str(stringLength)+"s",sv)[0] + + +# convert a binary string generated by encodeNumber (7 bit encoded number) +# to the value you would find inside the page*.dat files to be processed + +def convert(i): + result = '' + val = encodeNumber(i) + for j in xrange(len(val)): + c = ord(val[j:j+1]) + result += '%02x' % c + return result + + + +# the complete string table used to store all book text content +# as well as the xml tokens and values that make sense out of it + +class Dictionary(object): + def __init__(self, dictFile): + self.filename = dictFile + self.size = 0 + self.fo = file(dictFile,'rb') + self.stable = [] + self.size = readEncodedNumber(self.fo) + for i in xrange(self.size): + self.stable.append(self.escapestr(readString(self.fo))) + self.pos = 0 + + def escapestr(self, str): + str = str.replace('&','&') + str = str.replace('<','<') + str = str.replace('>','>') + str = str.replace('=','=') + return str + + def lookup(self,val): + if ((val >= 0) and (val < self.size)) : + self.pos = val + return self.stable[self.pos] else: - self.wineprefix.setText('') + print "Error - %d outside of string table limits" % val + raise TpzDRMError('outside of string table limits') + # sys.exit(-1) - self.l.addWidget(self.wineprefix) - self.wpLabel.setBuddy(self.wineprefix) + def getSize(self): + return self.size - def save_settings(self): - prefs['pids'] = str(self.pids.text()).replace(" ","") - prefs['serials'] = str(self.serials.text()).replace(" ","") - winepref=str(self.wineprefix.text()) - if winepref.strip() != '': - prefs['WINEPREFIX'] = winepref + def getPos(self): + return self.pos + + def dumpDict(self): + for i in xrange(self.size): + print "%d %s %s" % (i, convert(i), self.stable[i]) + return + +# parses the xml snippets that are represented by each page*.dat file. +# also parses the other0.dat file - the main stylesheet +# and information used to inject the xml snippets into page*.dat files + +class PageParser(object): + def __init__(self, filename, dict, debug, flat_xml): + self.fo = file(filename,'rb') + self.id = os.path.basename(filename).replace('.dat','') + self.dict = dict + self.debug = debug + self.flat_xml = flat_xml + self.tagpath = [] + self.doc = [] + self.snippetList = [] + + + # hash table used to enable the decoding process + # This has all been developed by trial and error so it may still have omissions or + # contain errors + # Format: + # tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped) + + token_tags = { + 'x' : (1, 'scalar_number', 0, 0), + 'y' : (1, 'scalar_number', 0, 0), + 'h' : (1, 'scalar_number', 0, 0), + 'w' : (1, 'scalar_number', 0, 0), + 'firstWord' : (1, 'scalar_number', 0, 0), + 'lastWord' : (1, 'scalar_number', 0, 0), + 'rootID' : (1, 'scalar_number', 0, 0), + 'stemID' : (1, 'scalar_number', 0, 0), + 'type' : (1, 'scalar_text', 0, 0), + + 'info' : (0, 'number', 1, 0), + + 'info.word' : (0, 'number', 1, 1), + 'info.word.ocrText' : (1, 'text', 0, 0), + 'info.word.firstGlyph' : (1, 'raw', 0, 0), + 'info.word.lastGlyph' : (1, 'raw', 0, 0), + 'info.word.bl' : (1, 'raw', 0, 0), + 'info.word.link_id' : (1, 'number', 0, 0), + + 'glyph' : (0, 'number', 1, 1), + 'glyph.x' : (1, 'number', 0, 0), + 'glyph.y' : (1, 'number', 0, 0), + 'glyph.glyphID' : (1, 'number', 0, 0), + + 'dehyphen' : (0, 'number', 1, 1), + 'dehyphen.rootID' : (1, 'number', 0, 0), + 'dehyphen.stemID' : (1, 'number', 0, 0), + 'dehyphen.stemPage' : (1, 'number', 0, 0), + 'dehyphen.sh' : (1, 'number', 0, 0), + + 'links' : (0, 'number', 1, 1), + 'links.page' : (1, 'number', 0, 0), + 'links.rel' : (1, 'number', 0, 0), + 'links.row' : (1, 'number', 0, 0), + 'links.title' : (1, 'text', 0, 0), + 'links.href' : (1, 'text', 0, 0), + 'links.type' : (1, 'text', 0, 0), + 'links.id' : (1, 'number', 0, 0), + + 'paraCont' : (0, 'number', 1, 1), + 'paraCont.rootID' : (1, 'number', 0, 0), + 'paraCont.stemID' : (1, 'number', 0, 0), + 'paraCont.stemPage' : (1, 'number', 0, 0), + + 'paraStems' : (0, 'number', 1, 1), + 'paraStems.stemID' : (1, 'number', 0, 0), + + 'wordStems' : (0, 'number', 1, 1), + 'wordStems.stemID' : (1, 'number', 0, 0), + + 'empty' : (1, 'snippets', 1, 0), + + 'page' : (1, 'snippets', 1, 0), + 'page.pageid' : (1, 'scalar_text', 0, 0), + 'page.pagelabel' : (1, 'scalar_text', 0, 0), + 'page.type' : (1, 'scalar_text', 0, 0), + 'page.h' : (1, 'scalar_number', 0, 0), + 'page.w' : (1, 'scalar_number', 0, 0), + 'page.startID' : (1, 'scalar_number', 0, 0), + + 'group' : (1, 'snippets', 1, 0), + 'group.type' : (1, 'scalar_text', 0, 0), + 'group._tag' : (1, 'scalar_text', 0, 0), + 'group.orientation': (1, 'scalar_text', 0, 0), + + 'region' : (1, 'snippets', 1, 0), + 'region.type' : (1, 'scalar_text', 0, 0), + 'region.x' : (1, 'scalar_number', 0, 0), + 'region.y' : (1, 'scalar_number', 0, 0), + 'region.h' : (1, 'scalar_number', 0, 0), + 'region.w' : (1, 'scalar_number', 0, 0), + 'region.orientation' : (1, 'scalar_text', 0, 0), + + 'empty_text_region' : (1, 'snippets', 1, 0), + + 'img' : (1, 'snippets', 1, 0), + 'img.x' : (1, 'scalar_number', 0, 0), + 'img.y' : (1, 'scalar_number', 0, 0), + 'img.h' : (1, 'scalar_number', 0, 0), + 'img.w' : (1, 'scalar_number', 0, 0), + 'img.src' : (1, 'scalar_number', 0, 0), + 'img.color_src' : (1, 'scalar_number', 0, 0), + + 'paragraph' : (1, 'snippets', 1, 0), + 'paragraph.class' : (1, 'scalar_text', 0, 0), + 'paragraph.firstWord' : (1, 'scalar_number', 0, 0), + 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), + 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), + 'paragraph.gridSize' : (1, 'scalar_number', 0, 0), + 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0), + + + 'word_semantic' : (1, 'snippets', 1, 1), + 'word_semantic.type' : (1, 'scalar_text', 0, 0), + 'word_semantic.firstWord' : (1, 'scalar_number', 0, 0), + 'word_semantic.lastWord' : (1, 'scalar_number', 0, 0), + + 'word' : (1, 'snippets', 1, 0), + 'word.type' : (1, 'scalar_text', 0, 0), + 'word.class' : (1, 'scalar_text', 0, 0), + 'word.firstGlyph' : (1, 'scalar_number', 0, 0), + 'word.lastGlyph' : (1, 'scalar_number', 0, 0), + + '_span' : (1, 'snippets', 1, 0), + '_span.firstWord' : (1, 'scalar_number', 0, 0), + '_span.lastWord' : (1, 'scalar_number', 0, 0), + '_span.gridSize' : (1, 'scalar_number', 0, 0), + '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0), + '_span.gridTopCenter' : (1, 'scalar_number', 0, 0), + '_span.gridBeginCenter' : (1, 'scalar_number', 0, 0), + '_span.gridEndCenter' : (1, 'scalar_number', 0, 0), + + 'span' : (1, 'snippets', 1, 0), + 'span.firstWord' : (1, 'scalar_number', 0, 0), + 'span.lastWord' : (1, 'scalar_number', 0, 0), + 'span.gridSize' : (1, 'scalar_number', 0, 0), + 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0), + 'span.gridTopCenter' : (1, 'scalar_number', 0, 0), + 'span.gridBeginCenter' : (1, 'scalar_number', 0, 0), + 'span.gridEndCenter' : (1, 'scalar_number', 0, 0), + + 'extratokens' : (1, 'snippets', 1, 0), + 'extratokens.type' : (1, 'scalar_text', 0, 0), + 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), + 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), + + 'glyph.h' : (1, 'number', 0, 0), + 'glyph.w' : (1, 'number', 0, 0), + 'glyph.use' : (1, 'number', 0, 0), + 'glyph.vtx' : (1, 'number', 0, 1), + 'glyph.len' : (1, 'number', 0, 1), + 'glyph.dpi' : (1, 'number', 0, 0), + 'vtx' : (0, 'number', 1, 1), + 'vtx.x' : (1, 'number', 0, 0), + 'vtx.y' : (1, 'number', 0, 0), + 'len' : (0, 'number', 1, 1), + 'len.n' : (1, 'number', 0, 0), + + 'book' : (1, 'snippets', 1, 0), + 'version' : (1, 'snippets', 1, 0), + 'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0), + 'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0), + 'version.Schema_id' : (1, 'scalar_text', 0, 0), + 'version.Schema_version' : (1, 'scalar_text', 0, 0), + 'version.Topaz_version' : (1, 'scalar_text', 0, 0), + 'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0), + 'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0), + 'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0), + 'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0), + 'version.chapterheaders' : (1, 'scalar_text', 0, 0), + 'version.creation_date' : (1, 'scalar_text', 0, 0), + 'version.header_footer' : (1, 'scalar_text', 0, 0), + 'version.init_from_ocr' : (1, 'scalar_text', 0, 0), + 'version.letter_insertion' : (1, 'scalar_text', 0, 0), + 'version.xmlinj_convert' : (1, 'scalar_text', 0, 0), + 'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0), + 'version.xmlinj_transform' : (1, 'scalar_text', 0, 0), + 'version.findlists' : (1, 'scalar_text', 0, 0), + 'version.page_num' : (1, 'scalar_text', 0, 0), + 'version.page_type' : (1, 'scalar_text', 0, 0), + 'version.bad_text' : (1, 'scalar_text', 0, 0), + 'version.glyph_mismatch' : (1, 'scalar_text', 0, 0), + 'version.margins' : (1, 'scalar_text', 0, 0), + 'version.staggered_lines' : (1, 'scalar_text', 0, 0), + 'version.paragraph_continuation' : (1, 'scalar_text', 0, 0), + 'version.toc' : (1, 'scalar_text', 0, 0), + + 'stylesheet' : (1, 'snippets', 1, 0), + 'style' : (1, 'snippets', 1, 0), + 'style._tag' : (1, 'scalar_text', 0, 0), + 'style.type' : (1, 'scalar_text', 0, 0), + 'style._parent_type' : (1, 'scalar_text', 0, 0), + 'style.class' : (1, 'scalar_text', 0, 0), + 'style._after_class' : (1, 'scalar_text', 0, 0), + 'rule' : (1, 'snippets', 1, 0), + 'rule.attr' : (1, 'scalar_text', 0, 0), + 'rule.value' : (1, 'scalar_text', 0, 0), + + 'original' : (0, 'number', 1, 1), + 'original.pnum' : (1, 'number', 0, 0), + 'original.pid' : (1, 'text', 0, 0), + 'pages' : (0, 'number', 1, 1), + 'pages.ref' : (1, 'number', 0, 0), + 'pages.id' : (1, 'number', 0, 0), + 'startID' : (0, 'number', 1, 1), + 'startID.page' : (1, 'number', 0, 0), + 'startID.id' : (1, 'number', 0, 0), + + } + + + # full tag path record keeping routines + def tag_push(self, token): + self.tagpath.append(token) + def tag_pop(self): + if len(self.tagpath) > 0 : + self.tagpath.pop() + def tagpath_len(self): + return len(self.tagpath) + def get_tagpath(self, i): + cnt = len(self.tagpath) + if i < cnt : result = self.tagpath[i] + for j in xrange(i+1, cnt) : + result += '.' + self.tagpath[j] + return result + + + # list of absolute command byte values values that indicate + # various types of loop meachanisms typically used to generate vectors + + cmd_list = (0x76, 0x76) + + # peek at and return 1 byte that is ahead by i bytes + def peek(self, aheadi): + c = self.fo.read(aheadi) + if (len(c) == 0): + return None + self.fo.seek(-aheadi,1) + c = c[-1:] + return ord(c) + + + # get the next value from the file being processed + def getNext(self): + nbyte = self.peek(1); + if (nbyte == None): + return None + val = readEncodedNumber(self.fo) + return val + + + # format an arg by argtype + def formatArg(self, arg, argtype): + if (argtype == 'text') or (argtype == 'scalar_text') : + result = self.dict.lookup(arg) + elif (argtype == 'raw') or (argtype == 'number') or (argtype == 'scalar_number') : + result = arg + elif (argtype == 'snippets') : + result = arg + else : + print "Error Unknown argtype %s" % argtype + sys.exit(-2) + return result + + + # process the next tag token, recursively handling subtags, + # arguments, and commands + def procToken(self, token): + + known_token = False + self.tag_push(token) + + if self.debug : print 'Processing: ', self.get_tagpath(0) + cnt = self.tagpath_len() + for j in xrange(cnt): + tkn = self.get_tagpath(j) + if tkn in self.token_tags : + num_args = self.token_tags[tkn][0] + argtype = self.token_tags[tkn][1] + subtags = self.token_tags[tkn][2] + splcase = self.token_tags[tkn][3] + ntags = -1 + known_token = True + break + + if known_token : + + # handle subtags if present + subtagres = [] + if (splcase == 1): + # this type of tag uses of escape marker 0x74 indicate subtag count + if self.peek(1) == 0x74: + skip = readEncodedNumber(self.fo) + subtags = 1 + num_args = 0 + + if (subtags == 1): + ntags = readEncodedNumber(self.fo) + if self.debug : print 'subtags: ' + token + ' has ' + str(ntags) + for j in xrange(ntags): + val = readEncodedNumber(self.fo) + subtagres.append(self.procToken(self.dict.lookup(val))) + + # arguments can be scalars or vectors of text or numbers + argres = [] + if num_args > 0 : + firstarg = self.peek(1) + if (firstarg in self.cmd_list) and (argtype != 'scalar_number') and (argtype != 'scalar_text'): + # single argument is a variable length vector of data + arg = readEncodedNumber(self.fo) + argres = self.decodeCMD(arg,argtype) + else : + # num_arg scalar arguments + for i in xrange(num_args): + argres.append(self.formatArg(readEncodedNumber(self.fo), argtype)) + + # build the return tag + result = [] + tkn = self.get_tagpath(0) + result.append(tkn) + result.append(subtagres) + result.append(argtype) + result.append(argres) + self.tag_pop() + return result + + # all tokens that need to be processed should be in the hash + # table if it may indicate a problem, either new token + # or an out of sync condition else: - prefs['WINEPREFIX'] = None + result = [] + if (self.debug): + print 'Unknown Token:', token + self.tag_pop() + return result + + + # special loop used to process code snippets + # it is NEVER used to format arguments. + # builds the snippetList + def doLoop72(self, argtype): + cnt = readEncodedNumber(self.fo) + if self.debug : + result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n' + result += 'of the document is indicated by snippet number sets at the\n' + result += 'end of each snippet. \n' + print result + for i in xrange(cnt): + if self.debug: print 'Snippet:',str(i) + snippet = [] + snippet.append(i) + val = readEncodedNumber(self.fo) + snippet.append(self.procToken(self.dict.lookup(val))) + self.snippetList.append(snippet) + return + + + + # general loop code gracisouly submitted by "skindle" - thank you! + def doLoop76Mode(self, argtype, cnt, mode): + result = [] + adj = 0 + if mode & 1: + adj = readEncodedNumber(self.fo) + mode = mode >> 1 + x = [] + for i in xrange(cnt): + x.append(readEncodedNumber(self.fo) - adj) + for i in xrange(mode): + for j in xrange(1, cnt): + x[j] = x[j] + x[j - 1] + for i in xrange(cnt): + result.append(self.formatArg(x[i],argtype)) + return result + + + # dispatches loop commands bytes with various modes + # The 0x76 style loops are used to build vectors + + # This was all derived by trial and error and + # new loop types may exist that are not handled here + # since they did not appear in the test cases + + def decodeCMD(self, cmd, argtype): + if (cmd == 0x76): + + # loop with cnt, and mode to control loop styles + cnt = readEncodedNumber(self.fo) + mode = readEncodedNumber(self.fo) + + if self.debug : print 'Loop for', cnt, 'with mode', mode, ': ' + return self.doLoop76Mode(argtype, cnt, mode) + + if self.dbug: print "Unknown command", cmd + result = [] + return result + + + + # add full tag path to injected snippets + def updateName(self, tag, prefix): + name = tag[0] + subtagList = tag[1] + argtype = tag[2] + argList = tag[3] + nname = prefix + '.' + name + nsubtaglist = [] + for j in subtagList: + nsubtaglist.append(self.updateName(j,prefix)) + ntag = [] + ntag.append(nname) + ntag.append(nsubtaglist) + ntag.append(argtype) + ntag.append(argList) + return ntag + + + + # perform depth first injection of specified snippets into this one + def injectSnippets(self, snippet): + snipno, tag = snippet + name = tag[0] + subtagList = tag[1] + argtype = tag[2] + argList = tag[3] + nsubtagList = [] + if len(argList) > 0 : + for j in argList: + asnip = self.snippetList[j] + aso, atag = self.injectSnippets(asnip) + atag = self.updateName(atag, name) + nsubtagList.append(atag) + argtype='number' + argList=[] + if len(nsubtagList) > 0 : + subtagList.extend(nsubtagList) + tag = [] + tag.append(name) + tag.append(subtagList) + tag.append(argtype) + tag.append(argList) + snippet = [] + snippet.append(snipno) + snippet.append(tag) + return snippet + + + + # format the tag for output + def formatTag(self, node): + name = node[0] + subtagList = node[1] + argtype = node[2] + argList = node[3] + fullpathname = name.split('.') + nodename = fullpathname.pop() + ilvl = len(fullpathname) + indent = ' ' * (3 * ilvl) + rlst = [] + rlst.append(indent + '<' + nodename + '>') + if len(argList) > 0: + alst = [] + for j in argList: + if (argtype == 'text') or (argtype == 'scalar_text') : + alst.append(j + '|') + else : + alst.append(str(j) + ',') + argres = "".join(alst) + argres = argres[0:-1] + if argtype == 'snippets' : + rlst.append('snippets:' + argres) + else : + rlst.append(argres) + if len(subtagList) > 0 : + rlst.append('\n') + for j in subtagList: + if len(j) > 0 : + rlst.append(self.formatTag(j)) + rlst.append(indent + '\n') + else: + rlst.append('\n') + return "".join(rlst) + + + # flatten tag + def flattenTag(self, node): + name = node[0] + subtagList = node[1] + argtype = node[2] + argList = node[3] + rlst = [] + rlst.append(name) + if (len(argList) > 0): + alst = [] + for j in argList: + if (argtype == 'text') or (argtype == 'scalar_text') : + alst.append(j + '|') + else : + alst.append(str(j) + '|') + argres = "".join(alst) + argres = argres[0:-1] + if argtype == 'snippets' : + rlst.append('.snippets=' + argres) + else : + rlst.append('=' + argres) + rlst.append('\n') + for j in subtagList: + if len(j) > 0 : + rlst.append(self.flattenTag(j)) + return "".join(rlst) + + + # reduce create xml output + def formatDoc(self, flat_xml): + rlst = [] + for j in self.doc : + if len(j) > 0: + if flat_xml: + rlst.append(self.flattenTag(j)) + else: + rlst.append(self.formatTag(j)) + result = "".join(rlst) + if self.debug : print result + return result + + + + # main loop - parse the page.dat files + # to create structured document and snippets + + # FIXME: value at end of magic appears to be a subtags count + # but for what? For now, inject an 'info" tag as it is in + # every dictionary and seems close to what is meant + # The alternative is to special case the last _ "0x5f" to mean something + + def process(self): + + # peek at the first bytes to see what type of file it is + magic = self.fo.read(9) + if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'): + first_token = 'info' + elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'): + skip = self.fo.read(2) + first_token = 'info' + elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'): + first_token = 'info' + elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'): + skip = self.fo.read(3) + first_token = 'info' + else : + # other0.dat file + first_token = None + self.fo.seek(-9,1) + + + # main loop to read and build the document tree + while True: + + if first_token != None : + # use "inserted" first token 'info' for page and glyph files + tag = self.procToken(first_token) + if len(tag) > 0 : + self.doc.append(tag) + first_token = None + + v = self.getNext() + if (v == None): + break + + if (v == 0x72): + self.doLoop72('number') + elif (v > 0) and (v < self.dict.getSize()) : + tag = self.procToken(self.dict.lookup(v)) + if len(tag) > 0 : + self.doc.append(tag) + else: + if self.debug: + print "Main Loop: Unknown value: %x" % v + if (v == 0): + if (self.peek(1) == 0x5f): + skip = self.fo.read(1) + first_token = 'info' + + # now do snippet injection + if len(self.snippetList) > 0 : + if self.debug : print 'Injecting Snippets:' + snippet = self.injectSnippets(self.snippetList[0]) + snipno = snippet[0] + tag_add = snippet[1] + if self.debug : print self.formatTag(tag_add) + if len(tag_add) > 0: + self.doc.append(tag_add) + + # handle generation of xml output + xmlpage = self.formatDoc(self.flat_xml) + + return xmlpage + + +def fromData(dict, fname): + flat_xml = True + debug = False + pp = PageParser(fname, dict, debug, flat_xml) + xmlpage = pp.process() + return xmlpage + +def getXML(dict, fname): + flat_xml = False + debug = False + pp = PageParser(fname, dict, debug, flat_xml) + xmlpage = pp.process() + return xmlpage + +def usage(): + print 'Usage: ' + print ' convert2xml.py dict0000.dat infile.dat ' + print ' ' + print ' Options:' + print ' -h print this usage help message ' + print ' -d turn on debug output to check for potential errors ' + print ' --flat-xml output the flattened xml page description only ' + print ' ' + print ' This program will attempt to convert a page*.dat file or ' + print ' glyphs*.dat file, using the dict0000.dat file, to its xml description. ' + print ' ' + print ' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump ' + print ' the *.dat files from a Topaz format e-book.' + +# +# Main +# + +def main(argv): + dictFile = "" + pageFile = "" + debug = False + flat_xml = False + printOutput = False + if len(argv) == 0: + printOutput = True + argv = sys.argv + + try: + opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"]) + + except getopt.GetoptError, err: + + # print help information and exit: + print str(err) # will print something like "option -a not recognized" + usage() + sys.exit(2) + + if len(opts) == 0 and len(args) == 0 : + usage() + sys.exit(2) + + for o, a in opts: + if o =="-d": + debug=True + if o =="-h": + usage() + sys.exit(0) + if o =="--flat-xml": + flat_xml = True + + dictFile, pageFile = args[0], args[1] + + # read in the string table dictionary + dict = Dictionary(dictFile) + # dict.dumpDict() + + # create a page parser + pp = PageParser(pageFile, dict, debug, flat_xml) + + xmlpage = pp.process() + + if printOutput: + print xmlpage + return 0 + + return xmlpage + +if __name__ == '__main__': + sys.exit(main('')) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2html.py b/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2html.py index 3cdc820035684d0d3dfe606b54de768977ec6652..e5647f4bc3842abb1546088d41b2c899fa88c36a 100644 GIT binary patch literal 29407 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 literal 171 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aU~Fg_^W}KA;dKz1VrNojv*mItQOl@ zfGR*b<^VB2nrcRbxOj4Yu3ln6K~Ab(NNPoiYhH4GN@iX`zm7DE64 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py b/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py index c412d7b..4dfd6c7 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/flatxml2svg.py @@ -1,846 +1,249 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.6 - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) import sys -sys.stdout=Unbuffered(sys.stdout) - import csv import os import getopt from struct import pack from struct import unpack -class TpzDRMError(Exception): - pass -# Get a 7 bit encoded number from string. The most -# significant byte comes first and has the high bit (8th) set +class PParser(object): + def __init__(self, gd, flatxml, meta_array): + self.gd = gd + self.flatdoc = flatxml.split('\n') + self.docSize = len(self.flatdoc) + self.temp = [] -def readEncodedNumber(file): - flag = False - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) + self.ph = -1 + self.pw = -1 + startpos = self.posinDoc('page.h') or self.posinDoc('book.h') + for p in startpos: + (name, argres) = self.lineinDoc(p) + self.ph = max(self.ph, int(argres)) + startpos = self.posinDoc('page.w') or self.posinDoc('book.w') + for p in startpos: + (name, argres) = self.lineinDoc(p) + self.pw = max(self.pw, int(argres)) - if data == 0xFF: - flag = True - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) + if self.ph <= 0: + self.ph = int(meta_array.get('pageHeight', '11000')) + if self.pw <= 0: + self.pw = int(meta_array.get('pageWidth', '8500')) - if data >= 0x80: - datax = (data & 0x7F) - while data >= 0x80 : - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - datax = (datax <<7) + (data & 0x7F) - data = datax + res = [] + startpos = self.posinDoc('info.glyph.x') + for p in startpos: + argres = self.getDataatPos('info.glyph.x', p) + res.extend(argres) + self.gx = res - if flag: - data = -data - return data + res = [] + startpos = self.posinDoc('info.glyph.y') + for p in startpos: + argres = self.getDataatPos('info.glyph.y', p) + res.extend(argres) + self.gy = res + + res = [] + startpos = self.posinDoc('info.glyph.glyphID') + for p in startpos: + argres = self.getDataatPos('info.glyph.glyphID', p) + res.extend(argres) + self.gid = res -# returns a binary string that encodes a number into 7 bits -# most significant byte first which has the high bit set + # return tag at line pos in document + def lineinDoc(self, pos) : + if (pos >= 0) and (pos < self.docSize) : + item = self.flatdoc[pos] + if item.find('=') >= 0: + (name, argres) = item.split('=',1) + else : + name = item + argres = '' + return name, argres -def encodeNumber(number): - result = "" - negative = False - flag = 0 - - if number < 0 : - number = -number + 1 - negative = True - - while True: - byte = number & 0x7F - number = number >> 7 - byte += flag - result += chr(byte) - flag = 0x80 - if number == 0 : - if (byte == 0xFF and negative == False) : - result += chr(0x80) - break - - if negative: - result += chr(0xFF) - - return result[::-1] - - - -# create / read a length prefixed string from the file - -def lengthPrefixString(data): - return encodeNumber(len(data))+data - -def readString(file): - stringLength = readEncodedNumber(file) - if (stringLength == None): - return "" - sv = file.read(stringLength) - if (len(sv) != stringLength): - return "" - return unpack(str(stringLength)+"s",sv)[0] - - -# convert a binary string generated by encodeNumber (7 bit encoded number) -# to the value you would find inside the page*.dat files to be processed - -def convert(i): - result = '' - val = encodeNumber(i) - for j in xrange(len(val)): - c = ord(val[j:j+1]) - result += '%02x' % c - return result - - - -# the complete string table used to store all book text content -# as well as the xml tokens and values that make sense out of it - -class Dictionary(object): - def __init__(self, dictFile): - self.filename = dictFile - self.size = 0 - self.fo = file(dictFile,'rb') - self.stable = [] - self.size = readEncodedNumber(self.fo) - for i in xrange(self.size): - self.stable.append(self.escapestr(readString(self.fo))) - self.pos = 0 - - def escapestr(self, str): - str = str.replace('&','&') - str = str.replace('<','<') - str = str.replace('>','>') - str = str.replace('=','=') - return str - - def lookup(self,val): - if ((val >= 0) and (val < self.size)) : - self.pos = val - return self.stable[self.pos] + # find tag in doc if within pos to end inclusive + def findinDoc(self, tagpath, pos, end) : + result = None + if end == -1 : + end = self.docSize else: - print "Error - %d outside of string table limits" % val - raise TpzDRMError('outside of string table limits') - # sys.exit(-1) + end = min(self.docSize, end) + foundat = -1 + for j in xrange(pos, end): + item = self.flatdoc[j] + if item.find('=') >= 0: + (name, argres) = item.split('=',1) + else : + name = item + argres = '' + if name.endswith(tagpath) : + result = argres + foundat = j + break + return foundat, result - def getSize(self): - return self.size + # return list of start positions for the tagpath + def posinDoc(self, tagpath): + startpos = [] + pos = 0 + res = "" + while res != None : + (foundpos, res) = self.findinDoc(tagpath, pos, -1) + if res != None : + startpos.append(foundpos) + pos = foundpos + 1 + return startpos - def getPos(self): - return self.pos - - def dumpDict(self): - for i in xrange(self.size): - print "%d %s %s" % (i, convert(i), self.stable[i]) - return - -# parses the xml snippets that are represented by each page*.dat file. -# also parses the other0.dat file - the main stylesheet -# and information used to inject the xml snippets into page*.dat files - -class PageParser(object): - def __init__(self, filename, dict, debug, flat_xml): - self.fo = file(filename,'rb') - self.id = os.path.basename(filename).replace('.dat','') - self.dict = dict - self.debug = debug - self.flat_xml = flat_xml - self.tagpath = [] - self.doc = [] - self.snippetList = [] - - - # hash table used to enable the decoding process - # This has all been developed by trial and error so it may still have omissions or - # contain errors - # Format: - # tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped) - - token_tags = { - 'x' : (1, 'scalar_number', 0, 0), - 'y' : (1, 'scalar_number', 0, 0), - 'h' : (1, 'scalar_number', 0, 0), - 'w' : (1, 'scalar_number', 0, 0), - 'firstWord' : (1, 'scalar_number', 0, 0), - 'lastWord' : (1, 'scalar_number', 0, 0), - 'rootID' : (1, 'scalar_number', 0, 0), - 'stemID' : (1, 'scalar_number', 0, 0), - 'type' : (1, 'scalar_text', 0, 0), - - 'info' : (0, 'number', 1, 0), - - 'info.word' : (0, 'number', 1, 1), - 'info.word.ocrText' : (1, 'text', 0, 0), - 'info.word.firstGlyph' : (1, 'raw', 0, 0), - 'info.word.lastGlyph' : (1, 'raw', 0, 0), - 'info.word.bl' : (1, 'raw', 0, 0), - 'info.word.link_id' : (1, 'number', 0, 0), - - 'glyph' : (0, 'number', 1, 1), - 'glyph.x' : (1, 'number', 0, 0), - 'glyph.y' : (1, 'number', 0, 0), - 'glyph.glyphID' : (1, 'number', 0, 0), - - 'dehyphen' : (0, 'number', 1, 1), - 'dehyphen.rootID' : (1, 'number', 0, 0), - 'dehyphen.stemID' : (1, 'number', 0, 0), - 'dehyphen.stemPage' : (1, 'number', 0, 0), - 'dehyphen.sh' : (1, 'number', 0, 0), - - 'links' : (0, 'number', 1, 1), - 'links.page' : (1, 'number', 0, 0), - 'links.rel' : (1, 'number', 0, 0), - 'links.row' : (1, 'number', 0, 0), - 'links.title' : (1, 'text', 0, 0), - 'links.href' : (1, 'text', 0, 0), - 'links.type' : (1, 'text', 0, 0), - 'links.id' : (1, 'number', 0, 0), - - 'paraCont' : (0, 'number', 1, 1), - 'paraCont.rootID' : (1, 'number', 0, 0), - 'paraCont.stemID' : (1, 'number', 0, 0), - 'paraCont.stemPage' : (1, 'number', 0, 0), - - 'paraStems' : (0, 'number', 1, 1), - 'paraStems.stemID' : (1, 'number', 0, 0), - - 'wordStems' : (0, 'number', 1, 1), - 'wordStems.stemID' : (1, 'number', 0, 0), - - 'empty' : (1, 'snippets', 1, 0), - - 'page' : (1, 'snippets', 1, 0), - 'page.pageid' : (1, 'scalar_text', 0, 0), - 'page.pagelabel' : (1, 'scalar_text', 0, 0), - 'page.type' : (1, 'scalar_text', 0, 0), - 'page.h' : (1, 'scalar_number', 0, 0), - 'page.w' : (1, 'scalar_number', 0, 0), - 'page.startID' : (1, 'scalar_number', 0, 0), - - 'group' : (1, 'snippets', 1, 0), - 'group.type' : (1, 'scalar_text', 0, 0), - 'group._tag' : (1, 'scalar_text', 0, 0), - 'group.orientation': (1, 'scalar_text', 0, 0), - - 'region' : (1, 'snippets', 1, 0), - 'region.type' : (1, 'scalar_text', 0, 0), - 'region.x' : (1, 'scalar_number', 0, 0), - 'region.y' : (1, 'scalar_number', 0, 0), - 'region.h' : (1, 'scalar_number', 0, 0), - 'region.w' : (1, 'scalar_number', 0, 0), - 'region.orientation' : (1, 'scalar_text', 0, 0), - - 'empty_text_region' : (1, 'snippets', 1, 0), - - 'img' : (1, 'snippets', 1, 0), - 'img.x' : (1, 'scalar_number', 0, 0), - 'img.y' : (1, 'scalar_number', 0, 0), - 'img.h' : (1, 'scalar_number', 0, 0), - 'img.w' : (1, 'scalar_number', 0, 0), - 'img.src' : (1, 'scalar_number', 0, 0), - 'img.color_src' : (1, 'scalar_number', 0, 0), - - 'paragraph' : (1, 'snippets', 1, 0), - 'paragraph.class' : (1, 'scalar_text', 0, 0), - 'paragraph.firstWord' : (1, 'scalar_number', 0, 0), - 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), - 'paragraph.lastWord' : (1, 'scalar_number', 0, 0), - 'paragraph.gridSize' : (1, 'scalar_number', 0, 0), - 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0), - 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0), - - - 'word_semantic' : (1, 'snippets', 1, 1), - 'word_semantic.type' : (1, 'scalar_text', 0, 0), - 'word_semantic.firstWord' : (1, 'scalar_number', 0, 0), - 'word_semantic.lastWord' : (1, 'scalar_number', 0, 0), - - 'word' : (1, 'snippets', 1, 0), - 'word.type' : (1, 'scalar_text', 0, 0), - 'word.class' : (1, 'scalar_text', 0, 0), - 'word.firstGlyph' : (1, 'scalar_number', 0, 0), - 'word.lastGlyph' : (1, 'scalar_number', 0, 0), - - '_span' : (1, 'snippets', 1, 0), - '_span.firstWord' : (1, 'scalar_number', 0, 0), - '_span.lastWord' : (1, 'scalar_number', 0, 0), - '_span.gridSize' : (1, 'scalar_number', 0, 0), - '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0), - '_span.gridTopCenter' : (1, 'scalar_number', 0, 0), - '_span.gridBeginCenter' : (1, 'scalar_number', 0, 0), - '_span.gridEndCenter' : (1, 'scalar_number', 0, 0), - - 'span' : (1, 'snippets', 1, 0), - 'span.firstWord' : (1, 'scalar_number', 0, 0), - 'span.lastWord' : (1, 'scalar_number', 0, 0), - 'span.gridSize' : (1, 'scalar_number', 0, 0), - 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0), - 'span.gridTopCenter' : (1, 'scalar_number', 0, 0), - 'span.gridBeginCenter' : (1, 'scalar_number', 0, 0), - 'span.gridEndCenter' : (1, 'scalar_number', 0, 0), - - 'extratokens' : (1, 'snippets', 1, 0), - 'extratokens.type' : (1, 'scalar_text', 0, 0), - 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), - 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), - - 'glyph.h' : (1, 'number', 0, 0), - 'glyph.w' : (1, 'number', 0, 0), - 'glyph.use' : (1, 'number', 0, 0), - 'glyph.vtx' : (1, 'number', 0, 1), - 'glyph.len' : (1, 'number', 0, 1), - 'glyph.dpi' : (1, 'number', 0, 0), - 'vtx' : (0, 'number', 1, 1), - 'vtx.x' : (1, 'number', 0, 0), - 'vtx.y' : (1, 'number', 0, 0), - 'len' : (0, 'number', 1, 1), - 'len.n' : (1, 'number', 0, 0), - - 'book' : (1, 'snippets', 1, 0), - 'version' : (1, 'snippets', 1, 0), - 'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0), - 'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0), - 'version.Schema_id' : (1, 'scalar_text', 0, 0), - 'version.Schema_version' : (1, 'scalar_text', 0, 0), - 'version.Topaz_version' : (1, 'scalar_text', 0, 0), - 'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0), - 'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0), - 'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0), - 'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0), - 'version.chapterheaders' : (1, 'scalar_text', 0, 0), - 'version.creation_date' : (1, 'scalar_text', 0, 0), - 'version.header_footer' : (1, 'scalar_text', 0, 0), - 'version.init_from_ocr' : (1, 'scalar_text', 0, 0), - 'version.letter_insertion' : (1, 'scalar_text', 0, 0), - 'version.xmlinj_convert' : (1, 'scalar_text', 0, 0), - 'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0), - 'version.xmlinj_transform' : (1, 'scalar_text', 0, 0), - 'version.findlists' : (1, 'scalar_text', 0, 0), - 'version.page_num' : (1, 'scalar_text', 0, 0), - 'version.page_type' : (1, 'scalar_text', 0, 0), - 'version.bad_text' : (1, 'scalar_text', 0, 0), - 'version.glyph_mismatch' : (1, 'scalar_text', 0, 0), - 'version.margins' : (1, 'scalar_text', 0, 0), - 'version.staggered_lines' : (1, 'scalar_text', 0, 0), - 'version.paragraph_continuation' : (1, 'scalar_text', 0, 0), - 'version.toc' : (1, 'scalar_text', 0, 0), - - 'stylesheet' : (1, 'snippets', 1, 0), - 'style' : (1, 'snippets', 1, 0), - 'style._tag' : (1, 'scalar_text', 0, 0), - 'style.type' : (1, 'scalar_text', 0, 0), - 'style._parent_type' : (1, 'scalar_text', 0, 0), - 'style.class' : (1, 'scalar_text', 0, 0), - 'style._after_class' : (1, 'scalar_text', 0, 0), - 'rule' : (1, 'snippets', 1, 0), - 'rule.attr' : (1, 'scalar_text', 0, 0), - 'rule.value' : (1, 'scalar_text', 0, 0), - - 'original' : (0, 'number', 1, 1), - 'original.pnum' : (1, 'number', 0, 0), - 'original.pid' : (1, 'text', 0, 0), - 'pages' : (0, 'number', 1, 1), - 'pages.ref' : (1, 'number', 0, 0), - 'pages.id' : (1, 'number', 0, 0), - 'startID' : (0, 'number', 1, 1), - 'startID.page' : (1, 'number', 0, 0), - 'startID.id' : (1, 'number', 0, 0), - - } - - - # full tag path record keeping routines - def tag_push(self, token): - self.tagpath.append(token) - def tag_pop(self): - if len(self.tagpath) > 0 : - self.tagpath.pop() - def tagpath_len(self): - return len(self.tagpath) - def get_tagpath(self, i): - cnt = len(self.tagpath) - if i < cnt : result = self.tagpath[i] - for j in xrange(i+1, cnt) : - result += '.' + self.tagpath[j] - return result - - - # list of absolute command byte values values that indicate - # various types of loop meachanisms typically used to generate vectors - - cmd_list = (0x76, 0x76) - - # peek at and return 1 byte that is ahead by i bytes - def peek(self, aheadi): - c = self.fo.read(aheadi) - if (len(c) == 0): - return None - self.fo.seek(-aheadi,1) - c = c[-1:] - return ord(c) - - - # get the next value from the file being processed - def getNext(self): - nbyte = self.peek(1); - if (nbyte == None): - return None - val = readEncodedNumber(self.fo) - return val - - - # format an arg by argtype - def formatArg(self, arg, argtype): - if (argtype == 'text') or (argtype == 'scalar_text') : - result = self.dict.lookup(arg) - elif (argtype == 'raw') or (argtype == 'number') or (argtype == 'scalar_number') : - result = arg - elif (argtype == 'snippets') : - result = arg - else : - print "Error Unknown argtype %s" % argtype - sys.exit(-2) - return result - - - # process the next tag token, recursively handling subtags, - # arguments, and commands - def procToken(self, token): - - known_token = False - self.tag_push(token) - - if self.debug : print 'Processing: ', self.get_tagpath(0) - cnt = self.tagpath_len() + def getData(self, path): + result = None + cnt = len(self.flatdoc) for j in xrange(cnt): - tkn = self.get_tagpath(j) - if tkn in self.token_tags : - num_args = self.token_tags[tkn][0] - argtype = self.token_tags[tkn][1] - subtags = self.token_tags[tkn][2] - splcase = self.token_tags[tkn][3] - ntags = -1 - known_token = True - break - - if known_token : - - # handle subtags if present - subtagres = [] - if (splcase == 1): - # this type of tag uses of escape marker 0x74 indicate subtag count - if self.peek(1) == 0x74: - skip = readEncodedNumber(self.fo) - subtags = 1 - num_args = 0 - - if (subtags == 1): - ntags = readEncodedNumber(self.fo) - if self.debug : print 'subtags: ' + token + ' has ' + str(ntags) - for j in xrange(ntags): - val = readEncodedNumber(self.fo) - subtagres.append(self.procToken(self.dict.lookup(val))) - - # arguments can be scalars or vectors of text or numbers - argres = [] - if num_args > 0 : - firstarg = self.peek(1) - if (firstarg in self.cmd_list) and (argtype != 'scalar_number') and (argtype != 'scalar_text'): - # single argument is a variable length vector of data - arg = readEncodedNumber(self.fo) - argres = self.decodeCMD(arg,argtype) - else : - # num_arg scalar arguments - for i in xrange(num_args): - argres.append(self.formatArg(readEncodedNumber(self.fo), argtype)) - - # build the return tag - result = [] - tkn = self.get_tagpath(0) - result.append(tkn) - result.append(subtagres) - result.append(argtype) - result.append(argres) - self.tag_pop() - return result - - # all tokens that need to be processed should be in the hash - # table if it may indicate a problem, either new token - # or an out of sync condition - else: - result = [] - if (self.debug): - print 'Unknown Token:', token - self.tag_pop() - return result - - - # special loop used to process code snippets - # it is NEVER used to format arguments. - # builds the snippetList - def doLoop72(self, argtype): - cnt = readEncodedNumber(self.fo) - if self.debug : - result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n' - result += 'of the document is indicated by snippet number sets at the\n' - result += 'end of each snippet. \n' - print result - for i in xrange(cnt): - if self.debug: print 'Snippet:',str(i) - snippet = [] - snippet.append(i) - val = readEncodedNumber(self.fo) - snippet.append(self.procToken(self.dict.lookup(val))) - self.snippetList.append(snippet) - return - - - - # general loop code gracisouly submitted by "skindle" - thank you! - def doLoop76Mode(self, argtype, cnt, mode): - result = [] - adj = 0 - if mode & 1: - adj = readEncodedNumber(self.fo) - mode = mode >> 1 - x = [] - for i in xrange(cnt): - x.append(readEncodedNumber(self.fo) - adj) - for i in xrange(mode): - for j in xrange(1, cnt): - x[j] = x[j] + x[j - 1] - for i in xrange(cnt): - result.append(self.formatArg(x[i],argtype)) - return result - - - # dispatches loop commands bytes with various modes - # The 0x76 style loops are used to build vectors - - # This was all derived by trial and error and - # new loop types may exist that are not handled here - # since they did not appear in the test cases - - def decodeCMD(self, cmd, argtype): - if (cmd == 0x76): - - # loop with cnt, and mode to control loop styles - cnt = readEncodedNumber(self.fo) - mode = readEncodedNumber(self.fo) - - if self.debug : print 'Loop for', cnt, 'with mode', mode, ': ' - return self.doLoop76Mode(argtype, cnt, mode) - - if self.dbug: print "Unknown command", cmd - result = [] - return result - - - - # add full tag path to injected snippets - def updateName(self, tag, prefix): - name = tag[0] - subtagList = tag[1] - argtype = tag[2] - argList = tag[3] - nname = prefix + '.' + name - nsubtaglist = [] - for j in subtagList: - nsubtaglist.append(self.updateName(j,prefix)) - ntag = [] - ntag.append(nname) - ntag.append(nsubtaglist) - ntag.append(argtype) - ntag.append(argList) - return ntag - - - - # perform depth first injection of specified snippets into this one - def injectSnippets(self, snippet): - snipno, tag = snippet - name = tag[0] - subtagList = tag[1] - argtype = tag[2] - argList = tag[3] - nsubtagList = [] - if len(argList) > 0 : - for j in argList: - asnip = self.snippetList[j] - aso, atag = self.injectSnippets(asnip) - atag = self.updateName(atag, name) - nsubtagList.append(atag) - argtype='number' - argList=[] - if len(nsubtagList) > 0 : - subtagList.extend(nsubtagList) - tag = [] - tag.append(name) - tag.append(subtagList) - tag.append(argtype) - tag.append(argList) - snippet = [] - snippet.append(snipno) - snippet.append(tag) - return snippet - - - - # format the tag for output - def formatTag(self, node): - name = node[0] - subtagList = node[1] - argtype = node[2] - argList = node[3] - fullpathname = name.split('.') - nodename = fullpathname.pop() - ilvl = len(fullpathname) - indent = ' ' * (3 * ilvl) - rlst = [] - rlst.append(indent + '<' + nodename + '>') - if len(argList) > 0: - alst = [] - for j in argList: - if (argtype == 'text') or (argtype == 'scalar_text') : - alst.append(j + '|') - else : - alst.append(str(j) + ',') - argres = "".join(alst) - argres = argres[0:-1] - if argtype == 'snippets' : - rlst.append('snippets:' + argres) - else : - rlst.append(argres) - if len(subtagList) > 0 : - rlst.append('\n') - for j in subtagList: - if len(j) > 0 : - rlst.append(self.formatTag(j)) - rlst.append(indent + '\n') - else: - rlst.append('\n') - return "".join(rlst) - - - # flatten tag - def flattenTag(self, node): - name = node[0] - subtagList = node[1] - argtype = node[2] - argList = node[3] - rlst = [] - rlst.append(name) - if (len(argList) > 0): - alst = [] - for j in argList: - if (argtype == 'text') or (argtype == 'scalar_text') : - alst.append(j + '|') - else : - alst.append(str(j) + '|') - argres = "".join(alst) - argres = argres[0:-1] - if argtype == 'snippets' : - rlst.append('.snippets=' + argres) - else : - rlst.append('=' + argres) - rlst.append('\n') - for j in subtagList: - if len(j) > 0 : - rlst.append(self.flattenTag(j)) - return "".join(rlst) - - - # reduce create xml output - def formatDoc(self, flat_xml): - rlst = [] - for j in self.doc : - if len(j) > 0: - if flat_xml: - rlst.append(self.flattenTag(j)) - else: - rlst.append(self.formatTag(j)) - result = "".join(rlst) - if self.debug : print result - return result - - - - # main loop - parse the page.dat files - # to create structured document and snippets - - # FIXME: value at end of magic appears to be a subtags count - # but for what? For now, inject an 'info" tag as it is in - # every dictionary and seems close to what is meant - # The alternative is to special case the last _ "0x5f" to mean something - - def process(self): - - # peek at the first bytes to see what type of file it is - magic = self.fo.read(9) - if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'): - first_token = 'info' - elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'): - skip = self.fo.read(2) - first_token = 'info' - elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'): - first_token = 'info' - elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'): - skip = self.fo.read(3) - first_token = 'info' - else : - # other0.dat file - first_token = None - self.fo.seek(-9,1) - - - # main loop to read and build the document tree - while True: - - if first_token != None : - # use "inserted" first token 'info' for page and glyph files - tag = self.procToken(first_token) - if len(tag) > 0 : - self.doc.append(tag) - first_token = None - - v = self.getNext() - if (v == None): - break - - if (v == 0x72): - self.doLoop72('number') - elif (v > 0) and (v < self.dict.getSize()) : - tag = self.procToken(self.dict.lookup(v)) - if len(tag) > 0 : - self.doc.append(tag) + item = self.flatdoc[j] + if item.find('=') >= 0: + (name, argt) = item.split('=') + argres = argt.split('|') else: - if self.debug: - print "Main Loop: Unknown value: %x" % v - if (v == 0): - if (self.peek(1) == 0x5f): - skip = self.fo.read(1) - first_token = 'info' + name = item + argres = [] + if (name.endswith(path)): + result = argres + break + if (len(argres) > 0) : + for j in xrange(0,len(argres)): + argres[j] = int(argres[j]) + return result - # now do snippet injection - if len(self.snippetList) > 0 : - if self.debug : print 'Injecting Snippets:' - snippet = self.injectSnippets(self.snippetList[0]) - snipno = snippet[0] - tag_add = snippet[1] - if self.debug : print self.formatTag(tag_add) - if len(tag_add) > 0: - self.doc.append(tag_add) + def getDataatPos(self, path, pos): + result = None + item = self.flatdoc[pos] + if item.find('=') >= 0: + (name, argt) = item.split('=') + argres = argt.split('|') + else: + name = item + argres = [] + if (len(argres) > 0) : + for j in xrange(0,len(argres)): + argres[j] = int(argres[j]) + if (name.endswith(path)): + result = argres + return result - # handle generation of xml output - xmlpage = self.formatDoc(self.flat_xml) + def getDataTemp(self, path): + result = None + cnt = len(self.temp) + for j in xrange(cnt): + item = self.temp[j] + if item.find('=') >= 0: + (name, argt) = item.split('=') + argres = argt.split('|') + else: + name = item + argres = [] + if (name.endswith(path)): + result = argres + self.temp.pop(j) + break + if (len(argres) > 0) : + for j in xrange(0,len(argres)): + argres[j] = int(argres[j]) + return result - return xmlpage + def getImages(self): + result = [] + self.temp = self.flatdoc + while (self.getDataTemp('img') != None): + h = self.getDataTemp('img.h')[0] + w = self.getDataTemp('img.w')[0] + x = self.getDataTemp('img.x')[0] + y = self.getDataTemp('img.y')[0] + src = self.getDataTemp('img.src')[0] + result.append('\n' % (src, x, y, w, h)) + return result + + def getGlyphs(self): + result = [] + if (self.gid != None) and (len(self.gid) > 0): + glyphs = [] + for j in set(self.gid): + glyphs.append(j) + glyphs.sort() + for gid in glyphs: + id='id="gl%d"' % gid + path = self.gd.lookup(id) + if path: + result.append(id + ' ' + path) + return result -def fromData(dict, fname): - flat_xml = True - debug = False - pp = PageParser(fname, dict, debug, flat_xml) - xmlpage = pp.process() - return xmlpage +def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi): + mlst = [] + pp = PParser(gdict, flat_xml, meta_array) + mlst.append('\n') + if (raw): + mlst.append('\n') + mlst.append('\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1)) + mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) + else: + mlst.append('\n') + mlst.append('\n') + mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) + mlst.append('\n') + mlst.append('\n') + mlst.append('\n') + mlst.append('

\n') + if previd == None: + mlst.append('\n') + else: + mlst.append('\n') -def getXML(dict, fname): - flat_xml = False - debug = False - pp = PageParser(fname, dict, debug, flat_xml) - xmlpage = pp.process() - return xmlpage - -def usage(): - print 'Usage: ' - print ' convert2xml.py dict0000.dat infile.dat ' - print ' ' - print ' Options:' - print ' -h print this usage help message ' - print ' -d turn on debug output to check for potential errors ' - print ' --flat-xml output the flattened xml page description only ' - print ' ' - print ' This program will attempt to convert a page*.dat file or ' - print ' glyphs*.dat file, using the dict0000.dat file, to its xml description. ' - print ' ' - print ' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump ' - print ' the *.dat files from a Topaz format e-book.' - -# -# Main -# - -def main(argv): - dictFile = "" - pageFile = "" - debug = False - flat_xml = False - printOutput = False - if len(argv) == 0: - printOutput = True - argv = sys.argv - - try: - opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"]) - - except getopt.GetoptError, err: - - # print help information and exit: - print str(err) # will print something like "option -a not recognized" - usage() - sys.exit(2) - - if len(opts) == 0 and len(args) == 0 : - usage() - sys.exit(2) - - for o, a in opts: - if o =="-d": - debug=True - if o =="-h": - usage() - sys.exit(0) - if o =="--flat-xml": - flat_xml = True - - dictFile, pageFile = args[0], args[1] - - # read in the string table dictionary - dict = Dictionary(dictFile) - # dict.dumpDict() - - # create a page parser - pp = PageParser(pageFile, dict, debug, flat_xml) - - xmlpage = pp.process() - - if printOutput: - print xmlpage - return 0 - - return xmlpage - -if __name__ == '__main__': - sys.exit(main('')) + mlst.append('' % (pp.pw, pp.ph)) + if (pp.gid != None): + mlst.append('\n') + gdefs = pp.getGlyphs() + for j in xrange(0,len(gdefs)): + mlst.append(gdefs[j]) + mlst.append('\n') + img = pp.getImages() + if (img != None): + for j in xrange(0,len(img)): + mlst.append(img[j]) + if (pp.gid != None): + for j in xrange(0,len(pp.gid)): + mlst.append('\n' % (pp.gid[j], pp.gx[j], pp.gy[j])) + if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0): + xpos = "%d" % (pp.pw // 3) + ypos = "%d" % (pp.ph // 3) + mlst.append('This page intentionally left blank.\n') + if (raw) : + mlst.append('') + else : + mlst.append('\n') + if nextid == None: + mlst.append('\n') + else : + mlst.append('\n') + mlst.append('
\n') + mlst.append('\n') + mlst.append('\n') + mlst.append('\n') + return "".join(mlst) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py b/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py index e5647f4..9733887 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/genbook.py @@ -1,147 +1,148 @@ #! /usr/bin/python # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab -# For use with Topaz Scripts Version 2.6 + +class Unbuffered: + def __init__(self, stream): + self.stream = stream + def write(self, data): + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) import sys +sys.stdout=Unbuffered(sys.stdout) + import csv import os -import math import getopt from struct import pack from struct import unpack +class TpzDRMError(Exception): + pass -class DocParser(object): - def __init__(self, flatxml, classlst, fileid, bookDir, gdict, fixedimage): - self.id = os.path.basename(fileid).replace('.dat','') - self.svgcount = 0 - self.docList = flatxml.split('\n') - self.docSize = len(self.docList) - self.classList = {} - self.bookDir = bookDir - self.gdict = gdict - tmpList = classlst.split('\n') - for pclass in tmpList: - if pclass != '': - # remove the leading period from the css name - cname = pclass[1:] - self.classList[cname] = True - self.fixedimage = fixedimage - self.ocrtext = [] - self.link_id = [] - self.link_title = [] - self.link_page = [] - self.link_href = [] - self.link_type = [] - self.dehyphen_rootid = [] - self.paracont_stemid = [] - self.parastems_stemid = [] +# local support routines +if 'calibre' in sys.modules: + inCalibre = True +else: + inCalibre = False + +if inCalibre : + from calibre_plugins.k4mobidedrm import convert2xml + from calibre_plugins.k4mobidedrm import flatxml2html + from calibre_plugins.k4mobidedrm import flatxml2svg + from calibre_plugins.k4mobidedrm import stylexml2css +else : + import convert2xml + import flatxml2html + import flatxml2svg + import stylexml2css + +# global switch +buildXML = False + +# Get a 7 bit encoded number from a file +def readEncodedNumber(file): + flag = False + c = file.read(1) + if (len(c) == 0): + return None + data = ord(c) + if data == 0xFF: + flag = True + c = file.read(1) + if (len(c) == 0): + return None + data = ord(c) + if data >= 0x80: + datax = (data & 0x7F) + while data >= 0x80 : + c = file.read(1) + if (len(c) == 0): + return None + data = ord(c) + datax = (datax <<7) + (data & 0x7F) + data = datax + if flag: + data = -data + return data + +# Get a length prefixed string from the file +def lengthPrefixString(data): + return encodeNumber(len(data))+data + +def readString(file): + stringLength = readEncodedNumber(file) + if (stringLength == None): + return None + sv = file.read(stringLength) + if (len(sv) != stringLength): + return "" + return unpack(str(stringLength)+"s",sv)[0] + +def getMetaArray(metaFile): + # parse the meta file + result = {} + fo = file(metaFile,'rb') + size = readEncodedNumber(fo) + for i in xrange(size): + tag = readString(fo) + value = readString(fo) + result[tag] = value + # print tag, value + fo.close() + return result - def getGlyph(self, gid): - result = '' - id='id="gl%d"' % gid - return self.gdict.lookup(id) - - def glyphs_to_image(self, glyphList): - - def extract(path, key): - b = path.find(key) + len(key) - e = path.find(' ',b) - return int(path[b:e]) - - svgDir = os.path.join(self.bookDir,'svg') - - imgDir = os.path.join(self.bookDir,'img') - imgname = self.id + '_%04d.svg' % self.svgcount - imgfile = os.path.join(imgDir,imgname) - - # get glyph information - gxList = self.getData('info.glyph.x',0,-1) - gyList = self.getData('info.glyph.y',0,-1) - gidList = self.getData('info.glyph.glyphID',0,-1) - - gids = [] - maxws = [] - maxhs = [] - xs = [] - ys = [] - gdefs = [] - - # get path defintions, positions, dimensions for each glyph - # that makes up the image, and find min x and min y to reposition origin - minx = -1 - miny = -1 - for j in glyphList: - gid = gidList[j] - gids.append(gid) - - xs.append(gxList[j]) - if minx == -1: minx = gxList[j] - else : minx = min(minx, gxList[j]) - - ys.append(gyList[j]) - if miny == -1: miny = gyList[j] - else : miny = min(miny, gyList[j]) - - path = self.getGlyph(gid) - gdefs.append(path) - - maxws.append(extract(path,'width=')) - maxhs.append(extract(path,'height=')) +# dictionary of all text strings by index value +class Dictionary(object): + def __init__(self, dictFile): + self.filename = dictFile + self.size = 0 + self.fo = file(dictFile,'rb') + self.stable = [] + self.size = readEncodedNumber(self.fo) + for i in xrange(self.size): + self.stable.append(self.escapestr(readString(self.fo))) + self.pos = 0 + def escapestr(self, str): + str = str.replace('&','&') + str = str.replace('<','<') + str = str.replace('>','>') + str = str.replace('=','=') + return str + def lookup(self,val): + if ((val >= 0) and (val < self.size)) : + self.pos = val + return self.stable[self.pos] + else: + print "Error - %d outside of string table limits" % val + raise TpzDRMError('outside or string table limits') + # sys.exit(-1) + def getSize(self): + return self.size + def getPos(self): + return self.pos - # change the origin to minx, miny and calc max height and width - maxw = maxws[0] + xs[0] - minx - maxh = maxhs[0] + ys[0] - miny - for j in xrange(0, len(xs)): - xs[j] = xs[j] - minx - ys[j] = ys[j] - miny - maxw = max( maxw, (maxws[j] + xs[j]) ) - maxh = max( maxh, (maxhs[j] + ys[j]) ) - - # open the image file for output - ifile = open(imgfile,'w') - ifile.write('\n') - ifile.write('\n') - ifile.write('\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh)) - ifile.write('\n') - for j in xrange(0,len(gdefs)): - ifile.write(gdefs[j]) - ifile.write('\n') - for j in xrange(0,len(gids)): - ifile.write('\n' % (gids[j], xs[j], ys[j])) - ifile.write('') - ifile.close() - - return 0 - - - - # return tag at line pos in document - def lineinDoc(self, pos) : - if (pos >= 0) and (pos < self.docSize) : - item = self.docList[pos] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - return name, argres - - - # find tag in doc if within pos to end inclusive +class PageDimParser(object): + def __init__(self, flatxml): + self.flatdoc = flatxml.split('\n') + # find tag if within pos to end inclusive def findinDoc(self, tagpath, pos, end) : result = None + docList = self.flatdoc + cnt = len(docList) if end == -1 : - end = self.docSize + end = cnt else: - end = min(self.docSize, end) + end = min(cnt,end) foundat = -1 for j in xrange(pos, end): - item = self.docList[j] + item = docList[j] if item.find('=') >= 0: - (name, argres) = item.split('=',1) + (name, argres) = item.split('=') else : name = item argres = '' @@ -150,644 +151,571 @@ class DocParser(object): foundat = j break return foundat, result - - - # return list of start positions for the tagpath - def posinDoc(self, tagpath): - startpos = [] - pos = 0 - res = "" - while res != None : - (foundpos, res) = self.findinDoc(tagpath, pos, -1) - if res != None : - startpos.append(foundpos) - pos = foundpos + 1 - return startpos - - - # returns a vector of integers for the tagpath - def getData(self, tagpath, pos, end): - argres=[] - (foundat, argt) = self.findinDoc(tagpath, pos, end) - if (argt != None) and (len(argt) > 0) : - argList = argt.split('|') - argres = [ int(strval) for strval in argList] - return argres - - - # get the class - def getClass(self, pclass): - nclass = pclass - - # class names are an issue given topaz may start them with numerals (not allowed), - # use a mix of cases (which cause some browsers problems), and actually - # attach numbers after "_reclustered*" to the end to deal classeses that inherit - # from a base class (but then not actually provide all of these _reclustereed - # classes in the stylesheet! - - # so we clean this up by lowercasing, prepend 'cl-', and getting any baseclass - # that exists in the stylesheet first, and then adding this specific class - # after - - # also some class names have spaces in them so need to convert to dashes - if nclass != None : - nclass = nclass.replace(' ','-') - classres = '' - nclass = nclass.lower() - nclass = 'cl-' + nclass - baseclass = '' - # graphic is the base class for captions - if nclass.find('cl-cap-') >=0 : - classres = 'graphic' + ' ' - else : - # strip to find baseclass - p = nclass.find('_') - if p > 0 : - baseclass = nclass[0:p] - if baseclass in self.classList: - classres += baseclass + ' ' - classres += nclass - nclass = classres - return nclass - - - # develop a sorted description of the starting positions of - # groups and regions on the page, as well as the page type - def PageDescription(self): - - def compare(x, y): - (xtype, xval) = x - (ytype, yval) = y - if xval > yval: - return 1 - if xval == yval: - return 0 - return -1 - - result = [] - (pos, pagetype) = self.findinDoc('page.type',0,-1) - - groupList = self.posinDoc('page.group') - groupregionList = self.posinDoc('page.group.region') - pageregionList = self.posinDoc('page.region') - # integrate into one list - for j in groupList: - result.append(('grpbeg',j)) - for j in groupregionList: - result.append(('gregion',j)) - for j in pageregionList: - result.append(('pregion',j)) - result.sort(compare) - - # insert group end and page end indicators - inGroup = False - j = 0 - while True: - if j == len(result): break - rtype = result[j][0] - rval = result[j][1] - if not inGroup and (rtype == 'grpbeg') : - inGroup = True - j = j + 1 - elif inGroup and (rtype in ('grpbeg', 'pregion')): - result.insert(j,('grpend',rval)) - inGroup = False - else: - j = j + 1 - if inGroup: - result.append(('grpend',-1)) - result.append(('pageend', -1)) - return pagetype, result - - - - # build a description of the paragraph - def getParaDescription(self, start, end, regtype): - - result = [] - - # paragraph - (pos, pclass) = self.findinDoc('paragraph.class',start,end) - - pclass = self.getClass(pclass) - - # if paragraph uses extratokens (extra glyphs) then make it fixed - (pos, extraglyphs) = self.findinDoc('paragraph.extratokens',start,end) - - # build up a description of the paragraph in result and return it - # first check for the basic - all words paragraph - (pos, sfirst) = self.findinDoc('paragraph.firstWord',start,end) - (pos, slast) = self.findinDoc('paragraph.lastWord',start,end) - if (sfirst != None) and (slast != None) : - first = int(sfirst) - last = int(slast) - - makeImage = (regtype == 'vertical') or (regtype == 'table') - makeImage = makeImage or (extraglyphs != None) - if self.fixedimage: - makeImage = makeImage or (regtype == 'fixed') - - if (pclass != None): - makeImage = makeImage or (pclass.find('.inverted') >= 0) - if self.fixedimage : - makeImage = makeImage or (pclass.find('cl-f-') >= 0) - - # before creating an image make sure glyph info exists - gidList = self.getData('info.glyph.glyphID',0,-1) - - makeImage = makeImage & (len(gidList) > 0) - - if not makeImage : - # standard all word paragraph - for wordnum in xrange(first, last): - result.append(('ocr', wordnum)) - return pclass, result - - # convert paragraph to svg image - # translate first and last word into first and last glyphs - # and generate inline image and include it - glyphList = [] - firstglyphList = self.getData('word.firstGlyph',0,-1) - gidList = self.getData('info.glyph.glyphID',0,-1) - firstGlyph = firstglyphList[first] - if last < len(firstglyphList): - lastGlyph = firstglyphList[last] - else : - lastGlyph = len(gidList) - - # handle case of white sapce paragraphs with no actual glyphs in them - # by reverting to text based paragraph - if firstGlyph >= lastGlyph: - # revert to standard text based paragraph - for wordnum in xrange(first, last): - result.append(('ocr', wordnum)) - return pclass, result - - for glyphnum in xrange(firstGlyph, lastGlyph): - glyphList.append(glyphnum) - # include any extratokens if they exist - (pos, sfg) = self.findinDoc('extratokens.firstGlyph',start,end) - (pos, slg) = self.findinDoc('extratokens.lastGlyph',start,end) - if (sfg != None) and (slg != None): - for glyphnum in xrange(int(sfg), int(slg)): - glyphList.append(glyphnum) - num = self.svgcount - self.glyphs_to_image(glyphList) - self.svgcount += 1 - result.append(('svg', num)) - return pclass, result - - # this type of paragraph may be made up of multiple spans, inline - # word monograms (images), and words with semantic meaning, - # plus glyphs used to form starting letter of first word - - # need to parse this type line by line - line = start + 1 - word_class = '' - - # if end is -1 then we must search to end of document - if end == -1 : - end = self.docSize - - # seems some xml has last* coming before first* so we have to - # handle any order - sp_first = -1 - sp_last = -1 - - gl_first = -1 - gl_last = -1 - - ws_first = -1 - ws_last = -1 - - word_class = '' - - word_semantic_type = '' - - while (line < end) : - - (name, argres) = self.lineinDoc(line) - - if name.endswith('span.firstWord') : - sp_first = int(argres) - - elif name.endswith('span.lastWord') : - sp_last = int(argres) - - elif name.endswith('word.firstGlyph') : - gl_first = int(argres) - - elif name.endswith('word.lastGlyph') : - gl_last = int(argres) - - elif name.endswith('word_semantic.firstWord'): - ws_first = int(argres) - - elif name.endswith('word_semantic.lastWord'): - ws_last = int(argres) - - elif name.endswith('word.class'): - (cname, space) = argres.split('-',1) - if space == '' : space = '0' - if (cname == 'spaceafter') and (int(space) > 0) : - word_class = 'sa' - - elif name.endswith('word.img.src'): - result.append(('img' + word_class, int(argres))) - word_class = '' - - elif name.endswith('region.img.src'): - result.append(('img' + word_class, int(argres))) - - if (sp_first != -1) and (sp_last != -1): - for wordnum in xrange(sp_first, sp_last): - result.append(('ocr', wordnum)) - sp_first = -1 - sp_last = -1 - - if (gl_first != -1) and (gl_last != -1): - glyphList = [] - for glyphnum in xrange(gl_first, gl_last): - glyphList.append(glyphnum) - num = self.svgcount - self.glyphs_to_image(glyphList) - self.svgcount += 1 - result.append(('svg', num)) - gl_first = -1 - gl_last = -1 - - if (ws_first != -1) and (ws_last != -1): - for wordnum in xrange(ws_first, ws_last): - result.append(('ocr', wordnum)) - ws_first = -1 - ws_last = -1 - - line += 1 - - return pclass, result - - - def buildParagraph(self, pclass, pdesc, type, regtype) : - parares = '' - sep ='' - - classres = '' - if pclass : - classres = ' class="' + pclass + '"' - - br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical') - - handle_links = len(self.link_id) > 0 - - if (type == 'full') or (type == 'begin') : - parares += '' - - if (type == 'end'): - parares += ' ' - - lstart = len(parares) - - cnt = len(pdesc) - - for j in xrange( 0, cnt) : - - (wtype, num) = pdesc[j] - - if wtype == 'ocr' : - word = self.ocrtext[num] - sep = ' ' - - if handle_links: - link = self.link_id[num] - if (link > 0): - linktype = self.link_type[link-1] - title = self.link_title[link-1] - if (title == "") or (parares.rfind(title) < 0): - title=parares[lstart:] - if linktype == 'external' : - linkhref = self.link_href[link-1] - linkhtml = '' % linkhref - else : - if len(self.link_page) >= link : - ptarget = self.link_page[link-1] - 1 - linkhtml = '' % ptarget - else : - # just link to the current page - linkhtml = '' - linkhtml += title + '' - pos = parares.rfind(title) - if pos >= 0: - parares = parares[0:pos] + linkhtml + parares[pos+len(title):] - else : - parares += linkhtml - lstart = len(parares) - if word == '_link_' : word = '' - elif (link < 0) : - if word == '_link_' : word = '' - - if word == '_lb_': - if ((num-1) in self.dehyphen_rootid ) or handle_links: - word = '' - sep = '' - elif br_lb : - word = '
\n' - sep = '' - else : - word = '\n' - sep = '' - - if num in self.dehyphen_rootid : - word = word[0:-1] - sep = '' - - parares += word + sep - - elif wtype == 'img' : - sep = '' - parares += '' % num - parares += sep - - elif wtype == 'imgsa' : - sep = ' ' - parares += '' % num - parares += sep - - elif wtype == 'svg' : - sep = '' - parares += '' % num - parares += sep - - if len(sep) > 0 : parares = parares[0:-1] - if (type == 'full') or (type == 'end') : - parares += '

' - return parares - - - def buildTOCEntry(self, pdesc) : - parares = '' - sep ='' - tocentry = '' - handle_links = len(self.link_id) > 0 - - lstart = 0 - - cnt = len(pdesc) - for j in xrange( 0, cnt) : - - (wtype, num) = pdesc[j] - - if wtype == 'ocr' : - word = self.ocrtext[num] - sep = ' ' - - if handle_links: - link = self.link_id[num] - if (link > 0): - linktype = self.link_type[link-1] - title = self.link_title[link-1] - title = title.rstrip('. ') - alt_title = parares[lstart:] - alt_title = alt_title.strip() - # now strip off the actual printed page number - alt_title = alt_title.rstrip('01234567890ivxldIVXLD-.') - alt_title = alt_title.rstrip('. ') - # skip over any external links - can't have them in a books toc - if linktype == 'external' : - title = '' - alt_title = '' - linkpage = '' - else : - if len(self.link_page) >= link : - ptarget = self.link_page[link-1] - 1 - linkpage = '%04d' % ptarget - else : - # just link to the current page - linkpage = self.id[4:] - if len(alt_title) >= len(title): - title = alt_title - if title != '' and linkpage != '': - tocentry += title + '|' + linkpage + '\n' - lstart = len(parares) - if word == '_link_' : word = '' - elif (link < 0) : - if word == '_link_' : word = '' - - if word == '_lb_': - word = '' - sep = '' - - if num in self.dehyphen_rootid : - word = word[0:-1] - sep = '' - - parares += word + sep - - else : - continue - - return tocentry - - - - - # walk the document tree collecting the information needed - # to build an html page using the ocrText - def process(self): + (pos, sph) = self.findinDoc('page.h',0,-1) + (pos, spw) = self.findinDoc('page.w',0,-1) + if (sph == None): sph = '-1' + if (spw == None): spw = '-1' + return sph, spw - tocinfo = '' - hlst = [] - - # get the ocr text - (pos, argres) = self.findinDoc('info.word.ocrText',0,-1) - if argres : self.ocrtext = argres.split('|') - - # get information to dehyphenate the text - self.dehyphen_rootid = self.getData('info.dehyphen.rootID',0,-1) - - # determine if first paragraph is continued from previous page - (pos, self.parastems_stemid) = self.findinDoc('info.paraStems.stemID',0,-1) - first_para_continued = (self.parastems_stemid != None) - - # determine if last paragraph is continued onto the next page - (pos, self.paracont_stemid) = self.findinDoc('info.paraCont.stemID',0,-1) - last_para_continued = (self.paracont_stemid != None) - - # collect link ids - self.link_id = self.getData('info.word.link_id',0,-1) - - # collect link destination page numbers - self.link_page = self.getData('info.links.page',0,-1) - - # collect link types (container versus external) - (pos, argres) = self.findinDoc('info.links.type',0,-1) - if argres : self.link_type = argres.split('|') - - # collect link destinations - (pos, argres) = self.findinDoc('info.links.href',0,-1) - if argres : self.link_href = argres.split('|') - - # collect link titles - (pos, argres) = self.findinDoc('info.links.title',0,-1) - if argres : - self.link_title = argres.split('|') - else: - self.link_title.append('') - - # get a descriptions of the starting points of the regions - # and groups on the page - (pagetype, pageDesc) = self.PageDescription() - regcnt = len(pageDesc) - 1 - - anchorSet = False - breakSet = False - inGroup = False - - # process each region on the page and convert what you can to html - - for j in xrange(regcnt): - - (etype, start) = pageDesc[j] - (ntype, end) = pageDesc[j+1] - - - # set anchor for link target on this page - if not anchorSet and not first_para_continued: - hlst.append('\n') - anchorSet = True - - # handle groups of graphics with text captions - if (etype == 'grpbeg'): - (pos, grptype) = self.findinDoc('group.type', start, end) - if grptype != None: - if grptype == 'graphic': - gcstr = ' class="' + grptype + '"' - hlst.append('') - inGroup = True - - elif (etype == 'grpend'): - if inGroup: - hlst.append('\n') - inGroup = False - - else: - (pos, regtype) = self.findinDoc('region.type',start,end) - - if regtype == 'graphic' : - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - if inGroup: - hlst.append('' % int(simgsrc)) - else: - hlst.append('
' % int(simgsrc)) - - elif regtype == 'chapterheading' : - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if not breakSet: - hlst.append('
 
\n') - breakSet = True - tag = 'h1' - if pclass and (len(pclass) >= 7): - if pclass[3:7] == 'ch1-' : tag = 'h1' - if pclass[3:7] == 'ch2-' : tag = 'h2' - if pclass[3:7] == 'ch3-' : tag = 'h3' - hlst.append('<' + tag + ' class="' + pclass + '">') - else: - hlst.append('<' + tag + '>') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - - elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'): - ptype = 'full' - # check to see if this is a continution from the previous page - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if pclass and (len(pclass) >= 6) and (ptype == 'full'): - tag = 'p' - if pclass[3:6] == 'h1-' : tag = 'h4' - if pclass[3:6] == 'h2-' : tag = 'h5' - if pclass[3:6] == 'h3-' : tag = 'h6' - hlst.append('<' + tag + ' class="' + pclass + '">') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - else : - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - elif (regtype == 'tocentry') : - ptype = 'full' - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - tocinfo += self.buildTOCEntry(pdesc) - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - elif (regtype == 'vertical') or (regtype == 'table') : - ptype = 'full' - if inGroup: - ptype = 'middle' - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start, end, regtype) - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - - - elif (regtype == 'synth_fcvr.center'): - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - hlst.append('
' % int(simgsrc)) - - else : - print ' Making region type', regtype, - (pos, temp) = self.findinDoc('paragraph',start,end) - (pos2, temp) = self.findinDoc('span',start,end) - if pos != -1 or pos2 != -1: - print ' a "text" region' - orig_regtype = regtype - regtype = 'fixed' - ptype = 'full' - # check to see if this is a continution from the previous page - if first_para_continued : - ptype = 'end' - first_para_continued = False - (pclass, pdesc) = self.getParaDescription(start,end, regtype) - if not pclass: - if orig_regtype.endswith('.right') : pclass = 'cl-right' - elif orig_regtype.endswith('.center') : pclass = 'cl-center' - elif orig_regtype.endswith('.left') : pclass = 'cl-left' - elif orig_regtype.endswith('.justify') : pclass = 'cl-justify' - if pclass and (ptype == 'full') and (len(pclass) >= 6): - tag = 'p' - if pclass[3:6] == 'h1-' : tag = 'h4' - if pclass[3:6] == 'h2-' : tag = 'h5' - if pclass[3:6] == 'h3-' : tag = 'h6' - hlst.append('<' + tag + ' class="' + pclass + '">') - hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype)) - hlst.append('') - else : - hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype)) - else : - print ' a "graphic" region' - (pos, simgsrc) = self.findinDoc('img.src',start,end) - if simgsrc: - hlst.append('
' % int(simgsrc)) - - - htmlpage = "".join(hlst) - if last_para_continued : - if htmlpage[-4:] == '

': - htmlpage = htmlpage[0:-4] - last_para_continued = False - - return htmlpage, tocinfo - - -def convert2HTML(flatxml, classlst, fileid, bookDir, gdict, fixedimage): +def getPageDim(flatxml): # create a document parser - dp = DocParser(flatxml, classlst, fileid, bookDir, gdict, fixedimage) - htmlpage, tocinfo = dp.process() - return htmlpage, tocinfo + dp = PageDimParser(flatxml) + (ph, pw) = dp.process() + return ph, pw + +class GParser(object): + def __init__(self, flatxml): + self.flatdoc = flatxml.split('\n') + self.dpi = 1440 + self.gh = self.getData('info.glyph.h') + self.gw = self.getData('info.glyph.w') + self.guse = self.getData('info.glyph.use') + if self.guse : + self.count = len(self.guse) + else : + self.count = 0 + self.gvtx = self.getData('info.glyph.vtx') + self.glen = self.getData('info.glyph.len') + self.gdpi = self.getData('info.glyph.dpi') + self.vx = self.getData('info.vtx.x') + self.vy = self.getData('info.vtx.y') + self.vlen = self.getData('info.len.n') + if self.vlen : + self.glen.append(len(self.vlen)) + elif self.glen: + self.glen.append(0) + if self.vx : + self.gvtx.append(len(self.vx)) + elif self.gvtx : + self.gvtx.append(0) + def getData(self, path): + result = None + cnt = len(self.flatdoc) + for j in xrange(cnt): + item = self.flatdoc[j] + if item.find('=') >= 0: + (name, argt) = item.split('=') + argres = argt.split('|') + else: + name = item + argres = [] + if (name == path): + result = argres + break + if (len(argres) > 0) : + for j in xrange(0,len(argres)): + argres[j] = int(argres[j]) + return result + def getGlyphDim(self, gly): + if self.gdpi[gly] == 0: + return 0, 0 + maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly] + maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly] + return maxh, maxw + def getPath(self, gly): + path = '' + if (gly < 0) or (gly >= self.count): + return path + tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]] + ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]] + p = 0 + for k in xrange(self.glen[gly], self.glen[gly+1]): + if (p == 0): + zx = tx[0:self.vlen[k]+1] + zy = ty[0:self.vlen[k]+1] + else: + zx = tx[self.vlen[k-1]+1:self.vlen[k]+1] + zy = ty[self.vlen[k-1]+1:self.vlen[k]+1] + p += 1 + j = 0 + while ( j < len(zx) ): + if (j == 0): + # Start Position. + path += 'M %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly]) + elif (j <= len(zx)-3): + # Cubic Bezier Curve + path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[j+2] * self.dpi / self.gdpi[gly], zy[j+2] * self.dpi / self.gdpi[gly]) + j += 2 + elif (j == len(zx)-2): + # Cubic Bezier Curve to Start Position + path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly]) + j += 1 + elif (j == len(zx)-1): + # Quadratic Bezier Curve to Start Position + path += 'Q %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly]) + + j += 1 + path += 'z' + return path + + + +# dictionary of all text strings by index value +class GlyphDict(object): + def __init__(self): + self.gdict = {} + def lookup(self, id): + # id='id="gl%d"' % val + if id in self.gdict: + return self.gdict[id] + return None + def addGlyph(self, val, path): + id='id="gl%d"' % val + self.gdict[id] = path + + +def generateBook(bookDir, raw, fixedimage): + # sanity check Topaz file extraction + if not os.path.exists(bookDir) : + print "Can not find directory with unencrypted book" + return 1 + + dictFile = os.path.join(bookDir,'dict0000.dat') + if not os.path.exists(dictFile) : + print "Can not find dict0000.dat file" + return 1 + + pageDir = os.path.join(bookDir,'page') + if not os.path.exists(pageDir) : + print "Can not find page directory in unencrypted book" + return 1 + + imgDir = os.path.join(bookDir,'img') + if not os.path.exists(imgDir) : + print "Can not find image directory in unencrypted book" + return 1 + + glyphsDir = os.path.join(bookDir,'glyphs') + if not os.path.exists(glyphsDir) : + print "Can not find glyphs directory in unencrypted book" + return 1 + + metaFile = os.path.join(bookDir,'metadata0000.dat') + if not os.path.exists(metaFile) : + print "Can not find metadata0000.dat in unencrypted book" + return 1 + + svgDir = os.path.join(bookDir,'svg') + if not os.path.exists(svgDir) : + os.makedirs(svgDir) + + if buildXML: + xmlDir = os.path.join(bookDir,'xml') + if not os.path.exists(xmlDir) : + os.makedirs(xmlDir) + + otherFile = os.path.join(bookDir,'other0000.dat') + if not os.path.exists(otherFile) : + print "Can not find other0000.dat in unencrypted book" + return 1 + + print "Updating to color images if available" + spath = os.path.join(bookDir,'color_img') + dpath = os.path.join(bookDir,'img') + filenames = os.listdir(spath) + filenames = sorted(filenames) + for filename in filenames: + imgname = filename.replace('color','img') + sfile = os.path.join(spath,filename) + dfile = os.path.join(dpath,imgname) + imgdata = file(sfile,'rb').read() + file(dfile,'wb').write(imgdata) + + print "Creating cover.jpg" + isCover = False + cpath = os.path.join(bookDir,'img') + cpath = os.path.join(cpath,'img0000.jpg') + if os.path.isfile(cpath): + cover = file(cpath, 'rb').read() + cpath = os.path.join(bookDir,'cover.jpg') + file(cpath, 'wb').write(cover) + isCover = True + + + print 'Processing Dictionary' + dict = Dictionary(dictFile) + + print 'Processing Meta Data and creating OPF' + meta_array = getMetaArray(metaFile) + + # replace special chars in title and authors like & < > + title = meta_array.get('Title','No Title Provided') + title = title.replace('&','&') + title = title.replace('<','<') + title = title.replace('>','>') + meta_array['Title'] = title + authors = meta_array.get('Authors','No Authors Provided') + authors = authors.replace('&','&') + authors = authors.replace('<','<') + authors = authors.replace('>','>') + meta_array['Authors'] = authors + + if buildXML: + xname = os.path.join(xmlDir, 'metadata.xml') + mlst = [] + for key in meta_array: + mlst.append('\n') + metastr = "".join(mlst) + mlst = None + file(xname, 'wb').write(metastr) + + print 'Processing StyleSheet' + + # get some scaling info from metadata to use while processing styles + # and first page info + + fontsize = '135' + if 'fontSize' in meta_array: + fontsize = meta_array['fontSize'] + + # also get the size of a normal text page + # get the total number of pages unpacked as a safety check + filenames = os.listdir(pageDir) + numfiles = len(filenames) + + spage = '1' + if 'firstTextPage' in meta_array: + spage = meta_array['firstTextPage'] + pnum = int(spage) + if pnum >= numfiles or pnum < 0: + # metadata is wrong so just select a page near the front + # 10% of the book to get a normal text page + pnum = int(0.10 * numfiles) + # print "first normal text page is", spage + + # get page height and width from first text page for use in stylesheet scaling + pname = 'page%04d.dat' % (pnum + 1) + fname = os.path.join(pageDir,pname) + flat_xml = convert2xml.fromData(dict, fname) + + (ph, pw) = getPageDim(flat_xml) + if (ph == '-1') or (ph == '0') : ph = '11000' + if (pw == '-1') or (pw == '0') : pw = '8500' + meta_array['pageHeight'] = ph + meta_array['pageWidth'] = pw + if 'fontSize' not in meta_array.keys(): + meta_array['fontSize'] = fontsize + + # process other.dat for css info and for map of page files to svg images + # this map is needed because some pages actually are made up of multiple + # pageXXXX.xml files + xname = os.path.join(bookDir, 'style.css') + flat_xml = convert2xml.fromData(dict, otherFile) + + # extract info.original.pid to get original page information + pageIDMap = {} + pageidnums = stylexml2css.getpageIDMap(flat_xml) + if len(pageidnums) == 0: + filenames = os.listdir(pageDir) + numfiles = len(filenames) + for k in range(numfiles): + pageidnums.append(k) + # create a map from page ids to list of page file nums to process for that page + for i in range(len(pageidnums)): + id = pageidnums[i] + if id in pageIDMap.keys(): + pageIDMap[id].append(i) + else: + pageIDMap[id] = [i] + + # now get the css info + cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw) + file(xname, 'wb').write(cssstr) + if buildXML: + xname = os.path.join(xmlDir, 'other0000.xml') + file(xname, 'wb').write(convert2xml.getXML(dict, otherFile)) + + print 'Processing Glyphs' + gd = GlyphDict() + filenames = os.listdir(glyphsDir) + filenames = sorted(filenames) + glyfname = os.path.join(svgDir,'glyphs.svg') + glyfile = open(glyfname, 'w') + glyfile.write('\n') + glyfile.write('\n') + glyfile.write('\n') + glyfile.write('Glyphs for %s\n' % meta_array['Title']) + glyfile.write('\n') + counter = 0 + for filename in filenames: + # print ' ', filename + print '.', + fname = os.path.join(glyphsDir,filename) + flat_xml = convert2xml.fromData(dict, fname) + + if buildXML: + xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) + file(xname, 'wb').write(convert2xml.getXML(dict, fname)) + + gp = GParser(flat_xml) + for i in xrange(0, gp.count): + path = gp.getPath(i) + maxh, maxw = gp.getGlyphDim(i) + fullpath = '\n' % (counter * 256 + i, path, maxw, maxh) + glyfile.write(fullpath) + gd.addGlyph(counter * 256 + i, fullpath) + counter += 1 + glyfile.write('\n') + glyfile.write('\n') + glyfile.close() + print " " + + + # start up the html + # also build up tocentries while processing html + htmlFileName = "book.html" + hlst = [] + hlst.append('\n') + hlst.append('\n') + hlst.append('\n') + hlst.append('\n') + hlst.append('\n') + hlst.append('' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '\n') + hlst.append('\n') + hlst.append('\n') + if 'ASIN' in meta_array: + hlst.append('\n') + if 'GUID' in meta_array: + hlst.append('\n') + hlst.append('\n') + hlst.append('\n\n') + + print 'Processing Pages' + # Books are at 1440 DPI. This is rendering at twice that size for + # readability when rendering to the screen. + scaledpi = 1440.0 + + filenames = os.listdir(pageDir) + filenames = sorted(filenames) + numfiles = len(filenames) + + xmllst = [] + elst = [] + + for filename in filenames: + # print ' ', filename + print ".", + fname = os.path.join(pageDir,filename) + flat_xml = convert2xml.fromData(dict, fname) + + # keep flat_xml for later svg processing + xmllst.append(flat_xml) + + if buildXML: + xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) + file(xname, 'wb').write(convert2xml.getXML(dict, fname)) + + # first get the html + pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage) + elst.append(tocinfo) + hlst.append(pagehtml) + + # finish up the html string and output it + hlst.append('\n\n') + htmlstr = "".join(hlst) + hlst = None + file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr) + + print " " + print 'Extracting Table of Contents from Amazon OCR' + + # first create a table of contents file for the svg images + tlst = [] + tlst.append('\n') + tlst.append('\n') + tlst.append('') + tlst.append('\n') + tlst.append('' + meta_array['Title'] + '\n') + tlst.append('\n') + tlst.append('\n') + if 'ASIN' in meta_array: + tlst.append('\n') + if 'GUID' in meta_array: + tlst.append('\n') + tlst.append('\n') + tlst.append('\n') + + tlst.append('

Table of Contents

\n') + start = pageidnums[0] + if (raw): + startname = 'page%04d.svg' % start + else: + startname = 'page%04d.xhtml' % start + + tlst.append('

Start of Book

\n') + # build up a table of contents for the svg xhtml output + tocentries = "".join(elst) + elst = None + toclst = tocentries.split('\n') + toclst.pop() + for entry in toclst: + print entry + title, pagenum = entry.split('|') + id = pageidnums[int(pagenum)] + if (raw): + fname = 'page%04d.svg' % id + else: + fname = 'page%04d.xhtml' % id + tlst.append('

' + title + '

\n') + tlst.append('\n') + tlst.append('\n') + tochtml = "".join(tlst) + file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml) + + + # now create index_svg.xhtml that points to all required files + slst = [] + slst.append('\n') + slst.append('\n') + slst.append('') + slst.append('\n') + slst.append('' + meta_array['Title'] + '\n') + slst.append('\n') + slst.append('\n') + if 'ASIN' in meta_array: + slst.append('\n') + if 'GUID' in meta_array: + slst.append('\n') + slst.append('\n') + slst.append('\n') + + print "Building svg images of each book page" + slst.append('

List of Pages

\n') + slst.append('
\n') + idlst = sorted(pageIDMap.keys()) + numids = len(idlst) + cnt = len(idlst) + previd = None + for j in range(cnt): + pageid = idlst[j] + if j < cnt - 1: + nextid = idlst[j+1] + else: + nextid = None + print '.', + pagelst = pageIDMap[pageid] + flst = [] + for page in pagelst: + flst.append(xmllst[page]) + flat_svg = "".join(flst) + flst=None + svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi) + if (raw) : + pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w') + slst.append('Page %d\n' % (pageid, pageid)) + else : + pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w') + slst.append('Page %d\n' % (pageid, pageid)) + previd = pageid + pfile.write(svgxml) + pfile.close() + counter += 1 + slst.append('
\n') + slst.append('

Table of Contents

\n') + slst.append('\n\n') + svgindex = "".join(slst) + slst = None + file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex) + + print " " + + # build the opf file + opfname = os.path.join(bookDir, 'book.opf') + olst = [] + olst.append('\n') + olst.append('\n') + # adding metadata + olst.append(' \n') + if 'GUID' in meta_array: + olst.append(' ' + meta_array['GUID'] + '\n') + if 'ASIN' in meta_array: + olst.append(' ' + meta_array['ASIN'] + '\n') + if 'oASIN' in meta_array: + olst.append(' ' + meta_array['oASIN'] + '\n') + olst.append(' ' + meta_array['Title'] + '\n') + olst.append(' ' + meta_array['Authors'] + '\n') + olst.append(' en\n') + olst.append(' ' + meta_array['UpdateTime'] + '\n') + if isCover: + olst.append(' \n') + olst.append(' \n') + olst.append('\n') + olst.append(' \n') + olst.append(' \n') + # adding image files to manifest + filenames = os.listdir(imgDir) + filenames = sorted(filenames) + for filename in filenames: + imgname, imgext = os.path.splitext(filename) + if imgext == '.jpg': + imgext = 'jpeg' + if imgext == '.svg': + imgext = 'svg+xml' + olst.append(' \n') + if isCover: + olst.append(' \n') + olst.append('\n') + # adding spine + olst.append('\n \n\n') + if isCover: + olst.append(' \n') + olst.append(' \n') + olst.append(' \n') + olst.append('\n') + opfstr = "".join(olst) + olst = None + file(opfname, 'wb').write(opfstr) + + print 'Processing Complete' + + return 0 + +def usage(): + print "genbook.py generates a book from the extract Topaz Files" + print "Usage:" + print " genbook.py [-r] [-h [--fixed-image] " + print " " + print "Options:" + print " -h : help - print this usage message" + print " -r : generate raw svg files (not wrapped in xhtml)" + print " --fixed-image : genearate any Fixed Area as an svg image in the html" + print " " + + +def main(argv): + bookDir = '' + if len(argv) == 0: + argv = sys.argv + + try: + opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"]) + + except getopt.GetoptError, err: + print str(err) + usage() + return 1 + + if len(opts) == 0 and len(args) == 0 : + usage() + return 1 + + raw = 0 + fixedimage = True + for o, a in opts: + if o =="-h": + usage() + return 0 + if o =="-r": + raw = 1 + if o =="--fixed-image": + fixedimage = True + + bookDir = args[0] + + rv = generateBook(bookDir, raw, fixedimage) + return rv + + +if __name__ == '__main__': + sys.exit(main('')) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/getk4pcpids.py b/Calibre_Plugins/K4MobiDeDRM_plugin/getk4pcpids.py index 4dfd6c7..cc8bcd4 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/getk4pcpids.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/getk4pcpids.py @@ -1,249 +1,78 @@ -#! /usr/bin/python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab +#!/usr/bin/python +# +# This is a python script. You need a Python interpreter to run it. +# For example, ActiveState Python, which exists for windows. +# +# Changelog +# 1.00 - Initial version +# 1.01 - getPidList interface change + +__version__ = '1.01' import sys -import csv + +class Unbuffered: + def __init__(self, stream): + self.stream = stream + def write(self, data): + self.stream.write(data) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) +sys.stdout=Unbuffered(sys.stdout) + import os -import getopt -from struct import pack -from struct import unpack +import struct +import binascii +import kgenpids +import topazextract +import mobidedrm +from alfcrypto import Pukall_Cipher +class DrmException(Exception): + pass -class PParser(object): - def __init__(self, gd, flatxml, meta_array): - self.gd = gd - self.flatdoc = flatxml.split('\n') - self.docSize = len(self.flatdoc) - self.temp = [] +def getK4PCpids(path_to_ebook): + # Return Kindle4PC PIDs. Assumes that the caller checked that we are not on Linux, which will raise an exception - self.ph = -1 - self.pw = -1 - startpos = self.posinDoc('page.h') or self.posinDoc('book.h') - for p in startpos: - (name, argres) = self.lineinDoc(p) - self.ph = max(self.ph, int(argres)) - startpos = self.posinDoc('page.w') or self.posinDoc('book.w') - for p in startpos: - (name, argres) = self.lineinDoc(p) - self.pw = max(self.pw, int(argres)) + mobi = True + magic3 = file(path_to_ebook,'rb').read(3) + if magic3 == 'TPZ': + mobi = False - if self.ph <= 0: - self.ph = int(meta_array.get('pageHeight', '11000')) - if self.pw <= 0: - self.pw = int(meta_array.get('pageWidth', '8500')) - - res = [] - startpos = self.posinDoc('info.glyph.x') - for p in startpos: - argres = self.getDataatPos('info.glyph.x', p) - res.extend(argres) - self.gx = res - - res = [] - startpos = self.posinDoc('info.glyph.y') - for p in startpos: - argres = self.getDataatPos('info.glyph.y', p) - res.extend(argres) - self.gy = res - - res = [] - startpos = self.posinDoc('info.glyph.glyphID') - for p in startpos: - argres = self.getDataatPos('info.glyph.glyphID', p) - res.extend(argres) - self.gid = res - - - # return tag at line pos in document - def lineinDoc(self, pos) : - if (pos >= 0) and (pos < self.docSize) : - item = self.flatdoc[pos] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - return name, argres - - # find tag in doc if within pos to end inclusive - def findinDoc(self, tagpath, pos, end) : - result = None - if end == -1 : - end = self.docSize - else: - end = min(self.docSize, end) - foundat = -1 - for j in xrange(pos, end): - item = self.flatdoc[j] - if item.find('=') >= 0: - (name, argres) = item.split('=',1) - else : - name = item - argres = '' - if name.endswith(tagpath) : - result = argres - foundat = j - break - return foundat, result - - # return list of start positions for the tagpath - def posinDoc(self, tagpath): - startpos = [] - pos = 0 - res = "" - while res != None : - (foundpos, res) = self.findinDoc(tagpath, pos, -1) - if res != None : - startpos.append(foundpos) - pos = foundpos + 1 - return startpos - - def getData(self, path): - result = None - cnt = len(self.flatdoc) - for j in xrange(cnt): - item = self.flatdoc[j] - if item.find('=') >= 0: - (name, argt) = item.split('=') - argres = argt.split('|') - else: - name = item - argres = [] - if (name.endswith(path)): - result = argres - break - if (len(argres) > 0) : - for j in xrange(0,len(argres)): - argres[j] = int(argres[j]) - return result - - def getDataatPos(self, path, pos): - result = None - item = self.flatdoc[pos] - if item.find('=') >= 0: - (name, argt) = item.split('=') - argres = argt.split('|') - else: - name = item - argres = [] - if (len(argres) > 0) : - for j in xrange(0,len(argres)): - argres[j] = int(argres[j]) - if (name.endswith(path)): - result = argres - return result - - def getDataTemp(self, path): - result = None - cnt = len(self.temp) - for j in xrange(cnt): - item = self.temp[j] - if item.find('=') >= 0: - (name, argt) = item.split('=') - argres = argt.split('|') - else: - name = item - argres = [] - if (name.endswith(path)): - result = argres - self.temp.pop(j) - break - if (len(argres) > 0) : - for j in xrange(0,len(argres)): - argres[j] = int(argres[j]) - return result - - def getImages(self): - result = [] - self.temp = self.flatdoc - while (self.getDataTemp('img') != None): - h = self.getDataTemp('img.h')[0] - w = self.getDataTemp('img.w')[0] - x = self.getDataTemp('img.x')[0] - y = self.getDataTemp('img.y')[0] - src = self.getDataTemp('img.src')[0] - result.append('\n' % (src, x, y, w, h)) - return result - - def getGlyphs(self): - result = [] - if (self.gid != None) and (len(self.gid) > 0): - glyphs = [] - for j in set(self.gid): - glyphs.append(j) - glyphs.sort() - for gid in glyphs: - id='id="gl%d"' % gid - path = self.gd.lookup(id) - if path: - result.append(id + ' ' + path) - return result - - -def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi): - mlst = [] - pp = PParser(gdict, flat_xml, meta_array) - mlst.append('\n') - if (raw): - mlst.append('\n') - mlst.append('\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1)) - mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) + if mobi: + mb = mobidedrm.MobiBook(path_to_ebook,False) else: - mlst.append('\n') - mlst.append('\n') - mlst.append('Page %d - %s by %s\n' % (pageid, meta_array['Title'],meta_array['Authors'])) - mlst.append('\n') - mlst.append('\n') - mlst.append('\n') - mlst.append('
\n') - if previd == None: - mlst.append('\n') - else: - mlst.append('\n') + mb = topazextract.TopazBook(path_to_ebook) - mlst.append('' % (pp.pw, pp.ph)) - if (pp.gid != None): - mlst.append('\n') - gdefs = pp.getGlyphs() - for j in xrange(0,len(gdefs)): - mlst.append(gdefs[j]) - mlst.append('\n') - img = pp.getImages() - if (img != None): - for j in xrange(0,len(img)): - mlst.append(img[j]) - if (pp.gid != None): - for j in xrange(0,len(pp.gid)): - mlst.append('\n' % (pp.gid[j], pp.gx[j], pp.gy[j])) - if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0): - xpos = "%d" % (pp.pw // 3) - ypos = "%d" % (pp.ph // 3) - mlst.append('This page intentionally left blank.\n') - if (raw) : - mlst.append('') - else : - mlst.append('\n') - if nextid == None: - mlst.append('\n') - else : - mlst.append('\n') - mlst.append('
\n') - mlst.append('\n') - mlst.append('\n') - mlst.append('\n') - return "".join(mlst) + md1, md2 = mb.getPIDMetaInfo() + + return kgenpids.getPidList(md1, md2) + + +def main(argv=sys.argv): + print ('getk4pcpids.py v%(__version__)s. ' + 'Copyright 2012 Apprentice Alf' % globals()) + + if len(argv)<2 or len(argv)>3: + print "Gets the possible book-specific PIDs from K4PC for a particular book" + print "Usage:" + print " %s []" % sys.argv[0] + return 1 + else: + infile = argv[1] + try: + pidlist = getK4PCpids(infile) + except DrmException, e: + print "Error: %s" % e + return 1 + pidstring = ','.join(pidlist) + print "Possible PIDs are: ", pidstring + if len(argv) is 3: + outfile = argv[2] + file(outfile, 'w').write(pidstring) + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py b/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py deleted file mode 100644 index 9733887..0000000 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/k4mobidedrm_orig.py +++ /dev/null @@ -1,721 +0,0 @@ -#! /usr/bin/python -# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) - -import sys -sys.stdout=Unbuffered(sys.stdout) - -import csv -import os -import getopt -from struct import pack -from struct import unpack - -class TpzDRMError(Exception): - pass - -# local support routines -if 'calibre' in sys.modules: - inCalibre = True -else: - inCalibre = False - -if inCalibre : - from calibre_plugins.k4mobidedrm import convert2xml - from calibre_plugins.k4mobidedrm import flatxml2html - from calibre_plugins.k4mobidedrm import flatxml2svg - from calibre_plugins.k4mobidedrm import stylexml2css -else : - import convert2xml - import flatxml2html - import flatxml2svg - import stylexml2css - -# global switch -buildXML = False - -# Get a 7 bit encoded number from a file -def readEncodedNumber(file): - flag = False - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - if data == 0xFF: - flag = True - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - if data >= 0x80: - datax = (data & 0x7F) - while data >= 0x80 : - c = file.read(1) - if (len(c) == 0): - return None - data = ord(c) - datax = (datax <<7) + (data & 0x7F) - data = datax - if flag: - data = -data - return data - -# Get a length prefixed string from the file -def lengthPrefixString(data): - return encodeNumber(len(data))+data - -def readString(file): - stringLength = readEncodedNumber(file) - if (stringLength == None): - return None - sv = file.read(stringLength) - if (len(sv) != stringLength): - return "" - return unpack(str(stringLength)+"s",sv)[0] - -def getMetaArray(metaFile): - # parse the meta file - result = {} - fo = file(metaFile,'rb') - size = readEncodedNumber(fo) - for i in xrange(size): - tag = readString(fo) - value = readString(fo) - result[tag] = value - # print tag, value - fo.close() - return result - - -# dictionary of all text strings by index value -class Dictionary(object): - def __init__(self, dictFile): - self.filename = dictFile - self.size = 0 - self.fo = file(dictFile,'rb') - self.stable = [] - self.size = readEncodedNumber(self.fo) - for i in xrange(self.size): - self.stable.append(self.escapestr(readString(self.fo))) - self.pos = 0 - def escapestr(self, str): - str = str.replace('&','&') - str = str.replace('<','<') - str = str.replace('>','>') - str = str.replace('=','=') - return str - def lookup(self,val): - if ((val >= 0) and (val < self.size)) : - self.pos = val - return self.stable[self.pos] - else: - print "Error - %d outside of string table limits" % val - raise TpzDRMError('outside or string table limits') - # sys.exit(-1) - def getSize(self): - return self.size - def getPos(self): - return self.pos - - -class PageDimParser(object): - def __init__(self, flatxml): - self.flatdoc = flatxml.split('\n') - # find tag if within pos to end inclusive - def findinDoc(self, tagpath, pos, end) : - result = None - docList = self.flatdoc - cnt = len(docList) - if end == -1 : - end = cnt - else: - end = min(cnt,end) - foundat = -1 - for j in xrange(pos, end): - item = docList[j] - if item.find('=') >= 0: - (name, argres) = item.split('=') - else : - name = item - argres = '' - if name.endswith(tagpath) : - result = argres - foundat = j - break - return foundat, result - def process(self): - (pos, sph) = self.findinDoc('page.h',0,-1) - (pos, spw) = self.findinDoc('page.w',0,-1) - if (sph == None): sph = '-1' - if (spw == None): spw = '-1' - return sph, spw - -def getPageDim(flatxml): - # create a document parser - dp = PageDimParser(flatxml) - (ph, pw) = dp.process() - return ph, pw - -class GParser(object): - def __init__(self, flatxml): - self.flatdoc = flatxml.split('\n') - self.dpi = 1440 - self.gh = self.getData('info.glyph.h') - self.gw = self.getData('info.glyph.w') - self.guse = self.getData('info.glyph.use') - if self.guse : - self.count = len(self.guse) - else : - self.count = 0 - self.gvtx = self.getData('info.glyph.vtx') - self.glen = self.getData('info.glyph.len') - self.gdpi = self.getData('info.glyph.dpi') - self.vx = self.getData('info.vtx.x') - self.vy = self.getData('info.vtx.y') - self.vlen = self.getData('info.len.n') - if self.vlen : - self.glen.append(len(self.vlen)) - elif self.glen: - self.glen.append(0) - if self.vx : - self.gvtx.append(len(self.vx)) - elif self.gvtx : - self.gvtx.append(0) - def getData(self, path): - result = None - cnt = len(self.flatdoc) - for j in xrange(cnt): - item = self.flatdoc[j] - if item.find('=') >= 0: - (name, argt) = item.split('=') - argres = argt.split('|') - else: - name = item - argres = [] - if (name == path): - result = argres - break - if (len(argres) > 0) : - for j in xrange(0,len(argres)): - argres[j] = int(argres[j]) - return result - def getGlyphDim(self, gly): - if self.gdpi[gly] == 0: - return 0, 0 - maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly] - maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly] - return maxh, maxw - def getPath(self, gly): - path = '' - if (gly < 0) or (gly >= self.count): - return path - tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]] - ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]] - p = 0 - for k in xrange(self.glen[gly], self.glen[gly+1]): - if (p == 0): - zx = tx[0:self.vlen[k]+1] - zy = ty[0:self.vlen[k]+1] - else: - zx = tx[self.vlen[k-1]+1:self.vlen[k]+1] - zy = ty[self.vlen[k-1]+1:self.vlen[k]+1] - p += 1 - j = 0 - while ( j < len(zx) ): - if (j == 0): - # Start Position. - path += 'M %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly]) - elif (j <= len(zx)-3): - # Cubic Bezier Curve - path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[j+2] * self.dpi / self.gdpi[gly], zy[j+2] * self.dpi / self.gdpi[gly]) - j += 2 - elif (j == len(zx)-2): - # Cubic Bezier Curve to Start Position - path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly]) - j += 1 - elif (j == len(zx)-1): - # Quadratic Bezier Curve to Start Position - path += 'Q %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly]) - - j += 1 - path += 'z' - return path - - - -# dictionary of all text strings by index value -class GlyphDict(object): - def __init__(self): - self.gdict = {} - def lookup(self, id): - # id='id="gl%d"' % val - if id in self.gdict: - return self.gdict[id] - return None - def addGlyph(self, val, path): - id='id="gl%d"' % val - self.gdict[id] = path - - -def generateBook(bookDir, raw, fixedimage): - # sanity check Topaz file extraction - if not os.path.exists(bookDir) : - print "Can not find directory with unencrypted book" - return 1 - - dictFile = os.path.join(bookDir,'dict0000.dat') - if not os.path.exists(dictFile) : - print "Can not find dict0000.dat file" - return 1 - - pageDir = os.path.join(bookDir,'page') - if not os.path.exists(pageDir) : - print "Can not find page directory in unencrypted book" - return 1 - - imgDir = os.path.join(bookDir,'img') - if not os.path.exists(imgDir) : - print "Can not find image directory in unencrypted book" - return 1 - - glyphsDir = os.path.join(bookDir,'glyphs') - if not os.path.exists(glyphsDir) : - print "Can not find glyphs directory in unencrypted book" - return 1 - - metaFile = os.path.join(bookDir,'metadata0000.dat') - if not os.path.exists(metaFile) : - print "Can not find metadata0000.dat in unencrypted book" - return 1 - - svgDir = os.path.join(bookDir,'svg') - if not os.path.exists(svgDir) : - os.makedirs(svgDir) - - if buildXML: - xmlDir = os.path.join(bookDir,'xml') - if not os.path.exists(xmlDir) : - os.makedirs(xmlDir) - - otherFile = os.path.join(bookDir,'other0000.dat') - if not os.path.exists(otherFile) : - print "Can not find other0000.dat in unencrypted book" - return 1 - - print "Updating to color images if available" - spath = os.path.join(bookDir,'color_img') - dpath = os.path.join(bookDir,'img') - filenames = os.listdir(spath) - filenames = sorted(filenames) - for filename in filenames: - imgname = filename.replace('color','img') - sfile = os.path.join(spath,filename) - dfile = os.path.join(dpath,imgname) - imgdata = file(sfile,'rb').read() - file(dfile,'wb').write(imgdata) - - print "Creating cover.jpg" - isCover = False - cpath = os.path.join(bookDir,'img') - cpath = os.path.join(cpath,'img0000.jpg') - if os.path.isfile(cpath): - cover = file(cpath, 'rb').read() - cpath = os.path.join(bookDir,'cover.jpg') - file(cpath, 'wb').write(cover) - isCover = True - - - print 'Processing Dictionary' - dict = Dictionary(dictFile) - - print 'Processing Meta Data and creating OPF' - meta_array = getMetaArray(metaFile) - - # replace special chars in title and authors like & < > - title = meta_array.get('Title','No Title Provided') - title = title.replace('&','&') - title = title.replace('<','<') - title = title.replace('>','>') - meta_array['Title'] = title - authors = meta_array.get('Authors','No Authors Provided') - authors = authors.replace('&','&') - authors = authors.replace('<','<') - authors = authors.replace('>','>') - meta_array['Authors'] = authors - - if buildXML: - xname = os.path.join(xmlDir, 'metadata.xml') - mlst = [] - for key in meta_array: - mlst.append('\n') - metastr = "".join(mlst) - mlst = None - file(xname, 'wb').write(metastr) - - print 'Processing StyleSheet' - - # get some scaling info from metadata to use while processing styles - # and first page info - - fontsize = '135' - if 'fontSize' in meta_array: - fontsize = meta_array['fontSize'] - - # also get the size of a normal text page - # get the total number of pages unpacked as a safety check - filenames = os.listdir(pageDir) - numfiles = len(filenames) - - spage = '1' - if 'firstTextPage' in meta_array: - spage = meta_array['firstTextPage'] - pnum = int(spage) - if pnum >= numfiles or pnum < 0: - # metadata is wrong so just select a page near the front - # 10% of the book to get a normal text page - pnum = int(0.10 * numfiles) - # print "first normal text page is", spage - - # get page height and width from first text page for use in stylesheet scaling - pname = 'page%04d.dat' % (pnum + 1) - fname = os.path.join(pageDir,pname) - flat_xml = convert2xml.fromData(dict, fname) - - (ph, pw) = getPageDim(flat_xml) - if (ph == '-1') or (ph == '0') : ph = '11000' - if (pw == '-1') or (pw == '0') : pw = '8500' - meta_array['pageHeight'] = ph - meta_array['pageWidth'] = pw - if 'fontSize' not in meta_array.keys(): - meta_array['fontSize'] = fontsize - - # process other.dat for css info and for map of page files to svg images - # this map is needed because some pages actually are made up of multiple - # pageXXXX.xml files - xname = os.path.join(bookDir, 'style.css') - flat_xml = convert2xml.fromData(dict, otherFile) - - # extract info.original.pid to get original page information - pageIDMap = {} - pageidnums = stylexml2css.getpageIDMap(flat_xml) - if len(pageidnums) == 0: - filenames = os.listdir(pageDir) - numfiles = len(filenames) - for k in range(numfiles): - pageidnums.append(k) - # create a map from page ids to list of page file nums to process for that page - for i in range(len(pageidnums)): - id = pageidnums[i] - if id in pageIDMap.keys(): - pageIDMap[id].append(i) - else: - pageIDMap[id] = [i] - - # now get the css info - cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw) - file(xname, 'wb').write(cssstr) - if buildXML: - xname = os.path.join(xmlDir, 'other0000.xml') - file(xname, 'wb').write(convert2xml.getXML(dict, otherFile)) - - print 'Processing Glyphs' - gd = GlyphDict() - filenames = os.listdir(glyphsDir) - filenames = sorted(filenames) - glyfname = os.path.join(svgDir,'glyphs.svg') - glyfile = open(glyfname, 'w') - glyfile.write('\n') - glyfile.write('\n') - glyfile.write('\n') - glyfile.write('Glyphs for %s\n' % meta_array['Title']) - glyfile.write('\n') - counter = 0 - for filename in filenames: - # print ' ', filename - print '.', - fname = os.path.join(glyphsDir,filename) - flat_xml = convert2xml.fromData(dict, fname) - - if buildXML: - xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) - file(xname, 'wb').write(convert2xml.getXML(dict, fname)) - - gp = GParser(flat_xml) - for i in xrange(0, gp.count): - path = gp.getPath(i) - maxh, maxw = gp.getGlyphDim(i) - fullpath = '\n' % (counter * 256 + i, path, maxw, maxh) - glyfile.write(fullpath) - gd.addGlyph(counter * 256 + i, fullpath) - counter += 1 - glyfile.write('\n') - glyfile.write('\n') - glyfile.close() - print " " - - - # start up the html - # also build up tocentries while processing html - htmlFileName = "book.html" - hlst = [] - hlst.append('\n') - hlst.append('\n') - hlst.append('\n') - hlst.append('\n') - hlst.append('\n') - hlst.append('' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '\n') - hlst.append('\n') - hlst.append('\n') - if 'ASIN' in meta_array: - hlst.append('\n') - if 'GUID' in meta_array: - hlst.append('\n') - hlst.append('\n') - hlst.append('\n\n') - - print 'Processing Pages' - # Books are at 1440 DPI. This is rendering at twice that size for - # readability when rendering to the screen. - scaledpi = 1440.0 - - filenames = os.listdir(pageDir) - filenames = sorted(filenames) - numfiles = len(filenames) - - xmllst = [] - elst = [] - - for filename in filenames: - # print ' ', filename - print ".", - fname = os.path.join(pageDir,filename) - flat_xml = convert2xml.fromData(dict, fname) - - # keep flat_xml for later svg processing - xmllst.append(flat_xml) - - if buildXML: - xname = os.path.join(xmlDir, filename.replace('.dat','.xml')) - file(xname, 'wb').write(convert2xml.getXML(dict, fname)) - - # first get the html - pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage) - elst.append(tocinfo) - hlst.append(pagehtml) - - # finish up the html string and output it - hlst.append('\n\n') - htmlstr = "".join(hlst) - hlst = None - file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr) - - print " " - print 'Extracting Table of Contents from Amazon OCR' - - # first create a table of contents file for the svg images - tlst = [] - tlst.append('\n') - tlst.append('\n') - tlst.append('') - tlst.append('\n') - tlst.append('' + meta_array['Title'] + '\n') - tlst.append('\n') - tlst.append('\n') - if 'ASIN' in meta_array: - tlst.append('\n') - if 'GUID' in meta_array: - tlst.append('\n') - tlst.append('\n') - tlst.append('\n') - - tlst.append('

Table of Contents

\n') - start = pageidnums[0] - if (raw): - startname = 'page%04d.svg' % start - else: - startname = 'page%04d.xhtml' % start - - tlst.append('

Start of Book

\n') - # build up a table of contents for the svg xhtml output - tocentries = "".join(elst) - elst = None - toclst = tocentries.split('\n') - toclst.pop() - for entry in toclst: - print entry - title, pagenum = entry.split('|') - id = pageidnums[int(pagenum)] - if (raw): - fname = 'page%04d.svg' % id - else: - fname = 'page%04d.xhtml' % id - tlst.append('

' + title + '

\n') - tlst.append('\n') - tlst.append('\n') - tochtml = "".join(tlst) - file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml) - - - # now create index_svg.xhtml that points to all required files - slst = [] - slst.append('\n') - slst.append('\n') - slst.append('') - slst.append('\n') - slst.append('' + meta_array['Title'] + '\n') - slst.append('\n') - slst.append('\n') - if 'ASIN' in meta_array: - slst.append('\n') - if 'GUID' in meta_array: - slst.append('\n') - slst.append('\n') - slst.append('\n') - - print "Building svg images of each book page" - slst.append('

List of Pages

\n') - slst.append('
\n') - idlst = sorted(pageIDMap.keys()) - numids = len(idlst) - cnt = len(idlst) - previd = None - for j in range(cnt): - pageid = idlst[j] - if j < cnt - 1: - nextid = idlst[j+1] - else: - nextid = None - print '.', - pagelst = pageIDMap[pageid] - flst = [] - for page in pagelst: - flst.append(xmllst[page]) - flat_svg = "".join(flst) - flst=None - svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi) - if (raw) : - pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w') - slst.append('Page %d\n' % (pageid, pageid)) - else : - pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w') - slst.append('Page %d\n' % (pageid, pageid)) - previd = pageid - pfile.write(svgxml) - pfile.close() - counter += 1 - slst.append('
\n') - slst.append('

Table of Contents

\n') - slst.append('\n\n') - svgindex = "".join(slst) - slst = None - file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex) - - print " " - - # build the opf file - opfname = os.path.join(bookDir, 'book.opf') - olst = [] - olst.append('\n') - olst.append('\n') - # adding metadata - olst.append(' \n') - if 'GUID' in meta_array: - olst.append(' ' + meta_array['GUID'] + '\n') - if 'ASIN' in meta_array: - olst.append(' ' + meta_array['ASIN'] + '\n') - if 'oASIN' in meta_array: - olst.append(' ' + meta_array['oASIN'] + '\n') - olst.append(' ' + meta_array['Title'] + '\n') - olst.append(' ' + meta_array['Authors'] + '\n') - olst.append(' en\n') - olst.append(' ' + meta_array['UpdateTime'] + '\n') - if isCover: - olst.append(' \n') - olst.append(' \n') - olst.append('\n') - olst.append(' \n') - olst.append(' \n') - # adding image files to manifest - filenames = os.listdir(imgDir) - filenames = sorted(filenames) - for filename in filenames: - imgname, imgext = os.path.splitext(filename) - if imgext == '.jpg': - imgext = 'jpeg' - if imgext == '.svg': - imgext = 'svg+xml' - olst.append(' \n') - if isCover: - olst.append(' \n') - olst.append('\n') - # adding spine - olst.append('\n \n\n') - if isCover: - olst.append(' \n') - olst.append(' \n') - olst.append(' \n') - olst.append('\n') - opfstr = "".join(olst) - olst = None - file(opfname, 'wb').write(opfstr) - - print 'Processing Complete' - - return 0 - -def usage(): - print "genbook.py generates a book from the extract Topaz Files" - print "Usage:" - print " genbook.py [-r] [-h [--fixed-image] " - print " " - print "Options:" - print " -h : help - print this usage message" - print " -r : generate raw svg files (not wrapped in xhtml)" - print " --fixed-image : genearate any Fixed Area as an svg image in the html" - print " " - - -def main(argv): - bookDir = '' - if len(argv) == 0: - argv = sys.argv - - try: - opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"]) - - except getopt.GetoptError, err: - print str(err) - usage() - return 1 - - if len(opts) == 0 and len(args) == 0 : - usage() - return 1 - - raw = 0 - fixedimage = True - for o, a in opts: - if o =="-h": - usage() - return 0 - if o =="-r": - raw = 1 - if o =="--fixed-image": - fixedimage = True - - bookDir = args[0] - - rv = generateBook(bookDir, raw, fixedimage) - return rv - - -if __name__ == '__main__': - sys.exit(main('')) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/kgenpids.py b/Calibre_Plugins/K4MobiDeDRM_plugin/kgenpids.py index 813f8cee7eb399ddee5fe522813a953e0f77a81c..b0fbaa4ac221383a9b9f2f157e10270df4906644 100644 GIT binary patch literal 8091 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 literal 171 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aU~Fg_^W}KA;dKz1VrNojv*mItQOn3 zfhs^c<^VB2nrcRbxOj4Yu3ln6K~Ab(NNPoiYhH4GN@iX`)D7D@mB diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto.dylib b/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto.dylib index d491d7d8dd7e52c8fc98b1c3ad2b197e441521b9..01c348cc8a638e243754aea2cd2681ad30e629a4 100644 GIT binary patch literal 87160 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 literal 16182 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 literal 171 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aU~Fg_^W}KA;dKz1VrNojv*mItQOmO zfhs^c<^VB2nrcRbxOj4Yu3ln6K~Ab(NNPoiYhH4GN@iX`+37EAyD diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto64.so b/Calibre_Plugins/K4MobiDeDRM_plugin/libalfcrypto64.so index 40d84ad713c427faa5b25b18a9a4a2cdcf100a6e..a08ac28930fdd54e0eb4e8973a398e5e951d3825 100644 GIT binary patch literal 33417 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 Ko^Mvd(mwtG literal 8136 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 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/outputfix.py b/Calibre_Plugins/K4MobiDeDRM_plugin/outputfix.py new file mode 100644 index 0000000..906c6e9 --- /dev/null +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/outputfix.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# +# Adapted and simplified from the kitchen project +# +# Kitchen Project Copyright (c) 2012 Red Hat, Inc. +# +# kitchen is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# kitchen 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with kitchen; if not, see +# +# Authors: +# Toshio Kuratomi +# Seth Vidal +# +# Portions of code taken from yum/i18n.py and +# python-fedora: fedora/textutils.py + +import codecs + +# returns a char string unchanged +# returns a unicode string converted to a char string of the passed encoding +# return the empty string for anything else +def getwriter(encoding): + class _StreamWriter(codecs.StreamWriter): + def __init__(self, stream): + codecs.StreamWriter.__init__(self, stream, 'replace') + + def encode(self, msg, errors='replace'): + if isinstance(msg, basestring): + if isinstance(msg, str): + return (msg, len(msg)) + return (msg.encode(self.encoding, 'replace'), len(msg)) + return ('',0) + + _StreamWriter.encoding = encoding + return _StreamWriter diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/pbkdf2.py b/Calibre_Plugins/K4MobiDeDRM_plugin/pbkdf2.py index 9a5a442617a2046fb9b7050d339b18bca8993e7b..65220a950808997c8598098d76a14cfe610e229f 100644 GIT binary patch literal 2002 zcmah~VQZWnk_M!qFt7*9nfaToPHU$f|gDuXNnX^DvtW=cSlN@ z+O1e%L=t(td-v|$@d#dlS52!C;{OIs5J6c3wuW}Quj&`Duvel0#u-7r|!{%fIaq5JKNA}E4)8gSAj4U zc%efvBQICB?VZ}m0ISo}XYfvh0KZsi2!Vm;4S&9bt}H=#1EPEJ<&g{cNf?D1-dG(Z zJg+1_U)I8hhM*O7!$&q!c_?j#(1;$kNSF|HPuXI^LpiEi(FbQGOb>16TI(@lyv2Lv znI`fQtn10){b{iTxG96(qD#;L;0e4nDk!1Bp+?~`S*`%iDKl3}w)}w%$?~1pBgGe6 zs>VSPbSb>lY75H1uZj3oaTduUl*!R|0P{H?|LiYFq}-gtl9*RJ^x>mdpAej&9S1=Q zVoo1K?k%R+Rf$aQ%A5v5vqRVxF=b`yJHGycI*fnAe?{DDh=EL>vtDmo<7t%G!c*~F( z?UjMVQAjiF0@^Mt)@xinY7l?oGCFP&_qMx1)fe(ry|tj-nY{| z_~sH$6KxDi!^tHqXYqt&RggLhgNJD?-dlr@#OKL&L$>pIJjSy?u~MBdJIs~~&?n2G zG8|&VA=2FZ_&NjmAH`KLxOEshXJGUD>3Tr_$1E!3!8&S}48#JqW7$;=B#BJqoD;uo zCsCbH9^r$~U8+ttA6(>Xw%rBkiI%!XH_u}lL+In<;xDUAmX*?FG>5Z?{7-)Lc^;?U z!vbPkeo}45VDg-xM-96=CRd5@C`{QfrA0~8J4;byQ*8k;tg$jF!)=MCzX zR3JR?gXfoTu`EDxH;Gs(Qn;ddNn)mPjRs1GdA=uL z!*Dk?@Ff-p{Io5E4yftNYGy5A%5MoRK literal 23859 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 diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt b/Calibre_Plugins/K4MobiDeDRM_plugin/plugin-import-name-k4mobidedrm.txt index a08ac28930fdd54e0eb4e8973a398e5e951d3825..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 33417 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 Ko^Mvd(mwtG diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/scrolltextwidget.py b/Calibre_Plugins/K4MobiDeDRM_plugin/scrolltextwidget.py index 1ad2bac..98b4147 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/scrolltextwidget.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/scrolltextwidget.py @@ -1,460 +1,27 @@ -#!/usr/bin/python -# -# This is a python script. You need a Python interpreter to run it. -# For example, ActiveState Python, which exists for windows. -# -# Changelog -# 0.01 - Initial version -# 0.02 - Huffdic compressed books were not properly decrypted -# 0.03 - Wasn't checking MOBI header length -# 0.04 - Wasn't sanity checking size of data record -# 0.05 - It seems that the extra data flags take two bytes not four -# 0.06 - And that low bit does mean something after all :-) -# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size -# 0.08 - ...and also not in Mobi header version < 6 -# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4! -# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre -# import filter it works when importing unencrypted files. -# Also now handles encrypted files that don't need a specific PID. -# 0.11 - use autoflushed stdout and proper return values -# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors -# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace -# and extra blank lines, converted CR/LF pairs at ends of each line, -# and other cosmetic fixes. -# 0.14 - Working out when the extra data flags are present has been problematic -# Versions 7 through 9 have tried to tweak the conditions, but have been -# only partially successful. Closer examination of lots of sample -# files reveals that a confusion has arisen because trailing data entries -# are not encrypted, but it turns out that the multibyte entries -# in utf8 file are encrypted. (Although neither kind gets compressed.) -# This knowledge leads to a simplification of the test for the -# trailing data byte flags - version 5 and higher AND header size >= 0xE4. -# 0.15 - Now outputs 'heartbeat', and is also quicker for long files. -# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility. -# 0.17 - added modifications to support its use as an imported python module -# both inside calibre and also in other places (ie K4DeDRM tools) -# 0.17a- disabled the standalone plugin feature since a plugin can not import -# a plugin -# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file... -# Removed the disabled Calibre plug-in code -# Permit use of 8-digit PIDs -# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. -# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file. -# 0.21 - Added support for multiple pids -# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface -# 0.23 - fixed problem with older files with no EXTH section -# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well -# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption -# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% -# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) -# 0.28 - slight additional changes to metadata token generation (None -> '') -# 0.29 - It seems that the ideas about when multibyte trailing characters were -# included in the encryption were wrong. They are for DOC compressed -# files, but they are not for HUFF/CDIC compress files! -# 0.30 - Modified interface slightly to work better with new calibre plugin style -# 0.31 - The multibyte encrytion info is true for version 7 files too. -# 0.32 - Added support for "Print Replica" Kindle ebooks -# 0.33 - Performance improvements for large files (concatenation) -# 0.34 - Performance improvements in decryption (libalfcrypto) -# 0.35 - add interface to get mobi_version -# 0.36 - fixed problem with TEXtREAd and getBookTitle interface -# 0.37 - Fixed double announcement for stand-alone operation +#!/usr/bin/env python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab +import Tkinter +import Tkconstants -__version__ = '0.37' +# basic scrolled text widget +class ScrolledText(Tkinter.Text): + def __init__(self, master=None, **kw): + self.frame = Tkinter.Frame(master) + self.vbar = Tkinter.Scrollbar(self.frame) + self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y) + kw.update({'yscrollcommand': self.vbar.set}) + Tkinter.Text.__init__(self, self.frame, **kw) + self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True) + self.vbar['command'] = self.yview + # Copy geometry methods of self.frame without overriding Text + # methods = hack! + text_meths = vars(Tkinter.Text).keys() + methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys() + methods = set(methods).difference(text_meths) + for m in methods: + if m[0] != '_' and m != 'config' and m != 'configure': + setattr(self, m, getattr(self.frame, m)) -import sys - -class Unbuffered: - def __init__(self, stream): - self.stream = stream - def write(self, data): - self.stream.write(data) - self.stream.flush() - def __getattr__(self, attr): - return getattr(self.stream, attr) -sys.stdout=Unbuffered(sys.stdout) - -import os -import struct -import binascii -from alfcrypto import Pukall_Cipher - -class DrmException(Exception): - pass - - -# -# MobiBook Utility Routines -# - -# Implementation of Pukall Cipher 1 -def PC1(key, src, decryption=True): - return Pukall_Cipher().PC1(key,src,decryption) -# sum1 = 0; -# sum2 = 0; -# keyXorVal = 0; -# if len(key)!=16: -# print "Bad key length!" -# return None -# wkey = [] -# for i in xrange(8): -# wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) -# dst = "" -# for i in xrange(len(src)): -# temp1 = 0; -# byteXorVal = 0; -# for j in xrange(8): -# temp1 ^= wkey[j] -# sum2 = (sum2+j)*20021 + sum1 -# sum1 = (temp1*346)&0xFFFF -# sum2 = (sum2+sum1)&0xFFFF -# temp1 = (temp1*20021+1)&0xFFFF -# byteXorVal ^= temp1 ^ sum2 -# curByte = ord(src[i]) -# if not decryption: -# keyXorVal = curByte * 257; -# curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF -# if decryption: -# keyXorVal = curByte * 257; -# for j in xrange(8): -# wkey[j] ^= keyXorVal; -# dst+=chr(curByte) -# return dst - -def checksumPid(s): - letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789" - crc = (~binascii.crc32(s,-1))&0xFFFFFFFF - crc = crc ^ (crc >> 16) - res = s - l = len(letters) - for i in (0,1): - b = crc & 0xff - pos = (b // l) ^ (b % l) - res += letters[pos%l] - crc >>= 8 - return res - -def getSizeOfTrailingDataEntries(ptr, size, flags): - def getSizeOfTrailingDataEntry(ptr, size): - bitpos, result = 0, 0 - if size <= 0: - return result - while True: - v = ord(ptr[size-1]) - result |= (v & 0x7F) << bitpos - bitpos += 7 - size -= 1 - if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0): - return result - num = 0 - testflags = flags >> 1 - while testflags: - if testflags & 1: - num += getSizeOfTrailingDataEntry(ptr, size - num) - testflags >>= 1 - # Check the low bit to see if there's multibyte data present. - # if multibyte data is included in the encryped data, we'll - # have already cleared this flag. - if flags & 1: - num += (ord(ptr[size - num - 1]) & 0x3) + 1 - return num - - - -class MobiBook: - def loadSection(self, section): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - return self.data_file[off:endoff] - - def __init__(self, infile, announce = True): - if announce: - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2012 The Dark Reverser et al.' % globals()) - - # initial sanity check on file - self.data_file = file(infile, 'rb').read() - self.mobi_data = '' - self.header = self.data_file[0:78] - if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd': - raise DrmException("invalid file format") - self.magic = self.header[0x3C:0x3C+8] - self.crypto_type = -1 - - # build up section offset and flag info - self.num_sections, = struct.unpack('>H', self.header[76:78]) - self.sections = [] - for i in xrange(self.num_sections): - offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8]) - flags, val = a1, a2<<16|a3<<8|a4 - self.sections.append( (offset, flags, val) ) - - # parse information from section 0 - self.sect = self.loadSection(0) - self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) - self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2]) - - if self.magic == 'TEXtREAd': - print "Book has format: ", self.magic - self.extra_data_flags = 0 - self.mobi_length = 0 - self.mobi_codepage = 1252 - self.mobi_version = -1 - self.meta_array = {} - return - self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18]) - self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20]) - self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C]) - print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length) - self.extra_data_flags = 0 - if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): - self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) - print "Extra Data Flags = %d" % self.extra_data_flags - if (self.compression != 17480): - # multibyte utf8 data is included in the encryption for PalmDoc compression - # so clear that byte so that we leave it to be decrypted. - self.extra_data_flags &= 0xFFFE - - # if exth region exists parse it for metadata array - self.meta_array = {} - try: - exth_flag, = struct.unpack('>L', self.sect[0x80:0x84]) - exth = 'NONE' - if exth_flag & 0x40: - exth = self.sect[16 + self.mobi_length:] - if (len(exth) >= 4) and (exth[:4] == 'EXTH'): - nitems, = struct.unpack('>I', exth[8:12]) - pos = 12 - for i in xrange(nitems): - type, size = struct.unpack('>II', exth[pos: pos + 8]) - content = exth[pos + 8: pos + size] - self.meta_array[type] = content - # reset the text to speech flag and clipping limit, if present - if type == 401 and size == 9: - # set clipping limit to 100% - self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8) - elif type == 404 and size == 9: - # make sure text to speech is enabled - self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8) - # print type, size, content, content.encode('hex') - pos += size - except: - self.meta_array = {} - pass - self.print_replica = False - - def getBookTitle(self): - codec_map = { - 1252 : 'windows-1252', - 65001 : 'utf-8', - } - title = '' - codec = 'windows-1252' - if self.magic == 'BOOKMOBI': - if 503 in self.meta_array: - title = self.meta_array[503] - else: - toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c]) - tend = toff + tlen - title = self.sect[toff:tend] - if self.mobi_codepage in codec_map.keys(): - codec = codec_map[self.mobi_codepage] - if title == '': - title = self.header[:32] - title = title.split("\0")[0] - return unicode(title, codec).encode('utf-8') - - def getPIDMetaInfo(self): - rec209 = '' - token = '' - if 209 in self.meta_array: - rec209 = self.meta_array[209] - data = rec209 - # The 209 data comes in five byte groups. Interpret the last four bytes - # of each group as a big endian unsigned integer to get a key value - # if that key exists in the meta_array, append its contents to the token - for i in xrange(0,len(data),5): - val, = struct.unpack('>I',data[i+1:i+5]) - sval = self.meta_array.get(val,'') - token += sval - return rec209, token - - def patch(self, off, new): - self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] - - def patchSection(self, section, new, in_off = 0): - if (section + 1 == self.num_sections): - endoff = len(self.data_file) - else: - endoff = self.sections[section + 1][0] - off = self.sections[section][0] - assert off + in_off + len(new) <= endoff - self.patch(off + in_off, new) - - def parseDRM(self, data, count, pidlist): - found_key = None - keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96" - for pid in pidlist: - bigpid = pid.ljust(16,'\0') - temp_key = PC1(keyvec1, bigpid, False) - temp_key_sum = sum(map(ord,temp_key)) & 0xff - found_key = None - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver and (flags & 0x1F) == 1: - found_key = finalkey - break - if found_key != None: - break - if not found_key: - # Then try the default encoding that doesn't require a PID - pid = "00000000" - temp_key = keyvec1 - temp_key_sum = sum(map(ord,temp_key)) & 0xff - for i in xrange(count): - verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) - if cksum == temp_key_sum: - cookie = PC1(temp_key, cookie) - ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) - if verification == ver: - found_key = finalkey - break - return [found_key,pid] - - def getMobiFile(self, outpath): - file(outpath,'wb').write(self.mobi_data) - - def getMobiVersion(self): - return self.mobi_version - - def getPrintReplica(self): - return self.print_replica - - def processBook(self, pidlist): - crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2]) - print 'Crypto Type is: ', crypto_type - self.crypto_type = crypto_type - if crypto_type == 0: - print "This book is not encrypted." - # we must still check for Print Replica - self.print_replica = (self.loadSection(1)[0:4] == '%MOP') - self.mobi_data = self.data_file - return - if crypto_type != 2 and crypto_type != 1: - raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type) - if 406 in self.meta_array: - data406 = self.meta_array[406] - val406, = struct.unpack('>Q',data406) - if val406 != 0: - raise DrmException("Cannot decode library or rented ebooks.") - - goodpids = [] - for pid in pidlist: - if len(pid)==10: - if checksumPid(pid[0:-2]) != pid: - print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2]) - goodpids.append(pid[0:-2]) - elif len(pid)==8: - goodpids.append(pid) - - if self.crypto_type == 1: - t1_keyvec = "QDCVEPMU675RUBSZ" - if self.magic == 'TEXtREAd': - bookkey_data = self.sect[0x0E:0x0E+16] - elif self.mobi_version < 0: - bookkey_data = self.sect[0x90:0x90+16] - else: - bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32] - pid = "00000000" - found_key = PC1(t1_keyvec, bookkey_data) - else : - # calculate the keys - drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16]) - if drm_count == 0: - raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.") - found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids) - if not found_key: - raise DrmException("No key found in " + str(len(goodpids)) + " keys tried. Please report this failure for help.") - # kill the drm keys - self.patchSection(0, "\0" * drm_size, drm_ptr) - # kill the drm pointers - self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) - - if pid=="00000000": - print "File has default encryption, no specific PID." - else: - print "File is encoded with PID "+checksumPid(pid)+"." - - # clear the crypto type - self.patchSection(0, "\0" * 2, 0xC) - - # decrypt sections - print "Decrypting. Please wait . . .", - mobidataList = [] - mobidataList.append(self.data_file[:self.sections[1][0]]) - for i in xrange(1, self.records+1): - data = self.loadSection(i) - extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags) - if i%100 == 0: - print ".", - # print "record %d, extra_size %d" %(i,extra_size) - decoded_data = PC1(found_key, data[0:len(data) - extra_size]) - if i==1: - self.print_replica = (decoded_data[0:4] == '%MOP') - mobidataList.append(decoded_data) - if extra_size > 0: - mobidataList.append(data[-extra_size:]) - if self.num_sections > self.records+1: - mobidataList.append(self.data_file[self.sections[self.records+1][0]:]) - self.mobi_data = "".join(mobidataList) - print "done" - return - -def getUnencryptedBook(infile,pid,announce=True): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile,announce) - book.processBook([pid]) - return book.mobi_data - -def getUnencryptedBookWithList(infile,pidlist,announce=True): - if not os.path.isfile(infile): - raise DrmException('Input File Not Found') - book = MobiBook(infile, announce) - book.processBook(pidlist) - return book.mobi_data - - -def main(argv=sys.argv): - print ('MobiDeDrm v%(__version__)s. ' - 'Copyright 2008-2012 The Dark Reverser et al.' % globals()) - if len(argv)<3 or len(argv)>4: - print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks" - print "Usage:" - print " %s []" % sys.argv[0] - return 1 - else: - infile = argv[1] - outfile = argv[2] - if len(argv) is 4: - pidlist = argv[3].split(',') - else: - pidlist = {} - try: - stripped_file = getUnencryptedBookWithList(infile, pidlist, False) - file(outfile, 'wb').write(stripped_file) - except DrmException, e: - print "Error: %s" % e - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) + def __str__(self): + return str(self.frame) diff --git a/Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py b/Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py index 906c6e9..2347f6a 100644 --- a/Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py +++ b/Calibre_Plugins/K4MobiDeDRM_plugin/stylexml2css.py @@ -1,45 +1,266 @@ -# -*- coding: utf-8 -*- -# -# Adapted and simplified from the kitchen project -# -# Kitchen Project Copyright (c) 2012 Red Hat, Inc. -# -# kitchen is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# kitchen 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with kitchen; if not, see -# -# Authors: -# Toshio Kuratomi -# Seth Vidal -# -# Portions of code taken from yum/i18n.py and -# python-fedora: fedora/textutils.py +#! /usr/bin/python +# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab +# For use with Topaz Scripts Version 2.6 -import codecs +import csv +import sys +import os +import getopt +import re +from struct import pack +from struct import unpack -# returns a char string unchanged -# returns a unicode string converted to a char string of the passed encoding -# return the empty string for anything else -def getwriter(encoding): - class _StreamWriter(codecs.StreamWriter): - def __init__(self, stream): - codecs.StreamWriter.__init__(self, stream, 'replace') - def encode(self, msg, errors='replace'): - if isinstance(msg, basestring): - if isinstance(msg, str): - return (msg, len(msg)) - return (msg.encode(self.encoding, 'replace'), len(msg)) - return ('',0) +class DocParser(object): + def __init__(self, flatxml, fontsize, ph, pw): + self.flatdoc = flatxml.split('\n') + self.fontsize = int(fontsize) + self.ph = int(ph) * 1.0 + self.pw = int(pw) * 1.0 - _StreamWriter.encoding = encoding - return _StreamWriter + stags = { + 'paragraph' : 'p', + 'graphic' : '.graphic' + } + + attr_val_map = { + 'hang' : 'text-indent: ', + 'indent' : 'text-indent: ', + 'line-space' : 'line-height: ', + 'margin-bottom' : 'margin-bottom: ', + 'margin-left' : 'margin-left: ', + 'margin-right' : 'margin-right: ', + 'margin-top' : 'margin-top: ', + 'space-after' : 'padding-bottom: ', + } + + attr_str_map = { + 'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;', + 'align-left' : 'text-align: left;', + 'align-right' : 'text-align: right;', + 'align-justify' : 'text-align: justify;', + 'display-inline' : 'display: inline;', + 'pos-left' : 'text-align: left;', + 'pos-right' : 'text-align: right;', + 'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;', + } + + + # find tag if within pos to end inclusive + def findinDoc(self, tagpath, pos, end) : + result = None + docList = self.flatdoc + cnt = len(docList) + if end == -1 : + end = cnt + else: + end = min(cnt,end) + foundat = -1 + for j in xrange(pos, end): + item = docList[j] + if item.find('=') >= 0: + (name, argres) = item.split('=',1) + else : + name = item + argres = '' + if name.endswith(tagpath) : + result = argres + foundat = j + break + return foundat, result + + + # return list of start positions for the tagpath + def posinDoc(self, tagpath): + startpos = [] + pos = 0 + res = "" + while res != None : + (foundpos, res) = self.findinDoc(tagpath, pos, -1) + if res != None : + startpos.append(foundpos) + pos = foundpos + 1 + return startpos + + # returns a vector of integers for the tagpath + def getData(self, tagpath, pos, end, clean=False): + if clean: + digits_only = re.compile(r'''([0-9]+)''') + argres=[] + (foundat, argt) = self.findinDoc(tagpath, pos, end) + if (argt != None) and (len(argt) > 0) : + argList = argt.split('|') + for strval in argList: + if clean: + m = re.search(digits_only, strval) + if m != None: + strval = m.group() + argres.append(int(strval)) + return argres + + def process(self): + + classlst = '' + csspage = '.cl-center { text-align: center; margin-left: auto; margin-right: auto; }\n' + csspage += '.cl-right { text-align: right; }\n' + csspage += '.cl-left { text-align: left; }\n' + csspage += '.cl-justify { text-align: justify; }\n' + + # generate a list of each