about summary refs log tree commit diff
diff options
context:
space:
mode:
authorCiro Mattia Gonano <ciromattia@gmail.com>2013-11-14 15:49:46 +0100
committerCiro Mattia Gonano <ciromattia@gmail.com>2013-11-14 15:49:46 +0100
commitd33c53b691cdd34a622f72ed2a8add12af5b30a7 (patch)
tree4d339c15a3f13ba4966f9f69d0ac2efc08e6c303
parentCode cleanup (diff)
downloadkcc-d33c53b691cdd34a622f72ed2a8add12af5b30a7.tar.gz
kcc-d33c53b691cdd34a622f72ed2a8add12af5b30a7.tar.bz2
kcc-d33c53b691cdd34a622f72ed2a8add12af5b30a7.zip
Version 4.0 - first draft for Python3 conversion
-rw-r--r--kcc.iss2
-rw-r--r--kcc.py12
-rw-r--r--kcc/KCC_gui.py181
-rw-r--r--kcc/KCC_rc.py10
-rw-r--r--kcc/KCC_ui.py2
-rw-r--r--kcc/KCC_ui_linux.py2
-rw-r--r--kcc/KCC_ui_osx.py2
-rw-r--r--kcc/__init__.py2
-rw-r--r--kcc/__main__.py2
-rw-r--r--kcc/cbxarchive.py8
-rwxr-xr-xkcc/comic2ebook.py76
-rw-r--r--kcc/comic2panel.py32
-rwxr-xr-xkcc/image.py24
-rw-r--r--kcc/kindlesplit.py24
-rw-r--r--kcc/pdfjpgextract.py2
-rw-r--r--kcc/rarfile.py6
-rw-r--r--requirements.txt4
-rw-r--r--setup.py8
-rw-r--r--setup.sh2
19 files changed, 210 insertions, 191 deletions
diff --git a/kcc.iss b/kcc.iss
index fe6b807..7d5bffd 100644
--- a/kcc.iss
+++ b/kcc.iss
@@ -1,5 +1,5 @@
 #define MyAppName "Kindle Comic Converter"
-#define MyAppVersion "3.6"
+#define MyAppVersion "4.0"
 #define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
 #define MyAppURL "http://kcc.vulturis.eu/"
 #define MyAppExeName "KCC.exe"
diff --git a/kcc.py b/kcc.py
index 4d60f5e..a6f9c23 100644
--- a/kcc.py
+++ b/kcc.py
@@ -18,7 +18,7 @@
 # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 
-__version__ = '3.6'
+__version__ = '4.0'
 __license__ = 'ISC'
 __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
 __docformat__ = 'restructuredtext en'
@@ -29,13 +29,13 @@ try:
     #noinspection PyUnresolvedReferences
     from PyQt4 import QtCore, QtGui, QtNetwork
 except ImportError:
-    print "ERROR: PyQT4 is not installed!"
+    print("ERROR: PyQT4 is not installed!")
     if sys.platform.startswith('linux'):
-        import Tkinter
-        import tkMessageBox
-        importRoot = Tkinter.Tk()
+        import tkinter
+        import tkinter.messagebox
+        importRoot = tkinter.Tk()
         importRoot.withdraw()
-        tkMessageBox.showerror("KCC - Error", "PyQT4 is not installed!")
+        tkinter.messagebox.showerror("KCC - Error", "PyQT4 is not installed!")
     exit(1)
 from kcc import KCC_gui
 from multiprocessing import freeze_support
diff --git a/kcc/KCC_gui.py b/kcc/KCC_gui.py
index 3713f65..0142767 100644
--- a/kcc/KCC_gui.py
+++ b/kcc/KCC_gui.py
@@ -17,7 +17,7 @@
 # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 
-__version__ = '3.6'
+__version__ = '4.0'
 __license__ = 'ISC'
 __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
 __docformat__ = 'restructuredtext en'
@@ -25,39 +25,38 @@ __docformat__ = 'restructuredtext en'
 import os
 import sys
 import traceback
-import urllib2
+import urllib.request, urllib.error, urllib.parse
 import socket
-import comic2ebook
-import kindlesplit
-from string import split
+from . import comic2ebook
+from . import kindlesplit
 from time import sleep
 from shutil import move
-from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
-from SocketServer import ThreadingMixIn
-from image import ProfileData
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from socketserver import ThreadingMixIn
+from .image import ProfileData
 from subprocess import STDOUT, PIPE
 from PyQt4 import QtGui, QtCore
 from xml.dom.minidom import parse
-from HTMLParser import HTMLParser
-from KCC_rc_web import WebContent
+from html.parser import HTMLParser
+from .KCC_rc_web import WebContent
 try:
     #noinspection PyUnresolvedReferences
     from psutil import TOTAL_PHYMEM, Popen
 except ImportError:
-    print "ERROR: Psutil is not installed!"
+    print("ERROR: Psutil is not installed!")
     if sys.platform.startswith('linux'):
-        import Tkinter
-        import tkMessageBox
-        importRoot = Tkinter.Tk()
+        import tkinter
+        import tkinter.messagebox
+        importRoot = tkinter.Tk()
         importRoot.withdraw()
-        tkMessageBox.showerror("KCC - Error", "Psutil is not installed!")
+        tkinter.messagebox.showerror("KCC - Error", "Psutil is not installed!")
     exit(1)
 if sys.platform.startswith('darwin'):
-    import KCC_ui_osx as KCC_ui
+    from . import KCC_ui_osx as KCC_ui
 elif sys.platform.startswith('linux'):
-    import KCC_ui_linux as KCC_ui
+    from . import KCC_ui_linux as KCC_ui
 else:
-    import KCC_ui
+    from . import KCC_ui
 
 
 class Icons:
@@ -130,8 +129,8 @@ class WebServerHandler(BaseHTTPRequestHandler):
                                  '<p style="font-size:50px">- <img style="vertical-align: middle" '
                                  'alt="KCC Logo" src="' + GUI.webContent.logo + '" /> -</p>\n')
                 if len(GUI.completedWork) > 0 and not GUI.conversionAlive:
-                    for key in sorted(GUI.completedWork.iterkeys()):
-                        self.wfile.write('<p><a href="' + key + '">' + split(key, '.')[0] + '</a></p>\n')
+                    for key in sorted(GUI.completedWork.keys()):
+                        self.wfile.write('<p><a href="' + key + '">' + key.split('.')[0] + '</a></p>\n')
                 else:
                     self.wfile.write('<p style="font-weight: bold">No downloads are available.<br/>'
                                      'Convert some files and refresh this page.</p>\n')
@@ -139,7 +138,7 @@ class WebServerHandler(BaseHTTPRequestHandler):
                                  '</body>\n'
                                  '</html>\n')
             elif sendReply:
-                outputFile = GUI.completedWork[urllib2.unquote(self.path[1:])].decode('utf-8')
+                outputFile = GUI.completedWork[urllib.parse.unquote(self.path[1:])].decode('utf-8')
                 fp = open(outputFile, 'rb')
                 self.send_response(200)
                 self.send_header('Content-type', mimetype)
@@ -173,7 +172,7 @@ class WebServerThread(QtCore.QThread):
         try:
             # Sweet cross-platform one-liner to get LAN ip address
             lIP = [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1][0]
-        except StandardError:
+        except Exception:
             # Sadly it can fail on some Linux configurations
             lIP = None
         try:
@@ -186,7 +185,7 @@ class WebServerThread(QtCore.QThread):
                 self.emit(QtCore.SIGNAL("addMessage"), '<b>Content server</b> started on port 4242.', 'info')
             while self.running:
                 self.server.handle_request()
-        except StandardError:
+        except Exception:
             self.emit(QtCore.SIGNAL("addMessage"), '<b>Content server</b> failed to start!', 'error')
 
     def stop(self):
@@ -202,9 +201,9 @@ class VersionThread(QtCore.QThread):
 
     def run(self):
         try:
-            XML = urllib2.urlopen('http://kcc.vulturis.eu/Version.php')
+            XML = urllib.request.urlopen('http://kcc.vulturis.eu/Version.php')
             XML = parse(XML)
-        except StandardError:
+        except Exception:
             return
         latestVersion = XML.childNodes[0].getElementsByTagName('latest')[0].childNodes[0].toxml()
         if tuple(map(int, (latestVersion.split(".")))) > tuple(map(int, (__version__.split(".")))):
@@ -253,9 +252,10 @@ class KindleGenThread(QtCore.QRunnable):
         kindlegenError = ''
         try:
             if os.path.getsize(self.work) < 367001600:
-                output = Popen('kindlegen -locale en "' + self.work.encode(sys.getfilesystemencoding()) + '"',
-                               stdout=PIPE, stderr=STDOUT, shell=True)
+                output = Popen('kindlegen -locale en "' + self.work.encode(sys.getfilesystemencoding()).decode('utf-8')
+                               + '"', stdout=PIPE, stderr=STDOUT, shell=True)
                 for line in output.stdout:
+                    line = line.decode('utf-8')
                     # ERROR: Generic error
                     if "Error(" in line:
                         kindlegenErrorCode = 1
@@ -269,9 +269,10 @@ class KindleGenThread(QtCore.QRunnable):
                 # ERROR: EPUB too big
                 kindlegenErrorCode = 23026
             self.signals.result.emit([kindlegenErrorCode, kindlegenError, self.work])
-        except StandardError:
-            # ERROR: Unknown generic error
+        except Exception as err:
+            # ERROR: KCC unknown generic error
             kindlegenErrorCode = 1
+            kindlegenError = format(err)
             self.signals.result.emit([kindlegenErrorCode, kindlegenError, self.work])
 
 
@@ -297,8 +298,9 @@ class KindleUnpackThread(QtCore.QRunnable):
             mobisplit = kindlesplit.mobi_split(mobiPath + '_toclean', newKindle)
             open(mobiPath, 'wb').write(mobisplit.getResult())
             self.signals.result.emit([True])
-        except StandardError:
-            self.signals.result.emit([False])
+        except Exception as err:
+            traceback.print_exc()
+            self.signals.result.emit([False, format(err)])
 
 
 class WorkerThread(QtCore.QThread):
@@ -385,7 +387,7 @@ class WorkerThread(QtCore.QThread):
         for i in range(GUI.JobList.count()):
             # Make sure that we don't consider any system message as job to do
             if GUI.JobList.item(i).icon().isNull():
-                currentJobs.append(unicode(GUI.JobList.item(i).text()))
+                currentJobs.append(str(GUI.JobList.item(i).text()))
         GUI.JobList.clear()
         for job in currentJobs:
             sleep(0.5)
@@ -466,42 +468,45 @@ class WorkerThread(QtCore.QThread):
                     if self.kindlegenErrorCode[0] == 0:
                         GUI.progress.content = ''
                         self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI files... <b>Done!</b>', 'info', True)
-                        self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI files', 'info')
-                        GUI.progress.content = 'Cleaning MOBI files'
-                        self.workerOutput = []
-                        # Multithreading KindleUnpack in current form is a waste of resources.
-                        # Unless we higly optimise KindleUnpack or drop 32bit support this will not change.
-                        self.pool.setMaxThreadCount(1)
-                        for item in outputPath:
-                            worker = KindleUnpackThread([item, profile])
-                            worker.signals.result.connect(self.addResult)
-                            self.pool.start(worker)
-                        self.pool.waitForDone()
-                        sleep(0.5)
-                        for success in self.workerOutput:
-                            if not success:
-                                self.errors = True
-                                break
-                        if not self.errors:
-                            for item in outputPath:
-                                GUI.progress.content = ''
-                                mobiPath = item.replace('.epub', '.mobi')
-                                os.remove(mobiPath + '_toclean')
-                                GUI.completedWork[os.path.basename(mobiPath).encode('utf-8')] = \
-                                    mobiPath.encode('utf-8')
-                                self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI files... <b>Done!</b>', 'info',
-                                          True)
-                        else:
-                            GUI.progress.content = ''
-                            for item in outputPath:
-                                mobiPath = item.replace('.epub', '.mobi')
-                                if os.path.exists(mobiPath):
-                                    os.remove(mobiPath)
-                                if os.path.exists(mobiPath + '_toclean'):
-                                    os.remove(mobiPath + '_toclean')
-                            self.emit(QtCore.SIGNAL("addMessage"), 'KindleUnpack failed to clean MOBI file!', 'error')
-                            self.emit(QtCore.SIGNAL("addTrayMessage"), 'KindleUnpack failed to clean MOBI file!',
-                                      'Critical')
+                        self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI files is currently disabled in this branch', 'info')
+                        GUI.progress.content = 'Cleaning MOBI files is currently disabled in this branch'
+                        #self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI files', 'info')
+                        #GUI.progress.content = 'Cleaning MOBI files'
+                        #self.workerOutput = []
+                        ## Multithreading KindleUnpack in current form is a waste of resources.
+                        ## Unless we higly optimise KindleUnpack or drop 32bit support this will not change.
+                        #self.pool.setMaxThreadCount(1)
+                        #for item in outputPath:
+                        #    worker = KindleUnpackThread([item, profile])
+                        #    worker.signals.result.connect(self.addResult)
+                        #    self.pool.start(worker)
+                        #self.pool.waitForDone()
+                        #sleep(0.5)
+                        #for success in self.workerOutput:
+                        #    if not success[0]:
+                        #        self.errors = True
+                        #        print(success[1])
+                        #        break
+                        #if not self.errors:
+                        #    for item in outputPath:
+                        #        GUI.progress.content = ''
+                        #        mobiPath = item.replace('.epub', '.mobi')
+                        #        os.remove(mobiPath + '_toclean')
+                        #        GUI.completedWork[os.path.basename(mobiPath).encode('utf-8')] = \
+                        #            mobiPath.encode('utf-8')
+                        #        self.emit(QtCore.SIGNAL("addMessage"), 'Cleaning MOBI files... <b>Done!</b>', 'info',
+                        #                  True)
+                        #else:
+                        #    GUI.progress.content = ''
+                        #    for item in outputPath:
+                        #        mobiPath = item.replace('.epub', '.mobi')
+                        #        if os.path.exists(mobiPath):
+                        #            os.remove(mobiPath)
+                        #        if os.path.exists(mobiPath + '_toclean'):
+                        #            os.remove(mobiPath + '_toclean')
+                        #    self.emit(QtCore.SIGNAL("addMessage"), 'KindleUnpack failed to clean MOBI file!', 'error')
+                        #    self.emit(QtCore.SIGNAL("addTrayMessage"), 'KindleUnpack failed to clean MOBI file!',
+                        #              'Critical')
                     else:
                         GUI.progress.content = ''
                         epubSize = (os.path.getsize(self.kindlegenErrorCode[2]))/1024/1024
@@ -571,10 +576,10 @@ class KCCGUI(KCC_ui.Ui_KCC):
         else:
             dnames = ""
         for dname in dnames:
-            if unicode(dname) != "":
+            if str(dname) != "":
                 if sys.platform == 'win32':
                     dname = dname.replace('/', '\\')
-                self.lastPath = os.path.abspath(os.path.join(unicode(dname), os.pardir))
+                self.lastPath = os.path.abspath(os.path.join(str(dname), os.pardir))
                 GUI.JobList.addItem(dname)
         MW.setFocus()
 
@@ -597,8 +602,8 @@ class KCCGUI(KCC_ui.Ui_KCC):
                 fnames = QtGui.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
                                                             '*.cbz *.zip *.pdf')
         for fname in fnames:
-            if unicode(fname) != "":
-                self.lastPath = os.path.abspath(os.path.join(unicode(fname), os.pardir))
+            if str(fname) != "":
+                self.lastPath = os.path.abspath(os.path.join(str(fname), os.pardir))
                 GUI.JobList.addItem(fname)
 
     def clearJobs(self):
@@ -884,19 +889,19 @@ class KCCGUI(KCC_ui.Ui_KCC):
         self.settings.setValue('currentFormat', GUI.FormatBox.currentIndex())
         self.settings.setValue('currentMode', self.currentMode)
         self.settings.setValue('firstStart', False)
-        self.settings.setValue('options', QtCore.QVariant({'MangaBox': GUI.MangaBox.checkState(),
-                                                           'RotateBox': GUI.RotateBox.checkState(),
-                                                           'QualityBox': GUI.QualityBox.checkState(),
-                                                           'ProcessingBox': GUI.ProcessingBox.checkState(),
-                                                           'UpscaleBox': GUI.UpscaleBox.checkState(),
-                                                           'NoRotateBox': GUI.NoRotateBox.checkState(),
-                                                           'BorderBox': GUI.BorderBox.checkState(),
-                                                           'WebtoonBox': GUI.WebtoonBox.checkState(),
-                                                           'NoDitheringBox': GUI.NoDitheringBox.checkState(),
-                                                           'ColorBox': GUI.ColorBox.checkState(),
-                                                           'customWidth': GUI.customWidth.text(),
-                                                           'customHeight': GUI.customHeight.text(),
-                                                           'GammaSlider': float(self.GammaValue)*100}))
+        self.settings.setValue('options', {'MangaBox': GUI.MangaBox.checkState(),
+                                           'RotateBox': GUI.RotateBox.checkState(),
+                                           'QualityBox': GUI.QualityBox.checkState(),
+                                           'ProcessingBox': GUI.ProcessingBox.checkState(),
+                                           'UpscaleBox': GUI.UpscaleBox.checkState(),
+                                           'NoRotateBox': GUI.NoRotateBox.checkState(),
+                                           'BorderBox': GUI.BorderBox.checkState(),
+                                           'WebtoonBox': GUI.WebtoonBox.checkState(),
+                                           'NoDitheringBox': GUI.NoDitheringBox.checkState(),
+                                           'ColorBox': GUI.ColorBox.checkState(),
+                                           'customWidth': GUI.customWidth.text(),
+                                           'customHeight': GUI.customHeight.text(),
+                                           'GammaSlider': float(self.GammaValue)*100})
         self.settings.sync()
 
     def handleMessage(self, message):
@@ -947,8 +952,7 @@ class KCCGUI(KCC_ui.Ui_KCC):
         self.currentMode = self.settings.value('currentMode', 1, type=int)
         self.currentFormat = self.settings.value('currentFormat', 0, type=int)
         self.firstStart = self.settings.value('firstStart', True, type=bool)
-        self.options = self.settings.value('options', QtCore.QVariant({'GammaSlider': 0}))
-        self.options = self.options.toPyObject()
+        self.options = self.settings.value('options', {'GammaSlider': 0})
         self.worker = WorkerThread()
         self.versionCheck = VersionThread()
         self.contentServer = WebServerThread()
@@ -996,7 +1000,8 @@ class KCCGUI(KCC_ui.Ui_KCC):
             formats = ['MOBI', 'EPUB', 'CBZ']
             versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
             for line in versionCheck.stdout:
-                if "Amazon kindlegen" in line:
+                line = line.decode("utf-8")
+                if 'Amazon kindlegen' in line:
                     versionCheck = line.split('V')[1].split(' ')[0]
                     if tuple(map(int, (versionCheck.split(".")))) < tuple(map(int, ('2.9'.split(".")))):
                         self.addMessage('Your <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
diff --git a/kcc/KCC_rc.py b/kcc/KCC_rc.py
index 844ad65..f7307d2 100644
--- a/kcc/KCC_rc.py
+++ b/kcc/KCC_rc.py
@@ -2,14 +2,14 @@
 
 # Resource object code
 #
-# Created: N 6. paź 13:26:15 2013
-#      by: The Resource Compiler for PyQt (Qt v4.8.5)
+# Created: mer nov 13 21:20:16 2013
+#      by: The Resource Compiler for PyQt (Qt v4.8.6)
 #
 # WARNING! All changes made in this file will be lost!
 
 from PyQt4 import QtCore
 
-qt_resource_data = "\
+qt_resource_data = b"\
 \x00\x00\x09\x59\
 \x89\
 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
@@ -8360,7 +8360,7 @@ qt_resource_data = "\
 \xfc\xce\x76\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
 "
 
-qt_resource_name = "\
+qt_resource_name = b"\
 \x00\x07\
 \x0a\xcc\xf9\x43\
 \x00\x44\
@@ -8444,7 +8444,7 @@ qt_resource_name = "\
 \x00\x74\x00\x68\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\
 "
 
-qt_resource_struct = "\
+qt_resource_struct = b"\
 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\
 \x00\x00\x00\x28\x00\x02\x00\x00\x00\x01\x00\x00\x00\x17\
 \x00\x00\x00\x36\x00\x02\x00\x00\x00\x01\x00\x00\x00\x11\
diff --git a/kcc/KCC_ui.py b/kcc/KCC_ui.py
index a491c89..65c71a6 100644
--- a/kcc/KCC_ui.py
+++ b/kcc/KCC_ui.py
@@ -321,4 +321,4 @@ class Ui_KCC(object):
         self.ActionBasic.setText(_translate("KCC", "Basic", None))
         self.ActionAdvanced.setText(_translate("KCC", "Advanced", None))
 
-import KCC_rc
+from . import KCC_rc
diff --git a/kcc/KCC_ui_linux.py b/kcc/KCC_ui_linux.py
index 9fbdea3..c99227d 100644
--- a/kcc/KCC_ui_linux.py
+++ b/kcc/KCC_ui_linux.py
@@ -390,4 +390,4 @@ class Ui_KCC(object):
         self.ActionBasic.setText(_translate("KCC", "Basic", None))
         self.ActionAdvanced.setText(_translate("KCC", "Advanced", None))
 
-import KCC_rc
+from . import KCC_rc
diff --git a/kcc/KCC_ui_osx.py b/kcc/KCC_ui_osx.py
index c0910b8..175a3c4 100644
--- a/kcc/KCC_ui_osx.py
+++ b/kcc/KCC_ui_osx.py
@@ -408,4 +408,4 @@ class Ui_KCC(object):
         self.ActionBasic.setText(_translate("KCC", "Basic", None))
         self.ActionAdvanced.setText(_translate("KCC", "Advanced", None))
 
-import KCC_rc
+from . import KCC_rc
diff --git a/kcc/__init__.py b/kcc/__init__.py
index 506ea76..ca08060 100644
--- a/kcc/__init__.py
+++ b/kcc/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '3.6'
+__version__ = '4.0'
 __license__ = 'ISC'
 __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
 __docformat__ = 'restructuredtext en'
\ No newline at end of file
diff --git a/kcc/__main__.py b/kcc/__main__.py
new file mode 100644
index 0000000..03034f1
--- /dev/null
+++ b/kcc/__main__.py
@@ -0,0 +1,2 @@
+from kcc.comic2ebook import main
+main()
\ No newline at end of file
diff --git a/kcc/cbxarchive.py b/kcc/cbxarchive.py
index e083d28..ae70a0a 100644
--- a/kcc/cbxarchive.py
+++ b/kcc/cbxarchive.py
@@ -21,7 +21,7 @@ __docformat__ = 'restructuredtext en'
 
 import os
 import zipfile
-import rarfile
+from . import rarfile
 import locale
 from subprocess import STDOUT, PIPE
 from psutil import Popen
@@ -52,7 +52,7 @@ class CBxArchive:
             elif f.endswith('/'):
                 try:
                     os.makedirs(os.path.join(targetdir, f))
-                except StandardError:
+                except Exception:
                     pass  # the dir exists so we are going to extract the images only.
             else:
                 filelist.append(f)
@@ -67,7 +67,7 @@ class CBxArchive:
             elif f.endswith('/'):
                 try:
                     os.makedirs(os.path.join(targetdir, f))
-                except StandardError:
+                except Exception:
                     pass  # the dir exists so we are going to extract the images only.
             else:
                 filelist.append(f.encode(locale.getpreferredencoding()))
@@ -85,7 +85,7 @@ class CBxArchive:
             raise OSError
 
     def extract(self, targetdir):
-        print "\n" + targetdir + "\n"
+        print("\n" + targetdir + "\n")
         if self.compressor == 'rar':
             self.extractCBR(targetdir)
         elif self.compressor == 'zip':
diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py
index 09e5ad9..f54e28e 100755
--- a/kcc/comic2ebook.py
+++ b/kcc/comic2ebook.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 #
 # Copyright (c) 2012-2013 Ciro Mattia Gonano <ciromattia@gmail.com>
@@ -18,7 +18,7 @@
 # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 #
-__version__ = '3.6'
+__version__ = '4.0'
 __license__ = 'ISC'
 __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
 __docformat__ = 'restructuredtext en'
@@ -35,14 +35,15 @@ from optparse import OptionParser, OptionGroup
 from multiprocessing import Pool, freeze_support
 from xml.dom.minidom import parse
 from uuid import uuid4
+from slugify import slugify
 try:
     from PyQt4 import QtCore
 except ImportError:
     QtCore = None
-import comic2panel
-import image
-import cbxarchive
-import pdfjpgextract
+from . import comic2panel
+from . import image
+from . import cbxarchive
+from . import pdfjpgextract
 
 
 def buildHTML(path, imgfile):
@@ -172,7 +173,6 @@ def buildHTML(path, imgfile):
 
 def buildNCX(dstdir, title, chapters):
     options.uuid = str(uuid4())
-    options.uuid = options.uuid.encode('utf-8')
     ncxfile = os.path.join(dstdir, 'OEBPS', 'toc.ncx')
     f = open(ncxfile, "w")
     f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
@@ -186,7 +186,7 @@ def buildNCX(dstdir, title, chapters):
                   "<meta name=\"dtb:maxPageNumber\" content=\"0\"/>\n",
                   "<meta name=\"generated\" content=\"true\"/>\n",
                   "</head>\n",
-                  "<docTitle><text>", title.encode('utf-8'), "</text></docTitle>\n",
+                  "<docTitle><text>", title, "</text></docTitle>\n",
                   "<navMap>"
                   ])
     for chapter in chapters:
@@ -195,7 +195,7 @@ def buildNCX(dstdir, title, chapters):
             title = os.path.basename(folder)
         filename = getImageFileName(os.path.join(folder, chapter[1]))
         f.write("<navPoint id=\"" + folder.replace('/', '_').replace('\\', '_') + "\"><navLabel><text>"
-                + title.encode('utf-8') + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/")
+                + title + "</text></navLabel><content src=\"" + filename[0].replace("\\", "/")
                 + ".html\"/></navPoint>\n")
     f.write("</navMap>\n</ncx>")
     f.close()
@@ -216,11 +216,11 @@ def buildOPF(dstdir, title, filelist, cover=None):
                   "xmlns=\"http://www.idpf.org/2007/opf\">\n",
                   "<metadata xmlns:opf=\"http://www.idpf.org/2007/opf\" ",
                   "xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n",
-                  "<dc:title>", title.encode('utf-8'), "</dc:title>\n",
+                  "<dc:title>", title, "</dc:title>\n",
                   "<dc:language>en-US</dc:language>\n",
                   "<dc:identifier id=\"BookID\" opf:scheme=\"UUID\">", options.uuid, "</dc:identifier>\n"])
     for author in options.authors:
-        f.writelines(["<dc:Creator>", author.encode('utf-8'), "</dc:Creator>\n"])
+        f.writelines(["<dc:Creator>", author, "</dc:Creator>\n"])
     f.writelines(["<meta name=\"generator\" content=\"KindleComicConverter-" + __version__ + "\"/>\n",
                   "<meta name=\"RegionMagnification\" content=\"true\"/>\n",
                   "<meta name=\"region-mag\" content=\"true\"/>\n",
@@ -356,9 +356,9 @@ def fileImgProcess(work):
         dirpath = work[1]
         opt = work[2]
         if opt.verbose:
-            print "Optimizing " + afile + " for " + opt.profile
+            print("Optimizing " + afile + " for " + opt.profile)
         else:
-            print ".",
+            print(".", end=' ')
         img = image.ComicPage(os.path.join(dirpath, afile), opt.profileData)
         if opt.quality == 2:
             wipe = False
@@ -370,7 +370,7 @@ def fileImgProcess(work):
             split = img.splitPage(dirpath, opt.righttoleft, opt.rotate)
         if split is not None:
             if opt.verbose:
-                print "Splitted " + afile
+                print("Splitted " + afile)
             img0 = image.ComicPage(split[0], opt.profileData)
             applyImgOptimization(img0, opt)
             img0.saveToDir(dirpath, opt.forcepng, opt.forcecolor, wipe)
@@ -394,7 +394,9 @@ def fileImgProcess(work):
                     img2.rotated = True
                 applyImgOptimization(img2, opt, 0)
                 img2.saveToDir(dirpath, opt.forcepng, opt.forcecolor, True)
-    except StandardError:
+    except Exception:
+        import traceback
+        traceback.print_tb(sys.exc_info()[2])
         return str(sys.exc_info()[1])
 
 
@@ -565,7 +567,7 @@ def getWorkFolder(afile):
                 path = cbx.extract(workdir)
             except OSError:
                 rmtree(workdir, True)
-                print 'UnRAR/7za not found or file failed to extract!'
+                print('UnRAR/7za not found or file failed to extract!')
                 sys.exit(21)
         else:
             rmtree(workdir, True)
@@ -592,7 +594,7 @@ def checkComicInfo(path, originalPath):
     if os.path.exists(xmlPath):
         try:
             xml = parse(xmlPath)
-        except StandardError:
+        except Exception:
             os.remove(xmlPath)
             return
         options.authors = []
@@ -628,18 +630,14 @@ def checkComicInfo(path, originalPath):
         os.remove(xmlPath)
 
 
-def slugify(value):
-    # Normalizes string, converts to lowercase, removes non-alpha characters and converts spaces to hyphens.
-    if isinstance(value, str):
-        #noinspection PyArgumentList
-        value = unicodedata.normalize('NFKD', unicode(value, 'latin1')).encode('ascii', 'ignore')
-    elif isinstance(value, unicode):
-        value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
-    value = re.sub('[^\w\s\.-]', '', value).strip().lower()
-    value = re.sub('[-\.\s]+', '-', value)
-    value = re.sub(r'([0-9]+)', r'00000\1', value)
-    value = re.sub(r'0*([0-9]{6,})', r'\1', value)
-    return value
+#def slugify(value):
+#    # Normalizes string, converts to lowercase, removes non-alpha characters and converts spaces to hyphens.
+#    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
+#    value = re.sub('[^\w\s\.-]', '', value).strip().lower()
+#    value = re.sub('[-\.\s]+', '-', value)
+#    value = re.sub(r'([0-9]+)', r'00000\1', value)
+#    value = re.sub(r'0*([0-9]{6,})', r'\1', value)
+#    return value
 
 
 def sanitizeTree(filetree):
@@ -775,7 +773,7 @@ def preSplitDirectory(path):
             mode = 0
         else:
             if filesNumber > 0:
-                print '\nWARNING: Automatic output splitting failed.'
+                print('\nWARNING: Automatic output splitting failed.')
                 if GUI:
                     GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
                                                           '"https://github.com/ciromattia/kcc/wiki'
@@ -790,7 +788,7 @@ def preSplitDirectory(path):
                     if len(dirs) != 0:
                         detectedSubSubdirectories = True
                     elif len(dirs) == 0 and detectedSubSubdirectories:
-                        print '\nWARNING: Automatic output splitting failed.'
+                        print('\nWARNING: Automatic output splitting failed.')
                         if GUI:
                             GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
                                                                   '"https://github.com/ciromattia/kcc/wiki'
@@ -807,7 +805,7 @@ def preSplitDirectory(path):
                 # One level of subdirectories
                 mode = 1
             if detectedFilesInSubdirectories and detectedSubSubdirectories:
-                print '\nWARNING: Automatic output splitting failed.'
+                print('\nWARNING: Automatic output splitting failed.')
                 if GUI:
                     GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
                                                           '"https://github.com/ciromattia/kcc/wiki'
@@ -827,12 +825,12 @@ def preSplitDirectory(path):
 
 
 def Copyright():
-    print ('comic2ebook v%(__version__)s. '
-           'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())
+    print(('comic2ebook v%(__version__)s. '
+           'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals()))
 
 
 def Usage():
-    print "Generates EPUB/CBZ comic ebook from a bunch of images."
+    print("Generates EPUB/CBZ comic ebook from a bunch of images.")
     parser.print_help()
 
 
@@ -916,7 +914,7 @@ def main(argv=None, qtGUI=None):
         else:
             comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', path], qtGUI)
     if options.imgproc:
-        print "\nProcessing images..."
+        print("\nProcessing images...")
         if GUI:
             GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Processing images')
         dirImgProcess(path + "/OEBPS/Images/")
@@ -943,14 +941,14 @@ def main(argv=None, qtGUI=None):
             options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']'
         if options.cbzoutput:
             # if CBZ output wanted, compress all images and return filepath
-            print "\nCreating CBZ file..."
+            print("\nCreating CBZ file...")
             if len(tomes) > 1:
                 filepath.append(getOutputFilename(args[0], options.output, '.cbz', ' ' + str(tomeNumber)))
             else:
                 filepath.append(getOutputFilename(args[0], options.output, '.cbz', ''))
             make_archive(tome + '_comic', 'zip', tome + '/OEBPS/Images')
         else:
-            print "\nCreating EPUB structure..."
+            print("\nCreating EPUB structure...")
             genEpubStruct(tome)
             # actually zip the ePub
             if len(tomes) > 1:
@@ -1017,7 +1015,7 @@ def checkOptions():
         options.quality = 0
     # Kindle for Android profile require target resolution.
     if options.profile == 'KFA' and (options.customwidth == 0 or options.customheight == 0):
-        print "ERROR: Kindle for Android profile require --customwidth and --customheight options!"
+        print("ERROR: Kindle for Android profile require --customwidth and --customheight options!")
         sys.exit(1)
     # Override profile data
     if options.customwidth != 0 or options.customheight != 0:
diff --git a/kcc/comic2panel.py b/kcc/comic2panel.py
index d194acd..787eec8 100644
--- a/kcc/comic2panel.py
+++ b/kcc/comic2panel.py
@@ -18,7 +18,7 @@
 # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 # PERFORMANCE OF THIS SOFTWARE.
 #
-__version__ = '3.6'
+__version__ = '4.0'
 __license__ = 'ISC'
 __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
 __docformat__ = 'restructuredtext en'
@@ -32,26 +32,26 @@ try:
     #noinspection PyUnresolvedReferences
     from PIL import Image, ImageStat
     if tuple(map(int, ('2.2.1'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))):
-        print "ERROR: Pillow 2.2.1 or newer is required!"
+        print("ERROR: Pillow 2.2.1 or newer is required!")
         if sys.platform.startswith('linux'):
             #noinspection PyUnresolvedReferences
-            import Tkinter
+            import tkinter
             #noinspection PyUnresolvedReferences
-            import tkMessageBox
-            importRoot = Tkinter.Tk()
+            import tkinter.messagebox
+            importRoot = tkinter.Tk()
             importRoot.withdraw()
-            tkMessageBox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!")
+            tkinter.messagebox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!")
         exit(1)
 except ImportError:
-    print "ERROR: Pillow is not installed!"
+    print("ERROR: Pillow is not installed!")
     if sys.platform.startswith('linux'):
         #noinspection PyUnresolvedReferences
-        import Tkinter
+        import tkinter
         #noinspection PyUnresolvedReferences
-        import tkMessageBox
-        importRoot = Tkinter.Tk()
+        import tkinter.messagebox
+        importRoot = tkinter.Tk()
         importRoot.withdraw()
-        tkMessageBox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!")
+        tkinter.messagebox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!")
     exit(1)
 try:
     from PyQt4 import QtCore
@@ -118,7 +118,7 @@ def splitImage(work):
         # Harcoded opttions
         threshold = 1.0
         delta = 15
-        print ".",
+        print(".", end=' ')
         fileExpanded = os.path.splitext(name)
         filePath = os.path.join(path, name)
         # Detect corrupted files
@@ -210,13 +210,13 @@ def splitImage(work):
                                               str(pageNumber) + '.png'), 'PNG')
                     pageNumber += 1
             os.remove(filePath)
-    except StandardError:
+    except Exception:
         return str(sys.exc_info()[1])
 
 
 def Copyright():
-    print ('comic2panel v%(__version__)s. '
-           'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())
+    print(('comic2panel v%(__version__)s. '
+           'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals()))
 
 
 def main(argv=None, qtGUI=None):
@@ -245,7 +245,7 @@ def main(argv=None, qtGUI=None):
     if options.height > 0:
         options.sourceDir = args[0]
         options.targetDir = args[0] + "-Splitted"
-        print "\nSplitting images..."
+        print("\nSplitting images...")
         if os.path.isdir(options.sourceDir):
             rmtree(options.targetDir, True)
             copytree(options.sourceDir, options.targetDir)
diff --git a/kcc/image.py b/kcc/image.py
index 3c80d74..1174eb2 100755
--- a/kcc/image.py
+++ b/kcc/image.py
@@ -26,26 +26,26 @@ try:
     # noinspection PyUnresolvedReferences
     from PIL import Image, ImageOps, ImageStat, ImageChops
     if tuple(map(int, ('2.2.1'.split(".")))) > tuple(map(int, (Image.PILLOW_VERSION.split(".")))):
-        print "ERROR: Pillow 2.2.1 or newer is required!"
+        print("ERROR: Pillow 2.2.1 or newer is required!")
         if platform.startswith('linux'):
             #noinspection PyUnresolvedReferences
-            import Tkinter
+            import tkinter
             #noinspection PyUnresolvedReferences
-            import tkMessageBox
-            importRoot = Tkinter.Tk()
+            import tkinter.messagebox
+            importRoot = tkinter.Tk()
             importRoot.withdraw()
-            tkMessageBox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!")
+            tkinter.messagebox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!")
         exit(1)
 except ImportError:
-    print "ERROR: Pillow is not installed!"
+    print("ERROR: Pillow is not installed!")
     if platform.startswith('linux'):
         #noinspection PyUnresolvedReferences
-        import Tkinter
+        import tkinter
         #noinspection PyUnresolvedReferences
-        import tkMessageBox
-        importRoot = Tkinter.Tk()
+        import tkinter.messagebox
+        importRoot = tkinter.Tk()
         importRoot.withdraw()
-        tkMessageBox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!")
+        tkinter.messagebox.showerror("KCC - Error", "Pillow 2.2.1 or newer is required!")
     exit(1)
 
 
@@ -287,10 +287,10 @@ class ComicPage:
         ratioDev = float(self.size[0]) / float(self.size[1])
         if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
             diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
-            self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill)
+            self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=fill)
         elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
             diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
-            self.image = ImageOps.expand(self.image, border=(0, diff / 2), fill=fill)
+            self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=fill)
         self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
         if generateBorder:
             if fill == 'white':
diff --git a/kcc/kindlesplit.py b/kcc/kindlesplit.py
index c8ae74d..c74ff95 100644
--- a/kcc/kindlesplit.py
+++ b/kcc/kindlesplit.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
 # Based on initial version of KindleUnpack. Copyright (C) 2009 Charles M. Hannum <root@ihack.net>
 # Improvements Copyright (C) 2009-2012 P. Durrant, K. Hendricks, S. Siebert, fandrieu, DiapDealer, nickredding
 # Copyright (C) 2013 Pawel Jastrzebski <pawelj@vulturis.eu>
@@ -105,8 +108,8 @@ def nullsection(datain, secno):  # make it zero-length without deleting it
         datalst.append('\0' * lpad)
     datalst.append(datain[zerosecstart: secstart])
     datalst.append(datain[secend:])
-    dataout = "".join(datalst)
-    return dataout
+    dataout = "".join(str(datalst)[1:-1])
+    return dataout.encode('utf-8')
 
 
 def deletesectionrange(datain, firstsec, lastsec):  # delete a range of sections
@@ -135,8 +138,8 @@ def deletesectionrange(datain, firstsec, lastsec):  # delete a range of sections
         datalst.append('\0' * lpad)
     datalst.append(datain[zerosecstart:firstsecstart])
     datalst.append(datain[lastsecend:])
-    dataout = "".join(datalst)
-    return dataout
+    dataout = "".join(str(datalst)[1:-1])
+    return dataout.encode('utf-8')
 
 
 def insertsection(datain, secno, secdata):  # insert a new section
@@ -166,13 +169,14 @@ def insertsection(datain, secno, secdata):  # insert a new section
     datalst.append(datain[zerosecstart:secstart])
     datalst.append(secdata)
     datalst.append(datain[secstart:])
-    dataout = "".join(datalst)
-    return dataout
+    dataout = "".join(str(datalst)[1:-1])
+    return dataout.encode('utf-8')
 
 
 def insertsectionrange(sectionsource, firstsec, lastsec, sectiontarget, targetsec):  # insert a range of sections
     dataout = sectiontarget
     for idx in range(lastsec, firstsec-1, -1):
+        print(dataout)
         dataout = insertsection(dataout, targetsec, readsection(sectionsource, idx))
     return dataout
 
@@ -381,4 +385,10 @@ class mobi_split:
                 raise
 
     def getResult(self):
-        return self.result_file
\ No newline at end of file
+        return self.result_file
+
+
+if __name__ == "__main__":
+    import sys
+    mobi_split(sys.argv[1], False)
+    sys.exit(0)
diff --git a/kcc/pdfjpgextract.py b/kcc/pdfjpgextract.py
index d9dad09..76f8a0e 100644
--- a/kcc/pdfjpgextract.py
+++ b/kcc/pdfjpgextract.py
@@ -63,7 +63,7 @@ class PdfJpgExtract:
 
             istart += startfix
             iend += endfix
-            print "JPG %d from %d to %d" % (njpg, istart, iend)
+            print("JPG %d from %d to %d" % (njpg, istart, iend))
             jpg = pdf[istart:iend]
             jpgfile = file(self.path + "/jpg%d.jpg" % njpg, "wb")
             jpgfile.write(jpg)
diff --git a/kcc/rarfile.py b/kcc/rarfile.py
index d7340ae..fe7db2a 100644
--- a/kcc/rarfile.py
+++ b/kcc/rarfile.py
@@ -633,7 +633,7 @@ class RarFile(object):
     def printdir(self):
         """Print archive file list to stdout."""
         for f in self._info_list:
-            print(f.filename)
+            print((f.filename))
 
     def extract(self, member, path=None, pwd=None):
         """Extract single file into current directory.
@@ -1138,7 +1138,7 @@ class RarFile(object):
         if self._crc_check:
             crc = crc32(cmt)
             if crc < 0:
-                crc += (long(1) << 32)
+                crc += (int(1) << 32)
             if crc != inf.CRC:
                 return None
 
@@ -1342,7 +1342,7 @@ class RarExtFile(RawIOBase):
             raise BadRarFile("Failed the read enough data")
         crc = self.CRC
         if crc < 0:
-            crc += (long(1) << 32)
+            crc += (int(1) << 32)
         if crc != self.inf.CRC:
             raise BadRarFile("Corrupt file - CRC check failed: " + self.inf.filename)
 
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..264dcd4
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+Pillow==2.2.1
+Unidecode==0.04.14
+psutil==1.1.3
+python-slugify==0.0.6
diff --git a/setup.py b/setup.py
index ace85f9..149cb48 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@ Usage (Windows):
 from sys import platform
 
 NAME = "KindleComicConverter"
-VERSION = "3.6"
+VERSION = "4.0"
 MAIN = "kcc.py"
 
 if platform == "darwin":
@@ -72,7 +72,7 @@ elif platform == "win32":
                                 appendScriptToLibrary=False,
                                 compress=True)])
 else:
-    print 'Please use setup.sh to build Linux package.'
+    print('Please use setup.sh to build Linux package.')
     exit()
 
 #noinspection PyUnboundLocalVariable
@@ -91,5 +91,5 @@ setup(
 
 if platform == "darwin":
     from os import chmod
-    chmod('dist/' + NAME + '.app/Contents/Resources/unrar', 0777)
-    chmod('dist/' + NAME + '.app/Contents/Resources/7za', 0777)
\ No newline at end of file
+    chmod('dist/' + NAME + '.app/Contents/Resources/unrar', 0o777)
+    chmod('dist/' + NAME + '.app/Contents/Resources/7za', 0o777)
\ No newline at end of file
diff --git a/setup.sh b/setup.sh
index a36d7f0..f4382d1 100644
--- a/setup.sh
+++ b/setup.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 # Linux Python package build script
 
-VERSION="3.6"
+VERSION="4.0"
 
 cp kcc.py __main__.py
 zip kcc.zip __main__.py kcc/*.py