]> xmof Git - DeDRM.git/commitdiff
mobidedrm 0.15
authorApprentice Alf <apprenticealf@gmail.com>
Wed, 17 Mar 2010 10:52:52 +0000 (10:52 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Tue, 3 Mar 2015 06:53:16 +0000 (06:53 +0000)
Kindle_Mobi_Tools/lib/mobidedrm.py
Kindle_Mobi_Tools/unswindle/mobidedrm.py
Macintosh_Applications/Mobipocket Unlocker.app/Contents/Resources/MobiDeDRM.py

index 59e749d48ffad1578ffd806bd67a1fe339069068..0565356e81e97ad6e43b361b56fc5a89a3ada053 100644 (file)
@@ -37,8 +37,9 @@
 #         in utf8 file are encrypted. (Although neither kind gets compressed.)
 #         This knowledge leads to a simplification of the test for the 
 #         trailing data byte flags - version 5 and higher AND header size >= 0xE4. 
+#  0.15 - Now outputs 'hearbeat', and is also quicker for long files.
 
-__version__ = '0.14'
+__version__ = '0.15'
 
 import sys
 import struct
@@ -196,8 +197,7 @@ class DrmStripper:
         mobi_length, = struct.unpack('>L',sect[0x14:0x18])
         mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
         extra_data_flags = 0
-        print "MOBI header length = %d" %mobi_length
-        print "MOBI header version = %d" %mobi_version
+        print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
         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
@@ -227,13 +227,22 @@ class DrmStripper:
             self.patchSection(0, "\0" * 2, 0xC)
 
             # decrypt sections
-            print "Decrypting. Please wait...",
+            print "Decrypting. Please wait . . .",
+            new_data = self.data_file[:self.sections[1][0]]
             for i in xrange(1, records+1):
                 data = self.loadSection(i)
                 extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
+                if i%100 == 0:
+                    print ".",
                 # print "record %d, extra_size %d" %(i,extra_size)
-                self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
-        print "done"
+                new_data += PC1(found_key, data[0:len(data) - extra_size])
+                if extra_size > 0:
+                    new_data += data[-extra_size:]
+                #self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
+            if self.num_sections > records+1:
+                new_data += self.data_file[self.sections[records+1][0]:]
+            self.data_file = new_data
+            print "done."
 
     def getResult(self):
         return self.data_file
@@ -246,7 +255,7 @@ if not __name__ == "__main__":
         description         = 'Removes DRM from secure Mobi files'
         supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
         author              = 'The Dark Reverser' # The author of this plugin
-        version             = (0, 1, 4)   # The version number of this plugin
+        version             = (0, 1, 5)   # 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
 
index 59e749d48ffad1578ffd806bd67a1fe339069068..0565356e81e97ad6e43b361b56fc5a89a3ada053 100644 (file)
@@ -37,8 +37,9 @@
 #         in utf8 file are encrypted. (Although neither kind gets compressed.)
 #         This knowledge leads to a simplification of the test for the 
 #         trailing data byte flags - version 5 and higher AND header size >= 0xE4. 
+#  0.15 - Now outputs 'hearbeat', and is also quicker for long files.
 
-__version__ = '0.14'
+__version__ = '0.15'
 
 import sys
 import struct
@@ -196,8 +197,7 @@ class DrmStripper:
         mobi_length, = struct.unpack('>L',sect[0x14:0x18])
         mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
         extra_data_flags = 0
-        print "MOBI header length = %d" %mobi_length
-        print "MOBI header version = %d" %mobi_version
+        print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
         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
@@ -227,13 +227,22 @@ class DrmStripper:
             self.patchSection(0, "\0" * 2, 0xC)
 
             # decrypt sections
-            print "Decrypting. Please wait...",
+            print "Decrypting. Please wait . . .",
+            new_data = self.data_file[:self.sections[1][0]]
             for i in xrange(1, records+1):
                 data = self.loadSection(i)
                 extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
+                if i%100 == 0:
+                    print ".",
                 # print "record %d, extra_size %d" %(i,extra_size)
-                self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
-        print "done"
+                new_data += PC1(found_key, data[0:len(data) - extra_size])
+                if extra_size > 0:
+                    new_data += data[-extra_size:]
+                #self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
+            if self.num_sections > records+1:
+                new_data += self.data_file[self.sections[records+1][0]:]
+            self.data_file = new_data
+            print "done."
 
     def getResult(self):
         return self.data_file
@@ -246,7 +255,7 @@ if not __name__ == "__main__":
         description         = 'Removes DRM from secure Mobi files'
         supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
         author              = 'The Dark Reverser' # The author of this plugin
-        version             = (0, 1, 4)   # The version number of this plugin
+        version             = (0, 1, 5)   # 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
 
index 1f02cf93f8fad24d8b269e5cb8e6288cf412ce90..0565356e81e97ad6e43b361b56fc5a89a3ada053 100644 (file)
 #  0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
 #  0.08 - ...and also not in Mobi header version < 6
 #  0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
+#  0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
+#         import filter it works when importing unencrypted files.
+#         Also now handles encrypted files that don't need a specific PID.
+#  0.11 - use autoflushed stdout and proper return values
+#  0.12 - Fix for problems with metadata import as Calibre plugin, report errors
+#  0.13 - Formatting fixes: retabbed file, removed trailing whitespace
+#         and extra blank lines, converted CR/LF pairs at ends of each line,
+#         and other cosmetic fixes.
+#  0.14 - Working out when the extra data flags are present has been problematic
+#         Versions 7 through 9 have tried to tweak the conditions, but have been
+#         only partially successful. Closer examination of lots of sample
+#         files reveals that a confusin has arisen because trailing data entries
+#         are not encrypted, but it turns out that the multibyte entries
+#         in utf8 file are encrypted. (Although neither kind gets compressed.)
+#         This knowledge leads to a simplification of the test for the 
+#         trailing data byte flags - version 5 and higher AND header size >= 0xE4. 
+#  0.15 - Now outputs 'hearbeat', and is also quicker for long files.
 
-import sys,struct,binascii
+__version__ = '0.15'
+
+import sys
+import struct
+import binascii
+
+class Unbuffered:
+    def __init__(self, stream):
+        self.stream = stream
+    def write(self, data):
+        self.stream.write(data)
+        self.stream.flush()
+    def __getattr__(self, attr):
+        return getattr(self.stream, attr)
 
 class DrmException(Exception):
-       pass
+    pass
 
-#implementation of Pukall Cipher 1
+# Implementation of Pukall Cipher 1
 def PC1(key, src, decryption=True):
     sum1 = 0;
     sum2 = 0;
@@ -62,188 +92,218 @@ def PC1(key, src, decryption=True):
     return dst
 
 def checksumPid(s):
-       letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
-       crc = (~binascii.crc32(s,-1))&0xFFFFFFFF 
-       crc = crc ^ (crc >> 16)
-       res = s
-       l = len(letters)
-       for i in (0,1):
-               b = crc & 0xff
-               pos = (b // l) ^ (b % l)
-               res += letters[pos%l]
-               crc >>= 8
-       return res
+    letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+    crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
+    crc = crc ^ (crc >> 16)
+    res = s
+    l = len(letters)
+    for i in (0,1):
+        b = crc & 0xff
+        pos = (b // l) ^ (b % l)
+        res += letters[pos%l]
+        crc >>= 8
+    return res
 
 def getSizeOfTrailingDataEntries(ptr, size, flags):
-       def getSizeOfTrailingDataEntry(ptr, size):
-               bitpos, result = 0, 0
-               if size <= 0:
-                       return result
-               while True:
-                       v = ord(ptr[size-1])
-                       result |= (v & 0x7F) << bitpos
-                       bitpos += 7
-                       size -= 1
-                       if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
-                               return result
-       num = 0
-       testflags = flags >> 1
-       while testflags:
-               if testflags & 1:
-                       num += getSizeOfTrailingDataEntry(ptr, size - num)
-               testflags >>= 1
-       if flags & 1:
-               num += (ord(ptr[size - num - 1]) & 0x3) + 1
-       return num
+    def getSizeOfTrailingDataEntry(ptr, size):
+        bitpos, result = 0, 0
+        if size <= 0:
+            return result
+        while True:
+            v = ord(ptr[size-1])
+            result |= (v & 0x7F) << bitpos
+            bitpos += 7
+            size -= 1
+            if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
+                return result
+    num = 0
+    testflags = flags >> 1
+    while testflags:
+        if testflags & 1:
+            num += getSizeOfTrailingDataEntry(ptr, size - num)
+        testflags >>= 1
+    # Multibyte data, if present, is included in the encryption, so
+    # we do not need to check the low bit.
+    # if flags & 1:
+    #    num += (ord(ptr[size - num - 1]) & 0x3) + 1
+    return num
 
 class DrmStripper:
-       def loadSection(self, section): 
-               if (section + 1 == self.num_sections):
-                       endoff = len(self.data_file)
-               else:
-                       endoff = self.sections[section + 1][0]
-               off = self.sections[section][0]
-               return self.data_file[off:endoff]
-
-       def patch(self, off, new):      
-               self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
-
-       def patchSection(self, section, new, in_off = 0):
-               if (section + 1 == self.num_sections):
-                       endoff = len(self.data_file)
-               else:
-                       endoff = self.sections[section + 1][0]
-               off = self.sections[section][0]
-               assert off + in_off + len(new) <= endoff
-               self.patch(off + in_off, new)
-
-       def parseDRM(self, data, count, pid):
-               pid = pid.ljust(16,'\0')
-               keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
-               temp_key = PC1(keyvec1, pid, False)
-               temp_key_sum = sum(map(ord,temp_key)) & 0xff
-               found_key = None
-               for i in xrange(count):
-                       verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
-                       cookie = PC1(temp_key, cookie)
-                       ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
-                       if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
-                               found_key = finalkey
-                               break
-               return found_key                
-
-
-       def __init__(self, data_file, pid):
-
-               if checksumPid(pid[0:-2]) != pid:
-                       raise DrmException("invalid PID checksum")
-               pid = pid[0:-2]
-               
-               self.data_file = data_file
-               header = data_file[0:72]
-               if header[0x3C:0x3C+8] != 'BOOKMOBI':
-                       raise DrmException("invalid file format")
-               self.num_sections, = struct.unpack('>H', data_file[76:78])
-
-               self.sections = []
-               for i in xrange(self.num_sections):
-                       offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
-                       flags, val = a1, a2<<16|a3<<8|a4
-                       self.sections.append( (offset, flags, val) )
-
-               sect = self.loadSection(0)
-               records, = struct.unpack('>H', sect[0x8:0x8+2])
-               mobi_length, = struct.unpack('>L',sect[0x14:0x18])
-               mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
-               extra_data_flags = 0
-               print "MOBI header length = %d" %mobi_length
-               print "MOBI header version = %d" %mobi_version
-               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
-
-
-               crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
-               if crypto_type == 0:
-                       raise DrmException("it seems that this book isn't encrypted")
-               if crypto_type == 1:
-                       raise DrmException("cannot decode Mobipocket encryption type 1")
-               if crypto_type != 2:
-                       raise DrmException("unknown encryption type: %d" % crypto_type)
-
-               # calculate the keys
-               drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
-               if drm_count == 0:
-                       raise DrmException("no PIDs found in this file")
-               found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
-               if not found_key:
-                       raise DrmException("no key found. maybe the PID is incorrect")
-
-               # kill the drm keys
-               self.patchSection(0, "\0" * drm_size, drm_ptr)
-               # kill the drm pointers
-               self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
-               # clear the crypto type
-               self.patchSection(0, "\0" * 2, 0xC)
-
-               # decrypt sections
-               print "Decrypting. Please wait...",
-               for i in xrange(1, records+1):
-                       data = self.loadSection(i)
-                       extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
-                       # print "record %d, extra_size %d" %(i,extra_size)
-                       self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
-               print "done"
-       def getResult(self):
-               return self.data_file
+    def loadSection(self, section):
+        if (section + 1 == self.num_sections):
+            endoff = len(self.data_file)
+        else:
+            endoff = self.sections[section + 1][0]
+        off = self.sections[section][0]
+        return self.data_file[off:endoff]
+
+    def patch(self, off, new):
+        self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
+
+    def patchSection(self, section, new, in_off = 0):
+        if (section + 1 == self.num_sections):
+            endoff = len(self.data_file)
+        else:
+            endoff = self.sections[section + 1][0]
+        off = self.sections[section][0]
+        assert off + in_off + len(new) <= endoff
+        self.patch(off + in_off, new)
+
+    def parseDRM(self, data, count, pid):
+        pid = pid.ljust(16,'\0')
+        keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
+        temp_key = PC1(keyvec1, pid, False)
+        temp_key_sum = sum(map(ord,temp_key)) & 0xff
+        found_key = None
+        for i in xrange(count):
+            verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
+            cookie = PC1(temp_key, cookie)
+            ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
+            if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
+                found_key = finalkey
+                break
+        if not found_key:
+            # Then try the default encoding that doesn't require a PID
+            temp_key = keyvec1
+            temp_key_sum = sum(map(ord,temp_key)) & 0xff
+            for i in xrange(count):
+                verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
+                cookie = PC1(temp_key, cookie)
+                ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
+                if verification == ver and cksum == temp_key_sum:
+                    found_key = finalkey
+                    break
+        return found_key
+
+    def __init__(self, data_file, pid):
+        if checksumPid(pid[0:-2]) != pid:
+            raise DrmException("invalid PID checksum")
+        pid = pid[0:-2]
+
+        self.data_file = data_file
+        header = data_file[0:72]
+        if header[0x3C:0x3C+8] != 'BOOKMOBI':
+            raise DrmException("invalid file format")
+        self.num_sections, = struct.unpack('>H', data_file[76:78])
+
+        self.sections = []
+        for i in xrange(self.num_sections):
+            offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
+            flags, val = a1, a2<<16|a3<<8|a4
+            self.sections.append( (offset, flags, val) )
+
+        sect = self.loadSection(0)
+        records, = struct.unpack('>H', sect[0x8:0x8+2])
+        mobi_length, = struct.unpack('>L',sect[0x14:0x18])
+        mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
+        extra_data_flags = 0
+        print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
+        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
+
+        crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
+        if crypto_type == 0:
+            print "This book is not encrypted."
+        else:
+            if crypto_type == 1:
+                raise DrmException("cannot decode Mobipocket encryption type 1")
+            if crypto_type != 2:
+                raise DrmException("unknown encryption type: %d" % crypto_type)
+
+            # calculate the keys
+            drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
+            if drm_count == 0:
+                raise DrmException("no PIDs found in this file")
+            found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
+            if not found_key:
+                raise DrmException("no key found. maybe the PID is incorrect")
+
+            # kill the drm keys
+            self.patchSection(0, "\0" * drm_size, drm_ptr)
+            # kill the drm pointers
+            self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
+            # clear the crypto type
+            self.patchSection(0, "\0" * 2, 0xC)
+
+            # decrypt sections
+            print "Decrypting. Please wait . . .",
+            new_data = self.data_file[:self.sections[1][0]]
+            for i in xrange(1, records+1):
+                data = self.loadSection(i)
+                extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
+                if i%100 == 0:
+                    print ".",
+                # print "record %d, extra_size %d" %(i,extra_size)
+                new_data += PC1(found_key, data[0:len(data) - extra_size])
+                if extra_size > 0:
+                    new_data += data[-extra_size:]
+                #self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
+            if self.num_sections > records+1:
+                new_data += self.data_file[self.sections[records+1][0]:]
+            self.data_file = new_data
+            print "done."
+
+    def getResult(self):
+        return self.data_file
 
 if not __name__ == "__main__":
-       from calibre.customize import FileTypePlugin
-
-       class MobiDeDRM(FileTypePlugin):
-
-               name                = 'MobiDeDRM' # Name of the plugin
-               description         = 'Removes DRM from secure Mobi files'
-               supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
-               author              = 'The Dark Reverser' # The author of this plugin
-               version             = (0, 0, 9)   # The version number of this plugin
-               file_types          = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
-               on_import           = True # Run this plugin during the import
-
-       
-               def run(self, path_to_ebook):
-                       of = self.temporary_file('.mobi')
-                       PID = self.site_customization
-                       data_file = file(path_to_ebook, 'rb').read()
-                       ar = PID.split(',')
-                       for i in ar:
-                               try:
-                                       file(of.name, 'wb').write(DrmStripper(data_file, i).getResult())
-                               except DrmException:
-                                       # Hm, we should display an error dialog here.
-                                       # Dunno how though.
-                                       # Ignore the dirty hack behind the curtain.
-#                                      strexcept = 'echo exception: %s > /dev/tty' % e
-#                                      subprocess.call(strexcept,shell=True)
-                                       print i + ": not PID for book"
-                               else:
-                                       return of.name
-
-               def customization_help(self, gui=False):
-                       return 'Enter PID (separate multiple PIDs with comma)'
+    from calibre.customize import FileTypePlugin
+
+    class MobiDeDRM(FileTypePlugin):
+        name                = 'MobiDeDRM' # Name of the plugin
+        description         = 'Removes DRM from secure Mobi files'
+        supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
+        author              = 'The Dark Reverser' # The author of this plugin
+        version             = (0, 1, 5)   # The version number of this plugin
+        file_types          = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
+        on_import           = True # Run this plugin during the import
+
+        def run(self, path_to_ebook):
+            from calibre.gui2 import is_ok_to_use_qt
+            from PyQt4.Qt import QMessageBox
+            PID = self.site_customization
+            data_file = file(path_to_ebook, 'rb').read()
+            ar = PID.split(',')
+            for i in ar:
+                try:
+                    unlocked_file = DrmStripper(data_file, i).getResult()
+                except DrmException:
+                    # ignore the error
+                    pass
+                else:
+                    of = self.temporary_file('.mobi')
+                    of.write(unlocked_file)
+                    of.close()
+                    return of.name
+            if is_ok_to_use_qt():
+                d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Couldn't decode: %s\n\nImporting encrypted version." % path_to_ebook)
+                d.show()
+                d.raise_()
+                d.exec_()
+            return path_to_ebook
+
+        def customization_help(self, gui=False):
+            return 'Enter PID (separate multiple PIDs with comma)'
 
 if __name__ == "__main__":
-       print "MobiDeDrm v0.09. Copyright (c) 2008 The Dark Reverser"
-       if len(sys.argv)<4:
-               print "Removes protection from Mobipocket books"
-               print "Usage:"
-               print "  mobidedrm infile.mobi outfile.mobi PID"
-       else:  
-               infile = sys.argv[1]
-               outfile = sys.argv[2]
-               pid = sys.argv[3]
-               data_file = file(infile, 'rb').read()
-               try:
-                       file(outfile, 'wb').write(DrmStripper(data_file, pid).getResult())
-               except DrmException, e:
-                       print "Error: %s" % e
+    sys.stdout=Unbuffered(sys.stdout)
+    print ('MobiDeDrm v%(__version__)s. '
+          'Copyright 2008-2010 The Dark Reverser.' % globals())
+    if len(sys.argv)<4:
+        print "Removes protection from Mobipocket books"
+        print "Usage:"
+        print "    %s <infile> <outfile> <PID>" % sys.argv[0]
+        sys.exit(1)
+    else:
+        infile = sys.argv[1]
+        outfile = sys.argv[2]
+        pid = sys.argv[3]
+        data_file = file(infile, 'rb').read()
+        try:
+            strippedFile = DrmStripper(data_file, pid)
+            file(outfile, 'wb').write(strippedFile.getResult())
+        except DrmException, e:
+            print "Error: %s" % e
+            sys.exit(1)
+    sys.exit(0)
\ No newline at end of file