tools v2.3
First appearance of DeDRM AppleScript application
This commit is contained in:
476
Mobi_Additional_Tools/k4mdumpkinfo.py
Normal file
476
Mobi_Additional_Tools/k4mdumpkinfo.py
Normal file
@@ -0,0 +1,476 @@
|
||||
# engine to remove drm from Kindle for Mac books
|
||||
# for personal use for archiving and converting your ebooks
|
||||
# PLEASE DO NOT PIRATE!
|
||||
# We want all authors and Publishers, and eBook stores to live long and prosperous lives
|
||||
#
|
||||
# it borrows heavily from works by CMBDTC, IHeartCabbages, skindle,
|
||||
# unswindle, DiapDealer, some_updates and many many others
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
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 os, csv, getopt
|
||||
from struct import pack
|
||||
from struct import unpack
|
||||
import zlib
|
||||
|
||||
# for handling sub processes
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
#Exception Handling
|
||||
class K4MDEDRMError(Exception):
|
||||
pass
|
||||
class K4MDEDRMFatal(Exception):
|
||||
pass
|
||||
|
||||
#
|
||||
# crypto routines
|
||||
#
|
||||
import hashlib
|
||||
|
||||
def MD5(message):
|
||||
ctx = hashlib.md5()
|
||||
ctx.update(message)
|
||||
return ctx.digest()
|
||||
|
||||
def SHA1(message):
|
||||
ctx = hashlib.sha1()
|
||||
ctx.update(message)
|
||||
return ctx.digest()
|
||||
|
||||
def SHA256(message):
|
||||
ctx = hashlib.sha256()
|
||||
ctx.update(message)
|
||||
return ctx.digest()
|
||||
|
||||
# interface to needed routines in openssl's libcrypto
|
||||
def _load_crypto_libcrypto():
|
||||
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
|
||||
from ctypes.util import find_library
|
||||
|
||||
libcrypto = find_library('crypto')
|
||||
if libcrypto is None:
|
||||
raise K4MDEDRMError('libcrypto not found')
|
||||
libcrypto = CDLL(libcrypto)
|
||||
|
||||
AES_MAXNR = 14
|
||||
c_char_pp = POINTER(c_char_p)
|
||||
c_int_p = POINTER(c_int)
|
||||
|
||||
class AES_KEY(Structure):
|
||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
|
||||
AES_KEY_p = POINTER(AES_KEY)
|
||||
|
||||
def F(restype, name, argtypes):
|
||||
func = getattr(libcrypto, name)
|
||||
func.restype = restype
|
||||
func.argtypes = argtypes
|
||||
return func
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
|
||||
class LibCrypto(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 K4MDEDRMError('AES 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 K4MDEDRMError('Failed to initialize AES key')
|
||||
def decrypt(self, data):
|
||||
out = create_string_buffer(len(data))
|
||||
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
|
||||
if rv == 0:
|
||||
raise K4MDEDRMError('AES decryption failed')
|
||||
return out.raw
|
||||
def keyivgen(self, passwd):
|
||||
salt = '16743'
|
||||
saltlen = 5
|
||||
passlen = len(passwd)
|
||||
iter = 0x3e8
|
||||
keylen = 80
|
||||
out = create_string_buffer(keylen)
|
||||
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
||||
return out.raw
|
||||
return LibCrypto
|
||||
|
||||
def _load_crypto():
|
||||
LibCrypto = None
|
||||
try:
|
||||
LibCrypto = _load_crypto_libcrypto()
|
||||
except (ImportError, K4MDEDRMError):
|
||||
pass
|
||||
return LibCrypto
|
||||
|
||||
LibCrypto = _load_crypto()
|
||||
|
||||
#
|
||||
# Utility Routines
|
||||
#
|
||||
|
||||
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||
# returns with the first found serial number in that class
|
||||
def GetVolumeSerialNumber():
|
||||
sernum = os.getenv('MYSERIALNUMBER')
|
||||
if sernum != None:
|
||||
return sernum
|
||||
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||
poll = p.wait('wait')
|
||||
results = p.read()
|
||||
reslst = results.split('\n')
|
||||
cnt = len(reslst)
|
||||
bsdname = None
|
||||
sernum = None
|
||||
foundIt = False
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('"Serial Number" = "')
|
||||
if pp >= 0:
|
||||
sernum = resline[pp+19:-1]
|
||||
sernum = sernum.strip()
|
||||
bb = resline.find('"BSD Name" = "')
|
||||
if bb >= 0:
|
||||
bsdname = resline[bb+14:-1]
|
||||
bsdname = bsdname.strip()
|
||||
if (bsdname == 'disk0') and (sernum != None):
|
||||
foundIt = True
|
||||
break
|
||||
if not foundIt:
|
||||
sernum = '9999999999'
|
||||
return sernum
|
||||
|
||||
# uses unix env to get username instead of using sysctlbyname
|
||||
def GetUserName():
|
||||
username = os.getenv('USER')
|
||||
return username
|
||||
|
||||
MAX_PATH = 255
|
||||
|
||||
#
|
||||
# start of Kindle specific routines
|
||||
#
|
||||
|
||||
global kindleDatabase
|
||||
|
||||
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
|
||||
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||
|
||||
# Encode the bytes in data with the characters in map
|
||||
def encode(data, map):
|
||||
result = ""
|
||||
for char in data:
|
||||
value = ord(char)
|
||||
Q = (value ^ 0x80) // len(map)
|
||||
R = value % len(map)
|
||||
result += map[Q]
|
||||
result += map[R]
|
||||
return result
|
||||
|
||||
# Hash the bytes in data and then encode the digest with the characters in map
|
||||
def encodeHash(data,map):
|
||||
return encode(MD5(data),map)
|
||||
|
||||
# Decode the string in data with the characters in map. Returns the decoded bytes
|
||||
def decode(data,map):
|
||||
result = ""
|
||||
for i in range (0,len(data)-1,2):
|
||||
high = map.find(data[i])
|
||||
low = map.find(data[i+1])
|
||||
if (high == -1) or (low == -1) :
|
||||
break
|
||||
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
|
||||
result += pack("B",value)
|
||||
return result
|
||||
|
||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||
def CryptUnprotectData(encryptedData):
|
||||
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
|
||||
passwdData = encode(SHA256(sp),charMap1)
|
||||
crp = LibCrypto()
|
||||
key_iv = crp.keyivgen(passwdData)
|
||||
key = key_iv[0:32]
|
||||
iv = key_iv[32:48]
|
||||
crp.set_decrypt_key(key,iv)
|
||||
cleartext = crp.decrypt(encryptedData)
|
||||
return cleartext
|
||||
|
||||
# Locate and open the .kindle-info file
|
||||
def openKindleInfo():
|
||||
home = os.getenv('HOME')
|
||||
kinfopath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
|
||||
if not os.path.exists(kinfopath):
|
||||
kinfopath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
|
||||
if not os.path.exists(kinfopath):
|
||||
raise K4MDEDRMError('Error: .kindle-info file can not be found')
|
||||
return open(kinfopath,'r')
|
||||
|
||||
# Parse the Kindle.info file and return the records as a list of key-values
|
||||
def parseKindleInfo():
|
||||
DB = {}
|
||||
infoReader = openKindleInfo()
|
||||
infoReader.read(1)
|
||||
data = infoReader.read()
|
||||
items = data.split('[')
|
||||
for item in items:
|
||||
splito = item.split(':')
|
||||
DB[splito[0]] =splito[1]
|
||||
return DB
|
||||
|
||||
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
|
||||
def getKindleInfoValueForHash(hashedKey):
|
||||
global kindleDatabase
|
||||
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
|
||||
cleartext = CryptUnprotectData(encryptedValue)
|
||||
return decode(cleartext, charMap1)
|
||||
|
||||
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
|
||||
def getKindleInfoValueForKey(key):
|
||||
return getKindleInfoValueForHash(encodeHash(key,charMap2))
|
||||
|
||||
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
|
||||
def findNameForHash(hash):
|
||||
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||
result = ""
|
||||
for name in names:
|
||||
if hash == encodeHash(name, charMap2):
|
||||
result = name
|
||||
break
|
||||
return result
|
||||
|
||||
# Print all the records from the kindle.info file (option -i)
|
||||
def printKindleInfo():
|
||||
for record in kindleDatabase:
|
||||
name = findNameForHash(record)
|
||||
if name != "" :
|
||||
print (name)
|
||||
print ("--------------------------")
|
||||
else :
|
||||
print ("Unknown Record")
|
||||
print getKindleInfoValueForHash(record)
|
||||
print "\n"
|
||||
|
||||
#
|
||||
# PID generation routines
|
||||
#
|
||||
|
||||
# Returns two bit at offset from a bit field
|
||||
def getTwoBitsFromBitField(bitField,offset):
|
||||
byteNumber = offset // 4
|
||||
bitPosition = 6 - 2*(offset % 4)
|
||||
return ord(bitField[byteNumber]) >> bitPosition & 3
|
||||
|
||||
# Returns the six bits at offset from a bit field
|
||||
def getSixBitsFromBitField(bitField,offset):
|
||||
offset *= 3
|
||||
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
||||
return value
|
||||
|
||||
# 8 bits to six bits encoding from hash to generate PID string
|
||||
def encodePID(hash):
|
||||
global charMap3
|
||||
PID = ""
|
||||
for position in range (0,8):
|
||||
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
||||
return PID
|
||||
|
||||
|
||||
#
|
||||
# Main
|
||||
#
|
||||
|
||||
def main(argv=sys.argv):
|
||||
global kindleDatabase
|
||||
|
||||
kindleDatabase = None
|
||||
|
||||
#
|
||||
# Read the encrypted database
|
||||
#
|
||||
|
||||
try:
|
||||
kindleDatabase = parseKindleInfo()
|
||||
except Exception, message:
|
||||
print(message)
|
||||
|
||||
if kindleDatabase != None :
|
||||
printKindleInfo()
|
||||
|
||||
return 0
|
||||
|
||||
import signal
|
||||
import threading
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
# **heavily** chopped up and modfied version of asyncproc.py
|
||||
# to make it actually work on Windows as well as Mac/Linux
|
||||
# For the original see:
|
||||
# "http://www.lysator.liu.se/~bellman/download/"
|
||||
# author is "Thomas Bellman <bellman@lysator.liu.se>"
|
||||
# available under GPL version 3 or Later
|
||||
|
||||
# create an asynchronous subprocess whose output can be collected in
|
||||
# a non-blocking manner
|
||||
|
||||
# What a mess! Have to use threads just to get non-blocking io
|
||||
# in a cross-platform manner
|
||||
|
||||
# luckily all thread use is hidden within this class
|
||||
|
||||
class Process(object):
|
||||
def __init__(self, *params, **kwparams):
|
||||
if len(params) <= 3:
|
||||
kwparams.setdefault('stdin', subprocess.PIPE)
|
||||
if len(params) <= 4:
|
||||
kwparams.setdefault('stdout', subprocess.PIPE)
|
||||
if len(params) <= 5:
|
||||
kwparams.setdefault('stderr', subprocess.PIPE)
|
||||
self.__pending_input = []
|
||||
self.__collected_outdata = []
|
||||
self.__collected_errdata = []
|
||||
self.__exitstatus = None
|
||||
self.__lock = threading.Lock()
|
||||
self.__inputsem = threading.Semaphore(0)
|
||||
self.__quit = False
|
||||
|
||||
self.__process = subprocess.Popen(*params, **kwparams)
|
||||
|
||||
if self.__process.stdin:
|
||||
self.__stdin_thread = threading.Thread(
|
||||
name="stdin-thread",
|
||||
target=self.__feeder, args=(self.__pending_input,
|
||||
self.__process.stdin))
|
||||
self.__stdin_thread.setDaemon(True)
|
||||
self.__stdin_thread.start()
|
||||
|
||||
if self.__process.stdout:
|
||||
self.__stdout_thread = threading.Thread(
|
||||
name="stdout-thread",
|
||||
target=self.__reader, args=(self.__collected_outdata,
|
||||
self.__process.stdout))
|
||||
self.__stdout_thread.setDaemon(True)
|
||||
self.__stdout_thread.start()
|
||||
|
||||
if self.__process.stderr:
|
||||
self.__stderr_thread = threading.Thread(
|
||||
name="stderr-thread",
|
||||
target=self.__reader, args=(self.__collected_errdata,
|
||||
self.__process.stderr))
|
||||
self.__stderr_thread.setDaemon(True)
|
||||
self.__stderr_thread.start()
|
||||
|
||||
def pid(self):
|
||||
return self.__process.pid
|
||||
|
||||
def kill(self, signal):
|
||||
self.__process.send_signal(signal)
|
||||
|
||||
# check on subprocess (pass in 'nowait') to act like poll
|
||||
def wait(self, flag):
|
||||
if flag.lower() == 'nowait':
|
||||
rc = self.__process.poll()
|
||||
else:
|
||||
rc = self.__process.wait()
|
||||
if rc != None:
|
||||
if self.__process.stdin:
|
||||
self.closeinput()
|
||||
if self.__process.stdout:
|
||||
self.__stdout_thread.join()
|
||||
if self.__process.stderr:
|
||||
self.__stderr_thread.join()
|
||||
return self.__process.returncode
|
||||
|
||||
def terminate(self):
|
||||
if self.__process.stdin:
|
||||
self.closeinput()
|
||||
self.__process.terminate()
|
||||
|
||||
# thread gets data from subprocess stdout
|
||||
def __reader(self, collector, source):
|
||||
while True:
|
||||
data = os.read(source.fileno(), 65536)
|
||||
self.__lock.acquire()
|
||||
collector.append(data)
|
||||
self.__lock.release()
|
||||
if data == "":
|
||||
source.close()
|
||||
break
|
||||
return
|
||||
|
||||
# thread feeds data to subprocess stdin
|
||||
def __feeder(self, pending, drain):
|
||||
while True:
|
||||
self.__inputsem.acquire()
|
||||
self.__lock.acquire()
|
||||
if not pending and self.__quit:
|
||||
drain.close()
|
||||
self.__lock.release()
|
||||
break
|
||||
data = pending.pop(0)
|
||||
self.__lock.release()
|
||||
drain.write(data)
|
||||
|
||||
# non-blocking read of data from subprocess stdout
|
||||
def read(self):
|
||||
self.__lock.acquire()
|
||||
outdata = "".join(self.__collected_outdata)
|
||||
del self.__collected_outdata[:]
|
||||
self.__lock.release()
|
||||
return outdata
|
||||
|
||||
# non-blocking read of data from subprocess stderr
|
||||
def readerr(self):
|
||||
self.__lock.acquire()
|
||||
errdata = "".join(self.__collected_errdata)
|
||||
del self.__collected_errdata[:]
|
||||
self.__lock.release()
|
||||
return errdata
|
||||
|
||||
# non-blocking write to stdin of subprocess
|
||||
def write(self, data):
|
||||
if self.__process.stdin is None:
|
||||
raise ValueError("Writing to process with stdin not a pipe")
|
||||
self.__lock.acquire()
|
||||
self.__pending_input.append(data)
|
||||
self.__inputsem.release()
|
||||
self.__lock.release()
|
||||
|
||||
# close stdinput of subprocess
|
||||
def closeinput(self):
|
||||
self.__lock.acquire()
|
||||
self.__quit = True
|
||||
self.__inputsem.release()
|
||||
self.__lock.release()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -3,14 +3,6 @@
|
||||
# This is a python script. You need a Python interpreter to run it.
|
||||
# For example, ActiveState Python, which exists for windows.
|
||||
#
|
||||
# It can run standalone to convert files, or it can be installed as a
|
||||
# plugin for Calibre (http://calibre-ebook.com/about) so that
|
||||
# importing files with DRM 'Just Works'.
|
||||
#
|
||||
# To create a Calibre plugin, rename this file so that the filename
|
||||
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
|
||||
# using its plugin configuration GUI.
|
||||
#
|
||||
# Changelog
|
||||
# 0.01 - Initial version
|
||||
# 0.02 - Huffdic compressed books were not properly decrypted
|
||||
@@ -37,14 +29,18 @@
|
||||
# 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 'hearbeat', and is also quicker for long files.
|
||||
# 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 K4MobiDeDRM tools)
|
||||
# and modified the plugin code so that it will not interfere with other
|
||||
# mobi/azw plugins if installed at the same time
|
||||
# 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.
|
||||
|
||||
__version__ = '0.17'
|
||||
__version__ = '0.19'
|
||||
|
||||
import sys
|
||||
import struct
|
||||
@@ -127,10 +123,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||
if testflags & 1:
|
||||
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
||||
testflags >>= 1
|
||||
# Multibyte data, if present, is included in the encryption, so
|
||||
# we do not need to check the low bit.
|
||||
# if flags & 1:
|
||||
# num += (ord(ptr[size - num - 1]) & 0x3) + 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 DrmStripper:
|
||||
@@ -181,9 +178,14 @@ class DrmStripper:
|
||||
return found_key
|
||||
|
||||
def __init__(self, data_file, pid):
|
||||
if checksumPid(pid[0:-2]) != pid:
|
||||
raise DrmException("invalid PID checksum")
|
||||
pid = pid[0:-2]
|
||||
if len(pid)==10:
|
||||
if checksumPid(pid[0:-2]) != pid:
|
||||
raise DrmException("invalid PID checksum")
|
||||
pid = pid[0:-2]
|
||||
elif len(pid)==8:
|
||||
print "PID without checksum given. With checksum PID is "+checksumPid(pid)
|
||||
else:
|
||||
raise DrmException("Invalid PID length")
|
||||
|
||||
self.data_file = data_file
|
||||
header = data_file[0:72]
|
||||
@@ -206,6 +208,10 @@ class DrmStripper:
|
||||
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
||||
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
|
||||
print "Extra Data Flags = %d" %extra_data_flags
|
||||
if mobi_version <= 5:
|
||||
# multibyte utf8 data is included in the encryption for mobi_version 5 and below
|
||||
# so clear that byte so that we leave it to be decrypted.
|
||||
extra_data_flags &= 0xFFFE
|
||||
|
||||
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
||||
if crypto_type == 0:
|
||||
@@ -282,42 +288,3 @@ def main(argv=sys.argv):
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
#if not __name__ == "__main__":
|
||||
# note a calibre plugin can not import code with another calibre plugin
|
||||
# in it as it ends up registering two different plugins
|
||||
from calibre.customize import FileTypePlugin
|
||||
|
||||
class MobiDeDRM(FileTypePlugin):
|
||||
name = 'MobiDeDRM' # Name of the plugin
|
||||
description = 'Removes DRM from secure Mobi files'
|
||||
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
||||
author = 'The Dark Reverser' # The author of this plugin
|
||||
version = (0, 1, 7) # The version number of this plugin
|
||||
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
||||
on_import = True # Run this plugin during the import
|
||||
|
||||
def run(self, path_to_ebook):
|
||||
from calibre.gui2 import is_ok_to_use_qt
|
||||
from PyQt4.Qt import QMessageBox
|
||||
PID = self.site_customization
|
||||
data_file = file(path_to_ebook, 'rb').read()
|
||||
ar = PID.split(',')
|
||||
for i in ar:
|
||||
try:
|
||||
unlocked_file = DrmStripper(data_file, i).getResult()
|
||||
except DrmException:
|
||||
if is_ok_to_use_qt():
|
||||
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
|
||||
d.show()
|
||||
d.raise_()
|
||||
d.exec_()
|
||||
raise Exception("MobiDeDRM Plugin: Error decoding ebook")
|
||||
else:
|
||||
of = self.temporary_file('.mobi')
|
||||
of.write(unlocked_file)
|
||||
of.close()
|
||||
return of.name
|
||||
|
||||
def customization_help(self, gui=False):
|
||||
return 'Enter PID (separate multiple PIDs with comma)'
|
||||
|
||||
Reference in New Issue
Block a user