]> xmof Git - DeDRM.git/commitdiff
tools v2.4
authorApprentice Alf <apprenticealf@gmail.com>
Wed, 15 Dec 2010 21:21:51 +0000 (21:21 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Wed, 4 Mar 2015 18:19:08 +0000 (18:19 +0000)
23 files changed:
Calibre_Plugins/ineptepub_plugin.zip
Calibre_Plugins/ineptepub_plugin/ade_key.py
Calibre_Plugins/ineptepub_plugin/ineptepub_plugin.py
Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo [deleted file]
Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo [deleted file]
Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo [deleted file]
Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo [deleted file]
Calibre_Plugins/k4mobidedrm_plugin.zip
Calibre_Plugins/k4mobidedrm_plugin/k4mobidedrm_plugin.py
Calibre_Plugins/k4mobidedrm_plugin/mobidedrm.py
DeDRM_Macintosh_Application/DeDRM.app.txt
DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py
Kindle_Mobi_Tools/FindTopazEbooks.pyw [new file with mode: 0644]
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/K4MobiDeDRM.pyw
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/k4mobidedrm.py
Kindle_Mobi_Tools/K4_Mobi_DeDRM_Combined_Tool/lib/mobidedrm.py
Kindle_Mobi_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py
Kindle_Mobi_Tools/Kindle_4_PC_Unswindle/mobidedrm.py
Kindle_Mobi_Tools/MobiDeDRM.py
Mobi_Additional_Tools/lib/mobidedrm.py
Topaz_Tools/FindTopazEbooks.pyw [new file with mode: 0644]

index 31ffcde98994648d1e0bfb34e3b3d722c84cc592..562923be8eede92e83973507fa33f04ec3eaca62 100644 (file)
Binary files a/Calibre_Plugins/ineptepub_plugin.zip and b/Calibre_Plugins/ineptepub_plugin.zip differ
index 9c7f6cfd560afac8537c8a1a2b644506bdfa31ca..eb9ae3d05d05f3e97fe4849e3baf0039488a1d2a 100644 (file)
@@ -310,35 +310,31 @@ if iswindows:
 else:
 
     import xml.etree.ElementTree as etree
-    import Carbon.File
-    import Carbon.Folder
-    import Carbon.Folders
-    import MacOS
-    
-    ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat'
+    import subprocess
+
     NSMAP = {'adept': 'http://ns.adobe.com/adept',
              'enc': 'http://www.w3.org/2001/04/xmlenc#'}
-    
-    def find_folder(domain, dtype):
-        try:
-            fsref = Carbon.Folder.FSFindFolder(domain, dtype, False)
-            return Carbon.File.pathname(fsref)
-        except MacOS.Error:
-            return None
-    
-    def find_app_support_file(subpath):
-        dtype = Carbon.Folders.kApplicationSupportFolderType
-        for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain:
-            path = find_folder(domain, dtype)
-            if path is None:
-                continue
-            path = os.path.join(path, subpath)
-            if os.path.isfile(path):
-                return path
+
+    def findActivationDat():
+        home = os.getenv('HOME')
+        cmdline = 'find "' + home + '/Library/Application Support/Adobe/Digital Editions" -name "activation.dat"'
+        cmdline = cmdline.encode(sys.getfilesystemencoding())
+        p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+        out1, out2 = p2.communicate()
+        reslst = out1.split('\n')
+        cnt = len(reslst)
+        for j in xrange(cnt):
+            resline = reslst[j]
+            pp = resline.find('activation.dat')
+            if pp >= 0:
+                ActDatPath = resline
+                break
+        if os.path.exists(ActDatPath):
+            return ActDatPath
         return None
     
     def retrieve_key():
-        actpath = find_app_support_file(ACTIVATION_PATH)
+        actpath = findActivationDat()
         if actpath is None:
             raise ADEPTError("Could not locate ADE activation")
         tree = etree.parse(actpath)
index ea52f0a8670d49984f7a57e316a539ed57af9121..137cc2a5d12d3656898387def9cd4c27d6c0b853 100644 (file)
@@ -42,7 +42,9 @@
 # Revision history:
 #   0.1 - Initial release
 #   0.1.1 - Allow Windows users to make use of openssl if they have it installed.
-#          - Incorporated SomeUpdates zipfix routine.
+#         - Incorporated SomeUpdates zipfix routine.
+#   0.1.2 - Removed Carbon dependency for Mac users. Fixes an issue that was a
+#           result of Calibre changing to python 2.7.
 
 
 """
@@ -363,7 +365,7 @@ class IneptDeDRM(FileTypePlugin):
                                 Credit given to I <3 Cabbages for the original stand-alone scripts.'
     supported_platforms     = ['linux', 'osx', 'windows']
     author                  = 'DiapDealer'
-    version                 = (0, 1, 1)
+    version                 = (0, 1, 2)
     minimum_calibre_version = (0, 6, 44)  # Compiled python libraries cannot be imported in earlier versions.
     file_types              = set(['epub'])
     on_import               = True
@@ -420,7 +422,7 @@ class IneptDeDRM(FileTypePlugin):
                 try:
                     keydata = retrieve_key()
                     userkeys.append(keydata)
-                    keypath = os.path.join(confpath, 'adeptkey.der')
+                    keypath = os.path.join(confpath, 'calibre-adeptkey.der')
                     with open(keypath, 'wb') as f:
                         f.write(keydata)
                     print 'IneptEpub: Created keyfile from ADE install.'    
diff --git a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo
deleted file mode 100644 (file)
index be3ab04..0000000
Binary files a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo and /dev/null differ
diff --git a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo
deleted file mode 100644 (file)
index 5a82134..0000000
Binary files a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo and /dev/null differ
diff --git a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo
deleted file mode 100644 (file)
index c5a2ee0..0000000
Binary files a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo and /dev/null differ
diff --git a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo b/Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo
deleted file mode 100644 (file)
index 0f5325d..0000000
Binary files a/Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo and /dev/null differ
index 21a61cb62de5bcb47821fe1f4bc7f3417c819ab2..fe8263ae3d7a3b777be7d5519356553cbf04b420 100644 (file)
Binary files a/Calibre_Plugins/k4mobidedrm_plugin.zip and b/Calibre_Plugins/k4mobidedrm_plugin.zip differ
index 1ad83bc5e0391ea99cf150087dce97709e6af3a7..2b91046374e5b9ea746dbcdb483f1c070496a9b3 100644 (file)
@@ -475,7 +475,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, 1, 3)   # The version number of this plugin
+        version             = (0, 1, 4)   # 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
@@ -483,6 +483,24 @@ if not __name__ == "__main__" and inCalibre:
         def run(self, path_to_ebook):
             from calibre.gui2 import is_ok_to_use_qt
             from PyQt4.Qt import QMessageBox
+            
+            # Head Topaz files off at the pass and warn the user that they will NOT
+            # be decrypted. Changes the file extension from .azw or .prc to .tpz so
+            # Calibre can at least read the metadata properly and the user can find
+            # them by sorting on 'format'.
+            with open(path_to_ebook, 'rb') as f:
+                raw = f.read()
+                if raw.startswith('TPZ'):
+                    tf = self.temporary_file('.tpz')
+                    if is_ok_to_use_qt():
+                        d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "%s is a Topaz book. It will NOT be decrypted!" % path_to_ebook)
+                        d.show()
+                        d.raise_()
+                        d.exec_()
+                    tf.write(raw)
+                    tf.close
+                    return tf.name
+                
             global kindleDatabase
             global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
             if sys.platform.startswith('win'):
index 9f17a3bfda8313f0e58b6196afa92a807464bbbe..183432cc82b3b540d56227f4b707155cd648e95a 100644 (file)
@@ -39,8 +39,9 @@
 #         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 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
 
-__version__ = '0.19'
+__version__ = '0.20'
 
 import sys
 import struct
@@ -208,8 +209,8 @@ 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
+        if mobi_version < 7:
+            # multibyte utf8 data is included in the encryption for mobi_version 6 and below
             # so clear that byte so that we leave it to be decrypted.
             extra_data_flags &= 0xFFFE
 
index 099e256a55a758e9f39077686b7575bfa773aa04..63a97902114dc07e5491108274710cdea3afdcc2 100644 (file)
@@ -162,7 +162,7 @@ on unlockpdbfile(encryptedFile)
        set sourcePath to (POSIX path of file parent_folder) & fileName & "_Source/"
        set pmlzFilePath to POSIX path of file parent_folder & fileName & "_dedrmed.pmlz"
        if length of bnKeys is 0 then
-               GetKeys()
+               GetKeys(false)
        end if
        set shellresult to "Error: No eReader/Barnes & Noble Name:Number keys supplied. Can't decrypt."
        repeat with BNKey in bnKeys
@@ -367,7 +367,7 @@ end unlockepubfile
 
 on unlockpdffile(encryptedFile)
        --check it's an ePub file.
-       set PDFSig to "NOT_DF"
+       set PDFSig to "NOT_PDF"
        try
                set PDFSig to read file encryptedFile from 1 for 4
        end try
@@ -393,23 +393,7 @@ on unlockpdffile(encryptedFile)
        
        set decoded to "NO"
        -- first we must check we have a PDF script
-       if not fileexists(AdobePDFTool) then
-               set newFile to AdobePDFTool
-               try
-                       tell me to activate
-                       set newFile to ((choose file with prompt "Please find the ineptpdf script") as text)
-               end try
-               if not fileexists(newFile) then
-                       set ErrorCount to ErrorCount + 1
-                       set ErrorList to ErrorList & encryptedFile & " is a PDF file and no ineptpdf script found.
-
-"
-                       return
-               end if
-               -- set AdobePDFTool to POSIX path of file CopyToPrefs(newFile)
-               set AdobePDFTool to POSIX path of file newFile
-               WritePrefs()
-       end if
+       GetIneptPDF(false)
        if not fileexists(AdobePDFTool) then
                set ErrorCount to ErrorCount + 1
                set ErrorList to ErrorList & encryptedFile & " is a PDF file and no ineptpdf script found.
@@ -596,7 +580,7 @@ on GetPIDs()
 Enter any additional Mobipocket PIDs for your Mobipocket books one at a time:"
                        set FinishedButton to "No More"
                end if
-               set dialogresult to (display dialog DialogPrompt default answer "" buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript" default button 2)
+               set dialogresult to (display dialog DialogPrompt default answer "" buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript 2/5" default button 2)
                if button returned of dialogresult is "Add" then
                        set PID to text returned of dialogresult
                        set PIDlength to length of PID
@@ -657,7 +641,37 @@ To add extra Kindle Info files, click the Add
        end repeat
 end GetKindleInfoFiles
 
-on GetKeys()
+on GetIneptPDF(always)
+       if always or not fileexists(AdobePDFTool) then
+               set newFile to ""
+               try
+                       tell me to activate
+                       if (always) then
+                               set promptstring to "DeDRM Applescript 5/5
+"
+                       else
+                               set promptstring to "DeDRM Applescript
+"
+                       end if
+                       if fileexists(AdobePDFTool) then
+                               set promptstring to promptstring & "Please find the new ineptpdf script or click Cancel if the path shown is correct:
+" & ((POSIX file AdobePDFTool) as text)
+                       else
+                               set promptstring to promptstring & "Please find the ineptpdf script to be able to dedrm PDF files."
+                       end if
+                       set newFile to ((choose file with prompt promptstring default location POSIX file AdobePDFTool) as text)
+                       --on error errormessage
+                       --      display dialog errormessage
+               end try
+               if fileexists(newFile) then
+                       -- set AdobePDFTool to POSIX path of file CopyToPrefs(newFile)
+                       set AdobePDFTool to POSIX path of file newFile
+                       WritePrefs()
+               end if
+       end if
+end GetIneptPDF
+
+on GetKeys(running)
        set bnKeyText to ""
        repeat
                set BNKeystring to GetBNKeystring()
@@ -671,9 +685,12 @@ on GetKeys()
 Please enter any additional "
                        set FinishedButton to "No More"
                end if
-               set DialogPrompt to DialogPrompt & "eReader/Barnes & Noble Name:Number key pairs one at a time. If you're only decoding eReader files, the last 8 digits of the Number will do. The full 16 are only needed for Barnes & Noble ePubs. Only the last eight will be stored or displayed. Please separate the name and number with a colon and click \"Add\". Or to add a an already generated .b64 file, just click \"Add\" with nothing in the text field."
-               
-               set dialogresult to (display dialog DialogPrompt default answer bnKeyText buttons {"Delete All", "Add", FinishedButton} with title "DeDRM Applescript" default button 2)
+               set DialogPrompt to DialogPrompt & "eReader/Barnes & Noble Name,Number key pairs one at a time. If you're only decoding eReader files, the last 8 digits of the Number will do. The full 15 or 16 are only needed for Barnes & Noble ePubs. Only the last eight will be stored or displayed. Please separate the name and number with a comma and click \"Add\". Or to add a an already generated .b64 file, just click \"Add\" with nothing in the text field."
+               set dialogtitle to "DeDRM Applescript"
+               if (running) then
+                       set dialogtitle to dialogtitle & " 3/5"
+               end if
+               set dialogresult to (display dialog DialogPrompt default answer bnKeyText buttons {"Delete All", "Add", FinishedButton} with title dialogtitle default button 2)
                if button returned of dialogresult is "Add" then
                        set bnKeyText to text returned of dialogresult
                        if bnKeyText is "" then
@@ -686,17 +703,17 @@ Please enter any additional "
                                                display dialog message
                                        end if
                                end try
-                       else if not (bnKeyText contains ":") then
-                               display dialog "Name and Number must be separated by a colon (:)." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution
+                       else if not (bnKeyText contains ",") then
+                               display dialog "Name and Number must be separated by a comma (,)." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution
                        else
-                               set AppleScript's text item delimiters to ":"
+                               set AppleScript's text item delimiters to ","
                                set keyNumber to the last text item of bnKeyText
                                set keyName to (text items 1 through -2 of bnKeyText) as string
                                
-                               if ((length of keyNumber) = 16 or (length of keyNumber) = 8) and (length of keyName) > 0 then
+                               if ((length of keyNumber) = 16 or (length of keyNumber) = 15 or (length of keyNumber) = 8) and (length of keyName) > 0 then
                                        set shellresult to ""
                                        set keyfilepath to ""
-                                       if (length of keyNumber) = 16 then
+                                       if (length of keyNumber) = 16 or (length of keyNumber) = 15 then
                                                -- get the B&N key from this pair
                                                set shellresult to "no result"
                                                set scriptError to "Key Gen Script failed."
@@ -717,7 +734,7 @@ Please enter any additional "
                                                display dialog "Error generating key from this info, error message was: " & scriptError buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution
                                        end if
                                else
-                                       display dialog "Key numbers must be 8 or 16 characters long (no spaces) and the key name must not be absent." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution
+                                       display dialog "Key numbers must be 8 or 15 or 16 characters long (no spaces) and the key name must not be absent." buttons {"OK"} default button 1 with title "DeDRM Applescript" with icon caution
                                end if
                        end if
                else if button returned of dialogresult is "Delete All" then
@@ -759,7 +776,7 @@ on GetAdeptKeyFiles()
 To add extra key files (.der), click the AddÉ button."
                        set FinishedButton to "No More"
                end if
-               set dialogresult to (display dialog DialogPrompt buttons {"Forget All", "AddÉ", FinishedButton} with title "DeDRM Applescript" default button 2)
+               set dialogresult to (display dialog DialogPrompt buttons {"Forget All", "AddÉ", FinishedButton} with title "DeDRM Applescript 4/5" default button 2)
                if button returned of dialogresult is "AddÉ" then
                        try
                                set newFile to (choose file with prompt "Please select an Adept key file") as text
@@ -1044,12 +1061,13 @@ Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
 
 For more information, please refer to
 <http://unlicense.org/>
-" with title "DeDRM Applescript" buttons {"Cancel", "Continue"} default button 2
+" with title "DeDRM Applescript 1/5" buttons {"Cancel", "Continue"} default button 2
                ReadPrefs()
                GetPIDs()
-               GetKeys()
+               GetKeys(true)
                GetAdeptKey(true)
                GetAdeptKeyFiles()
+               GetIneptPDF(true)
                --GetKindleInfoFiles()
                WritePrefs()
        end if
index 330ec11a43d4f2adb78f9c2b7ed33478a4ae5051..31358b6cace9679eaa26871cd6b1bb4ae43cbae5 100644 (file)
@@ -24,7 +24,7 @@
        <key>CFBundleExecutable</key>
        <string>droplet</string>
        <key>CFBundleGetInfoString</key>
-       <string>DeDRM 1.2, Copyright Â© 2010 by Apprentice Alf.</string>
+       <string>DeDRM 1.3, Copyright Â© 2010 by Apprentice Alf.</string>
        <key>CFBundleIconFile</key>
        <string>droplet</string>
        <key>CFBundleInfoDictionaryVersion</key>
@@ -34,7 +34,7 @@
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
-       <string>1.2</string>
+       <string>1.3</string>
        <key>CFBundleSignature</key>
        <string>dplt</string>
        <key>LSMinimumSystemVersion</key>
@@ -46,9 +46,9 @@
                <key>name</key>
                <string>ScriptWindowState</string>
                <key>positionOfDivider</key>
-               <real>885</real>
+               <real>739</real>
                <key>savedFrame</key>
-               <string>1507 -64 1262 964 1440 -150 1680 1050 </string>
+               <string>1533 -24 1262 818 1440 -150 1680 1050 </string>
                <key>selectedTabView</key>
                <string>result</string>
        </dict>
index 2a37d51f38f1d0d8f221394181ef2ffea1509759..86a117320cd2b056ddd0b2bcc7e7bd2ef7723616 100644 (file)
Binary files a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt and b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/Scripts/main.scpt differ
index 9f17a3bfda8313f0e58b6196afa92a807464bbbe..183432cc82b3b540d56227f4b707155cd648e95a 100644 (file)
@@ -39,8 +39,9 @@
 #         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 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
 
-__version__ = '0.19'
+__version__ = '0.20'
 
 import sys
 import struct
@@ -208,8 +209,8 @@ 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
+        if mobi_version < 7:
+            # multibyte utf8 data is included in the encryption for mobi_version 6 and below
             # so clear that byte so that we leave it to be decrypted.
             extra_data_flags &= 0xFFFE
 
diff --git a/Kindle_Mobi_Tools/FindTopazEbooks.pyw b/Kindle_Mobi_Tools/FindTopazEbooks.pyw
new file mode 100644 (file)
index 0000000..6a0df30
--- /dev/null
@@ -0,0 +1,216 @@
+#!/usr/bin/env python
+
+# This is a simple tool to identify all Amazon Topaz ebooks in a specific directory.
+# There always seems to be confusion since Topaz books downloaded to K4PC/Mac can have
+# almost any extension (.azw, .azw1, .prc, tpz). While the .azw1 and .tpz extensions
+# are fairly easy to indentify, the others are not (without opening the files in an editor).
+
+# To run the tool with the GUI frontend, just double-click on the 'FindTopazFiles.pyw' file
+# and select the folder where all of the ebooks in question are located. Then click 'Search'.
+# The program will list the file names of the ebooks that are indentified as being Topaz.
+# You can then isolate those books and use the Topaz tools to decrypt and convert them.
+
+# You can also run the script from a command line... supplying the folder to search
+# as a parameter: python FindTopazEbooks.pyw "C:\My Folder" (change appropriately for
+# your particular O.S.)
+
+# ** NOTE: This program does NOT decrypt or modify Topaz files in any way. It simply identifies them.
+
+# PLEASE DO NOT PIRATE EBOOKS! 
+
+# We want all authors and publishers, and eBook stores to live
+# long and prosperous lives but at the same time  we just want to 
+# be able to read OUR books on whatever device we want and to keep 
+# readable for a long, long time
+
+#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, 
+#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates 
+#    and many many others
+
+# Revision history:
+#   1 - Initial release.
+
+from __future__ import with_statement
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import re
+import shutil
+import Tkinter
+import Tkconstants
+import tkFileDialog
+import tkMessageBox
+
+
+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))
+
+    def __str__(self):
+        return str(self.frame)
+
+
+def cli_main(argv=sys.argv, obj=None):
+    progname = os.path.basename(argv[0])
+    if len(argv) != 2:
+        print "usage: %s DIRECTORY" % (progname,)
+        return 1
+    
+    if obj == None:
+        print "\nTopaz search results:\n"
+    else:
+        obj.stext.insert(Tkconstants.END,"Topaz search results:\n\n")
+        
+    inpath = argv[1]
+    files = os.listdir(inpath)
+    filefilter = re.compile("(\.azw$)|(\.azw1$)|(\.prc$)|(\.tpz$)", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    
+    if files:
+        topazcount = 0
+        totalcount = 0
+        for filename in files:
+            with open(os.path.join(inpath, filename), 'rb') as f:
+                try:
+                    if f.read().startswith('TPZ'):
+                        f.close()
+                        basename, extension = os.path.splitext(filename)
+                        if obj == None:
+                            print "   %s is a Topaz formatted ebook." % filename
+                            """
+                            if extension == '.azw' or extension == '.prc':
+                                print "   renaming to %s" % (basename + '.tpz')
+                                shutil.move(os.path.join(inpath, filename),
+                                            os.path.join(inpath, basename + '.tpz'))
+                            """
+                        else:
+                            msg1 = "   %s is a Topaz formatted ebook.\n" % filename
+                            obj.stext.insert(Tkconstants.END,msg1)
+                            """
+                            if extension == '.azw' or extension == '.prc':
+                                msg2 = "   renaming to %s\n" % (basename + '.tpz')
+                                obj.stext.insert(Tkconstants.END,msg2)
+                                shutil.move(os.path.join(inpath, filename),
+                                            os.path.join(inpath, basename + '.tpz'))
+                            """
+                        topazcount += 1
+                except:
+                    if obj == None:
+                        print "   Error reading %s." % filename
+                    else:
+                        msg = "   Error reading or %s.\n" % filename
+                        obj.stext.insert(Tkconstants.END,msg)
+                    pass
+            totalcount += 1
+        if topazcount == 0:
+            if obj == None:
+                print "\nNo Topaz books found in %s." % inpath
+            else:
+                msg = "\nNo Topaz books found in %s.\n\n" % inpath
+                obj.stext.insert(Tkconstants.END,msg)
+        else:
+            if obj == None:
+                print "\n%i Topaz books found in %s\n%i total books checked.\n" % (topazcount, inpath, totalcount)
+            else:
+                msg = "\n%i Topaz books found in %s\n%i total books checked.\n\n" %(topazcount, inpath, totalcount)
+                obj.stext.insert(Tkconstants.END,msg)
+    else:
+        if obj == None:
+            print "No typical Topaz file extensions found in %s.\n" % inpath
+        else:
+            msg = "No typical Topaz file extensions found in %s.\n\n" % inpath
+            obj.stext.insert(Tkconstants.END,msg)
+    
+    return 0
+
+
+class DecryptionDialog(Tkinter.Frame):
+    def __init__(self, root):
+        Tkinter.Frame.__init__(self, root, border=5)
+        ltext='Search a directory for Topaz eBooks\n'        
+        self.status = Tkinter.Label(self, text=ltext)
+        self.status.pack(fill=Tkconstants.X, expand=1)
+        body = Tkinter.Frame(self)
+        body.pack(fill=Tkconstants.X, expand=1)
+        sticky = Tkconstants.E + Tkconstants.W
+        body.grid_columnconfigure(1, weight=2)
+        Tkinter.Label(body, text='Directory to Search').grid(row=1)
+        self.inpath = Tkinter.Entry(body, width=30)
+        self.inpath.grid(row=1, column=1, sticky=sticky)
+        button = Tkinter.Button(body, text="...", command=self.get_inpath)
+        button.grid(row=1, column=2)
+        msg1 = 'Topaz search results \n\n'
+        self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE,
+                                  height=15, width=60, wrap=Tkconstants.WORD)
+        self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky)
+        #self.stext.insert(Tkconstants.END,msg1)
+        buttons = Tkinter.Frame(self)
+        buttons.pack()
+  
+
+        self.botton = Tkinter.Button(
+            buttons, text="Search", width=10, command=self.search)
+        self.botton.pack(side=Tkconstants.LEFT)
+        Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+        self.button = Tkinter.Button(
+            buttons, text="Quit", width=10, command=self.quit)
+        self.button.pack(side=Tkconstants.RIGHT)
+        
+    def get_inpath(self):
+        cwd = os.getcwdu()
+        cwd = cwd.encode('utf-8')
+        inpath = tkFileDialog.askdirectory(
+            parent=None, title='Directory to search',
+            initialdir=cwd, initialfile=None)
+        if inpath:
+            inpath = os.path.normpath(inpath)
+            self.inpath.delete(0, Tkconstants.END)
+            self.inpath.insert(0, inpath)
+        return
+        
+    
+    def search(self):
+        inpath = self.inpath.get()
+        if not inpath or not os.path.exists(inpath):
+            self.status['text'] = 'Specified directory does not exist'
+            return
+        argv = [sys.argv[0], inpath]
+        self.status['text'] = 'Searching...'
+        self.botton.configure(state='disabled')
+        cli_main(argv, self)
+        self.status['text'] = 'Search a directory for Topaz files'
+        self.botton.configure(state='normal')
+
+        return
+
+
+def gui_main():
+    root = Tkinter.Tk()
+    root.title('Topaz eBook Finder')
+    root.resizable(True, False)
+    root.minsize(370, 0)
+    DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
+    root.mainloop()
+    return 0
+
+
+if __name__ == '__main__':
+    if len(sys.argv) > 1:
+        sys.exit(cli_main())
+    sys.exit(gui_main())
\ No newline at end of file
index d25c029d216d24cd5115d01a6bd9ecb4fe59cba7..c909eb32ab86279b3219f8be07b24468c065f1ad 100644 (file)
@@ -192,6 +192,19 @@ class MainDialog(Tkinter.Frame):
             self.status['text'] = 'Specified K4PC, K4M or Mobi eBook file does not exist'
             self.sbotton.configure(state='normal')
             return
+        # Head all Topaz ebooks off at the pass and warn user.
+        with open(mobipath, 'rb') as f:
+            raw = f.read()
+            if raw.startswith('TPZ'):
+                f.close()
+                tkMessageBox.showerror(
+                "K4MobiDeDRM",
+                "%s is a Topaz ebook. It cannot be decrypted with this tool. "
+                "You must use the Topaz Tools for this particular ebook." % mobipath)
+                self.status['text'] = 'The selected file is a Topaz ebook! Use Topaz tools.'
+                self.sbotton.configure(state='normal')
+                return
+            f.close()
         if not outpath:
             self.status['text'] = 'No output directory specified'
             self.sbotton.configure(state='normal')
index df05f89dc09ad5888512ee97b62fca01db540ce7..ee6ebd9a3c9ee5a1143a478f873e57522244fe50 100644 (file)
@@ -475,7 +475,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, 1, 2)   # The version number of this plugin
+        version             = (0, 1, 3)   # 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
@@ -483,6 +483,24 @@ if not __name__ == "__main__" and inCalibre:
         def run(self, path_to_ebook):
             from calibre.gui2 import is_ok_to_use_qt
             from PyQt4.Qt import QMessageBox
+            
+            # Head Topaz files off at the pass and warn the user that they will NOT
+            # be decrypted. Changes the file extension from .azw or .prc to .tpz so
+            # Calibre can at least read the metadata properly and the user can find
+            # them by sorting on 'format'.
+            with open(path_to_ebook, 'rb') as f:
+                raw = f.read()
+                if raw.startswith('TPZ'):
+                    tf = self.temporary_file('.tpz')
+                    if is_ok_to_use_qt():
+                        d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "%s is a Topaz book. It will NOT be decrypted!" % path_to_ebook)
+                        d.show()
+                        d.raise_()
+                        d.exec_()
+                    tf.write(raw)
+                    tf.close
+                    return tf.name
+                
             global kindleDatabase
             global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
             if sys.platform.startswith('win'):
index 9f17a3bfda8313f0e58b6196afa92a807464bbbe..183432cc82b3b540d56227f4b707155cd648e95a 100644 (file)
@@ -39,8 +39,9 @@
 #         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 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
 
-__version__ = '0.19'
+__version__ = '0.20'
 
 import sys
 import struct
@@ -208,8 +209,8 @@ 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
+        if mobi_version < 7:
+            # multibyte utf8 data is included in the encryption for mobi_version 6 and below
             # so clear that byte so that we leave it to be decrypted.
             extra_data_flags &= 0xFFFE
 
index 9f17a3bfda8313f0e58b6196afa92a807464bbbe..183432cc82b3b540d56227f4b707155cd648e95a 100644 (file)
@@ -39,8 +39,9 @@
 #         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 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
 
-__version__ = '0.19'
+__version__ = '0.20'
 
 import sys
 import struct
@@ -208,8 +209,8 @@ 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
+        if mobi_version < 7:
+            # multibyte utf8 data is included in the encryption for mobi_version 6 and below
             # so clear that byte so that we leave it to be decrypted.
             extra_data_flags &= 0xFFFE
 
index 9f17a3bfda8313f0e58b6196afa92a807464bbbe..183432cc82b3b540d56227f4b707155cd648e95a 100644 (file)
@@ -39,8 +39,9 @@
 #         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 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
 
-__version__ = '0.19'
+__version__ = '0.20'
 
 import sys
 import struct
@@ -208,8 +209,8 @@ 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
+        if mobi_version < 7:
+            # multibyte utf8 data is included in the encryption for mobi_version 6 and below
             # so clear that byte so that we leave it to be decrypted.
             extra_data_flags &= 0xFFFE
 
index 9f17a3bfda8313f0e58b6196afa92a807464bbbe..183432cc82b3b540d56227f4b707155cd648e95a 100644 (file)
@@ -39,8 +39,9 @@
 #         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 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
 
-__version__ = '0.19'
+__version__ = '0.20'
 
 import sys
 import struct
@@ -208,8 +209,8 @@ 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
+        if mobi_version < 7:
+            # multibyte utf8 data is included in the encryption for mobi_version 6 and below
             # so clear that byte so that we leave it to be decrypted.
             extra_data_flags &= 0xFFFE
 
index 9f17a3bfda8313f0e58b6196afa92a807464bbbe..183432cc82b3b540d56227f4b707155cd648e95a 100644 (file)
@@ -39,8 +39,9 @@
 #         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 - Corretion: It seems that multibyte entries are encrypted in a v6 file.
 
-__version__ = '0.19'
+__version__ = '0.20'
 
 import sys
 import struct
@@ -208,8 +209,8 @@ 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
+        if mobi_version < 7:
+            # multibyte utf8 data is included in the encryption for mobi_version 6 and below
             # so clear that byte so that we leave it to be decrypted.
             extra_data_flags &= 0xFFFE
 
diff --git a/Topaz_Tools/FindTopazEbooks.pyw b/Topaz_Tools/FindTopazEbooks.pyw
new file mode 100644 (file)
index 0000000..6a0df30
--- /dev/null
@@ -0,0 +1,216 @@
+#!/usr/bin/env python
+
+# This is a simple tool to identify all Amazon Topaz ebooks in a specific directory.
+# There always seems to be confusion since Topaz books downloaded to K4PC/Mac can have
+# almost any extension (.azw, .azw1, .prc, tpz). While the .azw1 and .tpz extensions
+# are fairly easy to indentify, the others are not (without opening the files in an editor).
+
+# To run the tool with the GUI frontend, just double-click on the 'FindTopazFiles.pyw' file
+# and select the folder where all of the ebooks in question are located. Then click 'Search'.
+# The program will list the file names of the ebooks that are indentified as being Topaz.
+# You can then isolate those books and use the Topaz tools to decrypt and convert them.
+
+# You can also run the script from a command line... supplying the folder to search
+# as a parameter: python FindTopazEbooks.pyw "C:\My Folder" (change appropriately for
+# your particular O.S.)
+
+# ** NOTE: This program does NOT decrypt or modify Topaz files in any way. It simply identifies them.
+
+# PLEASE DO NOT PIRATE EBOOKS! 
+
+# We want all authors and publishers, and eBook stores to live
+# long and prosperous lives but at the same time  we just want to 
+# be able to read OUR books on whatever device we want and to keep 
+# readable for a long, long time
+
+#  This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle, 
+#    unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates 
+#    and many many others
+
+# Revision history:
+#   1 - Initial release.
+
+from __future__ import with_statement
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import re
+import shutil
+import Tkinter
+import Tkconstants
+import tkFileDialog
+import tkMessageBox
+
+
+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))
+
+    def __str__(self):
+        return str(self.frame)
+
+
+def cli_main(argv=sys.argv, obj=None):
+    progname = os.path.basename(argv[0])
+    if len(argv) != 2:
+        print "usage: %s DIRECTORY" % (progname,)
+        return 1
+    
+    if obj == None:
+        print "\nTopaz search results:\n"
+    else:
+        obj.stext.insert(Tkconstants.END,"Topaz search results:\n\n")
+        
+    inpath = argv[1]
+    files = os.listdir(inpath)
+    filefilter = re.compile("(\.azw$)|(\.azw1$)|(\.prc$)|(\.tpz$)", re.IGNORECASE)
+    files = filter(filefilter.search, files)
+    
+    if files:
+        topazcount = 0
+        totalcount = 0
+        for filename in files:
+            with open(os.path.join(inpath, filename), 'rb') as f:
+                try:
+                    if f.read().startswith('TPZ'):
+                        f.close()
+                        basename, extension = os.path.splitext(filename)
+                        if obj == None:
+                            print "   %s is a Topaz formatted ebook." % filename
+                            """
+                            if extension == '.azw' or extension == '.prc':
+                                print "   renaming to %s" % (basename + '.tpz')
+                                shutil.move(os.path.join(inpath, filename),
+                                            os.path.join(inpath, basename + '.tpz'))
+                            """
+                        else:
+                            msg1 = "   %s is a Topaz formatted ebook.\n" % filename
+                            obj.stext.insert(Tkconstants.END,msg1)
+                            """
+                            if extension == '.azw' or extension == '.prc':
+                                msg2 = "   renaming to %s\n" % (basename + '.tpz')
+                                obj.stext.insert(Tkconstants.END,msg2)
+                                shutil.move(os.path.join(inpath, filename),
+                                            os.path.join(inpath, basename + '.tpz'))
+                            """
+                        topazcount += 1
+                except:
+                    if obj == None:
+                        print "   Error reading %s." % filename
+                    else:
+                        msg = "   Error reading or %s.\n" % filename
+                        obj.stext.insert(Tkconstants.END,msg)
+                    pass
+            totalcount += 1
+        if topazcount == 0:
+            if obj == None:
+                print "\nNo Topaz books found in %s." % inpath
+            else:
+                msg = "\nNo Topaz books found in %s.\n\n" % inpath
+                obj.stext.insert(Tkconstants.END,msg)
+        else:
+            if obj == None:
+                print "\n%i Topaz books found in %s\n%i total books checked.\n" % (topazcount, inpath, totalcount)
+            else:
+                msg = "\n%i Topaz books found in %s\n%i total books checked.\n\n" %(topazcount, inpath, totalcount)
+                obj.stext.insert(Tkconstants.END,msg)
+    else:
+        if obj == None:
+            print "No typical Topaz file extensions found in %s.\n" % inpath
+        else:
+            msg = "No typical Topaz file extensions found in %s.\n\n" % inpath
+            obj.stext.insert(Tkconstants.END,msg)
+    
+    return 0
+
+
+class DecryptionDialog(Tkinter.Frame):
+    def __init__(self, root):
+        Tkinter.Frame.__init__(self, root, border=5)
+        ltext='Search a directory for Topaz eBooks\n'        
+        self.status = Tkinter.Label(self, text=ltext)
+        self.status.pack(fill=Tkconstants.X, expand=1)
+        body = Tkinter.Frame(self)
+        body.pack(fill=Tkconstants.X, expand=1)
+        sticky = Tkconstants.E + Tkconstants.W
+        body.grid_columnconfigure(1, weight=2)
+        Tkinter.Label(body, text='Directory to Search').grid(row=1)
+        self.inpath = Tkinter.Entry(body, width=30)
+        self.inpath.grid(row=1, column=1, sticky=sticky)
+        button = Tkinter.Button(body, text="...", command=self.get_inpath)
+        button.grid(row=1, column=2)
+        msg1 = 'Topaz search results \n\n'
+        self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE,
+                                  height=15, width=60, wrap=Tkconstants.WORD)
+        self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky)
+        #self.stext.insert(Tkconstants.END,msg1)
+        buttons = Tkinter.Frame(self)
+        buttons.pack()
+  
+
+        self.botton = Tkinter.Button(
+            buttons, text="Search", width=10, command=self.search)
+        self.botton.pack(side=Tkconstants.LEFT)
+        Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+        self.button = Tkinter.Button(
+            buttons, text="Quit", width=10, command=self.quit)
+        self.button.pack(side=Tkconstants.RIGHT)
+        
+    def get_inpath(self):
+        cwd = os.getcwdu()
+        cwd = cwd.encode('utf-8')
+        inpath = tkFileDialog.askdirectory(
+            parent=None, title='Directory to search',
+            initialdir=cwd, initialfile=None)
+        if inpath:
+            inpath = os.path.normpath(inpath)
+            self.inpath.delete(0, Tkconstants.END)
+            self.inpath.insert(0, inpath)
+        return
+        
+    
+    def search(self):
+        inpath = self.inpath.get()
+        if not inpath or not os.path.exists(inpath):
+            self.status['text'] = 'Specified directory does not exist'
+            return
+        argv = [sys.argv[0], inpath]
+        self.status['text'] = 'Searching...'
+        self.botton.configure(state='disabled')
+        cli_main(argv, self)
+        self.status['text'] = 'Search a directory for Topaz files'
+        self.botton.configure(state='normal')
+
+        return
+
+
+def gui_main():
+    root = Tkinter.Tk()
+    root.title('Topaz eBook Finder')
+    root.resizable(True, False)
+    root.minsize(370, 0)
+    DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
+    root.mainloop()
+    return 0
+
+
+if __name__ == '__main__':
+    if len(sys.argv) > 1:
+        sys.exit(cli_main())
+    sys.exit(gui_main())
\ No newline at end of file