mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-04-16 23:28:56 +00:00
tools v2.2
This commit is contained in:
@@ -45,11 +45,20 @@ class MainDialog(Tkinter.Frame):
|
||||
self.outpath.insert(0, outname)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_outpath)
|
||||
button.grid(row=1, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Kindle.info file (optional)').grid(row=2, sticky=Tkconstants.E)
|
||||
self.altinfopath = Tkinter.Entry(body, width=50)
|
||||
self.altinfopath.grid(row=2, column=1, sticky=sticky)
|
||||
#cwd = os.getcwdu()
|
||||
#cwd = cwd.encode('utf-8')
|
||||
#self.altinfopath.insert(0, cwd)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_altinfopath)
|
||||
button.grid(row=2, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Comma Separated List of 10 Character PIDs (no spaces)').grid(row=2, sticky=Tkconstants.E)
|
||||
Tkinter.Label(body, text='Comma Separated List of 10 Character PIDs (no spaces)').grid(row=3, sticky=Tkconstants.E)
|
||||
self.pidnums = Tkinter.StringVar()
|
||||
self.pidinfo = Tkinter.Entry(body, width=50, textvariable=self.pidnums)
|
||||
self.pidinfo.grid(row=2, column=1, sticky=sticky)
|
||||
self.pidinfo.grid(row=3, column=1, sticky=sticky)
|
||||
|
||||
msg1 = 'Conversion Log \n\n'
|
||||
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD)
|
||||
@@ -100,16 +109,23 @@ class MainDialog(Tkinter.Frame):
|
||||
return
|
||||
|
||||
# run as a subprocess via pipes and collect stdout
|
||||
def mobirdr(self, infile, outfile, pidnums):
|
||||
def mobirdr(self, infile, outfile, altinfopath, pidnums):
|
||||
# os.putenv('PYTHONUNBUFFERED', '1')
|
||||
cmdline = 'python ./lib/k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
|
||||
pidoption = ''
|
||||
if pidnums and pidnums != '':
|
||||
pidoption = ' -p "' + pidnums + '" '
|
||||
infooption = ''
|
||||
if altinfopath and altinfopath != '':
|
||||
infooption = ' -k "' + altinfopath + '" '
|
||||
cmdline = 'python ./lib/k4mobidedrm.py ' + pidoption + infooption + '"' + infile + '" "' + outfile + '"'
|
||||
print cmdline
|
||||
if sys.platform.startswith('win'):
|
||||
search_path = os.environ['PATH']
|
||||
search_path = search_path.lower()
|
||||
if search_path.find('python') >= 0:
|
||||
cmdline = 'python lib\k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
|
||||
cmdline = 'python lib\k4mobidedrm.py ' + pidoption + infooption + '"' + infile + '" "' + outfile + '"'
|
||||
else :
|
||||
cmdline = 'lib\k4mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnums + '"'
|
||||
cmdline = 'lib\k4mobidedrm.py ' + pidoption + infooption + '"' + infile + '" "' + outfile + '"'
|
||||
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||
@@ -141,6 +157,20 @@ class MainDialog(Tkinter.Frame):
|
||||
self.outpath.insert(0, outpath)
|
||||
return
|
||||
|
||||
def get_altinfopath(self):
|
||||
cwd = os.getcwdu()
|
||||
cwd = cwd.encode('utf-8')
|
||||
altinfopath = tkFileDialog.askopenfilename(
|
||||
parent=None, title='Select kindle.info File',
|
||||
defaultextension='.prc', filetypes=[('Kindle Info', '.info'),
|
||||
('All Files', '.*')],
|
||||
initialdir=cwd)
|
||||
if altinfopath:
|
||||
altinfopath = os.path.normpath(altinfopath)
|
||||
self.altinfopath.delete(0, Tkconstants.END)
|
||||
self.altinfopath.insert(0, altinfopath)
|
||||
return
|
||||
|
||||
def quitting(self):
|
||||
# kill any still running subprocess
|
||||
if self.p2 != None:
|
||||
@@ -154,6 +184,7 @@ class MainDialog(Tkinter.Frame):
|
||||
self.sbotton.configure(state='disabled')
|
||||
mobipath = self.mobipath.get()
|
||||
outpath = self.outpath.get()
|
||||
altinfopath = self.altinfopath.get()
|
||||
pidnums = self.pidinfo.get()
|
||||
|
||||
if not mobipath or not os.path.exists(mobipath):
|
||||
@@ -168,6 +199,10 @@ class MainDialog(Tkinter.Frame):
|
||||
self.status['text'] = 'Error specified output directory does not exist'
|
||||
self.sbotton.configure(state='normal')
|
||||
return
|
||||
if altinfopath and not os.path.exists(altinfopath):
|
||||
self.status['text'] = 'Specified kindle.info file does not exist'
|
||||
self.sbotton.configure(state='normal')
|
||||
return
|
||||
# default output file name to be input file name + '_nodrm.mobi'
|
||||
initname = os.path.splitext(os.path.basename(mobipath))[0]
|
||||
initname += '_nodrm.mobi'
|
||||
@@ -176,12 +211,13 @@ class MainDialog(Tkinter.Frame):
|
||||
log = 'Command = "python k4mobidedrm.py"\n'
|
||||
log += 'K4PC, K4M or Mobi Path = "'+ mobipath + '"\n'
|
||||
log += 'Output File = "' + outpath + '"\n'
|
||||
log += 'Kindle.info file = "' + altinfopath + '"\n'
|
||||
log += 'PID list = "' + pidnums + '"\n'
|
||||
log += '\n\n'
|
||||
log += 'Please Wait ...\n\n'
|
||||
log = log.encode('utf-8')
|
||||
self.stext.insert(Tkconstants.END,log)
|
||||
self.p2 = self.mobirdr(mobipath, outpath, pidnums)
|
||||
self.p2 = self.mobirdr(mobipath, outpath, altinfopath, pidnums)
|
||||
|
||||
# python does not seem to allow you to create
|
||||
# your own eventloop which every other gui does - strange
|
||||
|
||||
@@ -13,11 +13,13 @@ hit the first '...' button to locate your DRM Kindle-style ebook
|
||||
|
||||
3. Then hit the second '...' button to select an output directory for the unlocked file
|
||||
|
||||
4. Then add in any PIDs you need from KindleV1, Kindle for iPhone/iPad/iPodTouch, or other single PID devices to the provided box as a comma separated list of 10 digit PID numbers.
|
||||
4. If you have multiple Kindle.Info files and would like to use one specific one, please hit the third "...' button to select it. Note, if you only have one Kindle.Info file (like most users) this can and should be left blank.
|
||||
|
||||
5. . Then add in any PIDs you need from KindleV1, Kindle for iPhone/iPad/iPodTouch, or other single PID devices to the provided box as a comma separated list of 10 digit PID numbers.
|
||||
|
||||
If this is a Kindle for Mac or a Kindle for PC book then you can leave this box blank
|
||||
|
||||
5. hit the 'Start' button
|
||||
6. hit the 'Start' button
|
||||
|
||||
After a short delay, you should see progress in the Conversion Log window indicating is the unlocking was a success or failure.
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import sys
|
||||
import os, csv, getopt
|
||||
import binascii
|
||||
import zlib
|
||||
import re
|
||||
from struct import pack, unpack, unpack_from
|
||||
|
||||
|
||||
@@ -115,9 +116,9 @@ def decode(data,map):
|
||||
|
||||
|
||||
# Parse the Kindle.info file and return the records as a list of key-values
|
||||
def parseKindleInfo():
|
||||
def parseKindleInfo(kInfoFile):
|
||||
DB = {}
|
||||
infoReader = openKindleInfo()
|
||||
infoReader = openKindleInfo(kInfoFile)
|
||||
infoReader.read(1)
|
||||
data = infoReader.read()
|
||||
if sys.platform.startswith('win'):
|
||||
@@ -279,10 +280,10 @@ class MobiPeek:
|
||||
|
||||
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
|
||||
# file to calculate the book pid.
|
||||
def getK4Pids(exth, title):
|
||||
def getK4Pids(exth, title, kInfoFile=None):
|
||||
global kindleDatabase
|
||||
try:
|
||||
kindleDatabase = parseKindleInfo()
|
||||
kindleDatabase = parseKindleInfo(kInfoFile)
|
||||
except Exception as message:
|
||||
print(message)
|
||||
|
||||
@@ -353,30 +354,49 @@ def getK4Pids(exth, title):
|
||||
raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
|
||||
return null
|
||||
|
||||
def usage(progname):
|
||||
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
|
||||
print "Usage:"
|
||||
print " %s [-k <kindle.info>] [-p <pidnums>] <infile> <outfile> " % progname
|
||||
|
||||
#
|
||||
# Main
|
||||
#
|
||||
def main(argv=sys.argv):
|
||||
global kindleDatabase
|
||||
import mobidedrm
|
||||
|
||||
progname = os.path.basename(argv[0])
|
||||
kInfoFiles = []
|
||||
pidnums = ""
|
||||
|
||||
print ('K4MobiDeDrm v%(__version__)s '
|
||||
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
|
||||
|
||||
if len(argv)<3:
|
||||
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
|
||||
print "Usage:"
|
||||
print " %s <infile> <outfile> [<pidnums>]" % argv[0]
|
||||
return 1
|
||||
|
||||
if len(argv) == 4:
|
||||
pidnums = argv[3]
|
||||
|
||||
if len(argv) == 3:
|
||||
pidnums = ""
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "k:p:")
|
||||
except getopt.GetoptError, err:
|
||||
print str(err)
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
if len(args)<2:
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-k":
|
||||
if a == None :
|
||||
raise DrmException("Invalid parameter for -k")
|
||||
kInfoFiles.append(a)
|
||||
if o == "-p":
|
||||
if a == None :
|
||||
raise DrmException("Invalid parameter for -p")
|
||||
pidnums = a
|
||||
|
||||
kindleDatabase = None
|
||||
infile = argv[1]
|
||||
outfile = argv[2]
|
||||
infile = args[0]
|
||||
outfile = args[1]
|
||||
try:
|
||||
# first try with K4PC/K4M
|
||||
ex = MobiPeek(infile)
|
||||
@@ -394,8 +414,25 @@ def main(argv=sys.argv):
|
||||
else:
|
||||
file(outfile, 'wb').write(unlocked_file)
|
||||
return 0
|
||||
|
||||
# now try from the pid list
|
||||
|
||||
# now try alternate kindle.info files
|
||||
if kInfoFiles:
|
||||
for infoFile in kInfoFiles:
|
||||
kindleDatabase = None
|
||||
try:
|
||||
title = ex.getBookTitle()
|
||||
exth = ex.getexthData()
|
||||
pid = getK4Pids(exth, title, infoFile)
|
||||
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||
except DrmException:
|
||||
pass
|
||||
except mobidedrm.DrmException:
|
||||
pass
|
||||
else:
|
||||
file(outfile, 'wb').write(unlocked_file)
|
||||
return 0
|
||||
|
||||
# Lastly, try from the pid list
|
||||
pids = pidnums.split(',')
|
||||
for pid in pids:
|
||||
try:
|
||||
@@ -426,7 +463,7 @@ if not __name__ == "__main__" and inCalibre:
|
||||
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
|
||||
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
|
||||
author = 'DiapDealer, SomeUpdates' # The author of this plugin
|
||||
version = (0, 0, 1) # The version number of this plugin
|
||||
version = (0, 1, 1) # 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
|
||||
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
|
||||
@@ -442,7 +479,27 @@ if not __name__ == "__main__" and inCalibre:
|
||||
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||
import mobidedrm
|
||||
|
||||
# Get supplied list of PIDs to try from plugin customization.
|
||||
pidnums = self.site_customization
|
||||
|
||||
# Load any kindle info files (*.info) included Calibre's config directory.
|
||||
kInfoFiles = []
|
||||
try:
|
||||
# Find Calibre's configuration directory.
|
||||
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
|
||||
print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath
|
||||
files = os.listdir(confpath)
|
||||
filefilter = re.compile("\.info$", re.IGNORECASE)
|
||||
files = filter(filefilter.search, files)
|
||||
|
||||
if files:
|
||||
for filename in files:
|
||||
fpath = os.path.join(confpath, filename)
|
||||
kInfoFiles.append(fpath)
|
||||
print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename
|
||||
except IOError:
|
||||
print 'K4MobiDeDRM: Error reading kindle info files from config directory.'
|
||||
pass
|
||||
|
||||
# first try with book specifc pid from K4PC or K4M
|
||||
try:
|
||||
@@ -463,6 +520,25 @@ if not __name__ == "__main__" and inCalibre:
|
||||
of.write(unlocked_file)
|
||||
of.close()
|
||||
return of.name
|
||||
|
||||
# Now try alternate kindle info files
|
||||
if kInfoFiles:
|
||||
for infoFile in kInfoFiles:
|
||||
kindleDatabase = None
|
||||
try:
|
||||
title = ex.getBookTitle()
|
||||
exth = ex.getexthData()
|
||||
pid = getK4Pids(exth, title, infoFile)
|
||||
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||
except DrmException:
|
||||
pass
|
||||
except mobidedrm.DrmException:
|
||||
pass
|
||||
else:
|
||||
of = self.temporary_file('.mobi')
|
||||
of.write(unlocked_file)
|
||||
of.close()
|
||||
return of.name
|
||||
|
||||
# now try from the pid list
|
||||
pids = pidnums.split(',')
|
||||
|
||||
@@ -298,22 +298,25 @@ def CryptUnprotectData(encryptedData):
|
||||
return cleartext
|
||||
|
||||
# Locate and open the .kindle-info file
|
||||
def openKindleInfo():
|
||||
home = os.getenv('HOME')
|
||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||
poll = p1.wait('wait')
|
||||
results = p1.read()
|
||||
reslst = results.split('\n')
|
||||
kinfopath = 'NONE'
|
||||
cnt = len(reslst)
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('.kindle-info')
|
||||
if pp >= 0:
|
||||
kinfopath = resline
|
||||
break
|
||||
if not os.path.exists(kinfopath):
|
||||
raise K4MDrmException('Error: .kindle-info file can not be found')
|
||||
return open(kinfopath,'r')
|
||||
def openKindleInfo(kInfoFile=None):
|
||||
if kInfoFile == None:
|
||||
home = os.getenv('HOME')
|
||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||
poll = p1.wait('wait')
|
||||
results = p1.read()
|
||||
reslst = results.split('\n')
|
||||
kinfopath = 'NONE'
|
||||
cnt = len(reslst)
|
||||
for j in xrange(cnt):
|
||||
resline = reslst[j]
|
||||
pp = resline.find('.kindle-info')
|
||||
if pp >= 0:
|
||||
kinfopath = resline
|
||||
break
|
||||
if not os.path.exists(kinfopath):
|
||||
raise K4MDrmException('Error: .kindle-info file can not be found')
|
||||
return open(kinfopath,'r')
|
||||
else:
|
||||
return open(kInfoFile, 'r')
|
||||
@@ -101,7 +101,10 @@ CryptUnprotectData = CryptUnprotectData()
|
||||
#
|
||||
# Locate and open the Kindle.info file.
|
||||
#
|
||||
def openKindleInfo():
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
||||
def openKindleInfo(kInfoFile=None):
|
||||
if kInfoFile == None:
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
||||
else:
|
||||
return open(kInfoFile, 'r')
|
||||
|
||||
@@ -37,14 +37,17 @@
|
||||
# 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 K4DeDRM tools)
|
||||
# 0.17a - disabled the standalone plugin feature since a plugin can not import
|
||||
# 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
|
||||
|
||||
__version__ = '0.17'
|
||||
__version__ = '0.18'
|
||||
|
||||
import sys
|
||||
import struct
|
||||
@@ -127,10 +130,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 +185,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 +215,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 < 7:
|
||||
# multibyte utf8 data is included in the encryption for mobi_version 5 (& 6?)
|
||||
# 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,44 +295,3 @@ def main(argv=sys.argv):
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
#if not __name__ == "__main__":
|
||||
if False:
|
||||
|
||||
# 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