Skip to content

Instantly share code, notes, and snippets.

@avsej
Created September 24, 2012 13:34

Revisions

  1. avsej revised this gist Sep 28, 2012. 1 changed file with 33 additions and 121 deletions.
    154 changes: 33 additions & 121 deletions mobidedrm.py
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,5 @@
    #!/usr/bin/python
    # GistID: 3775979
    #
    # This is a python script. You need a Python interpreter to run it.
    # For example, ActiveState Python, which exists for windows.
    #
    @@ -30,46 +29,22 @@
    # 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.13K - Support Kindle deDRM modes.

    __version__ = '0.13K'
    # 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.
    # 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.

    __version__ = '0.16'

    import sys
    import struct
    import binascii
    import hashlib
    import base64

    # This is from kindlepid by Igor Skochinsky
    letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"

    def crc32(s):
    return (~binascii.crc32(s,-1))&0xFFFFFFFF

    def pidFromSerial(s, l):
    crc = crc32(s)
    print "CRC is %x" % crc

    arr1 = [0]*l
    for i in xrange(len(s)):
    arr1[i%l] ^= ord(s[i])

    for i in xrange(l):
    print "arr1[%d] == %d" % (i, arr1[i])

    crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
    for i in xrange(l):
    arr1[i] ^= crc_bytes[i&3]

    pid = ""
    for i in xrange(l):
    b = arr1[i] & 0xff
    pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]

    return pid


    # This is MobiDeDRM

    class Unbuffered:
    def __init__(self, stream):
    @@ -118,6 +93,7 @@ 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
    @@ -147,8 +123,10 @@ def getSizeOfTrailingDataEntry(ptr, size):
    if testflags & 1:
    num += getSizeOfTrailingDataEntry(ptr, size - num)
    testflags >>= 1
    if flags & 1:
    num += (ord(ptr[size - num - 1]) & 0x3) + 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:
    @@ -198,82 +176,9 @@ def parseDRM(self, data, count, pid):
    break
    return found_key

    def deriveK3PID(self, data_file, serial):
    """Attempt to derive a Kindle3 PID from the book and serial provided"""
    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)
    mh_start = 16
    mh_len, = struct.unpack('>L', sect[mh_start+4:mh_start+8])
    eh_start = mh_start + mh_len
    eh_id,eh_hlen,eh_count = struct.unpack('>LLL', sect[eh_start:eh_start+12])
    if eh_id != 0x45585448:
    raise DrmException("Book is missing its EXTH section?")

    # Find the key pointer block
    eh_rec = eh_start + 12
    key_block = ""
    for i in xrange(eh_count):
    rec_type, rec_len = struct.unpack('>LL', sect[eh_rec:eh_rec+8])
    if rec_type == 209:
    key_block = sect[eh_rec+8:eh_rec+rec_len]
    eh_rec = eh_rec + rec_len

    if key_block == "":
    raise DrmException("Unable to find key pointer block")

    # Now iterate the key pointer block and derive all the blocks
    key_buf = ""
    for i in xrange(len(key_block) / 5):
    rtype, = struct.unpack('>L', key_block[(i*5)+1:(i+1)*5])
    eh_rec = eh_start + 12
    for i in xrange(eh_count):
    rec_type, rec_len = struct.unpack('>LL', sect[eh_rec:eh_rec+8])
    if rec_type == rtype:
    key_buf = key_buf + sect[eh_rec+8:eh_rec+rec_len]
    eh_rec = eh_rec + rec_len

    # Built key data blocks, now calculate the PID for the book
    hasher = hashlib.sha1()
    hasher.update(serial)
    hasher.update(key_block)
    hasher.update(key_buf)
    digest = base64.b64encode(hasher.digest())

    pid = checksumPid(digest[0:8])
    return pid

    def __init__(self, data_file, pid):
    # if the pid smells like a kindle or iphone serial then try and convert it to a pid
    if len(pid) == 40:
    pid = checksumPid(pidFromSerial(pid, 8))
    elif pid.startswith("B001") or \
    pid.startswith("B101") or \
    pid.startswith("B002") or \
    pid.startswith("B003") or \
    pid.startswith("B004"):
    pid = checksumPid(pidFromSerial(pid,7)+"*")
    elif pid.startswith("B006") or \
    pid.startswith("B008") or \
    pid.startswith("B00A"):
    # K3 serial, calculate PID using book data also.
    pid = self.deriveK3PID(data_file, pid)

    if checksumPid(pid[0:-2]) != pid:
    raise DrmException("invalid PID checksum")

    # Strip checksum since it's no longer needed
    pid = pid[0:-2]

    self.data_file = data_file
    @@ -293,9 +198,8 @@ def __init__(self, data_file, pid):
    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):
    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

    @@ -324,13 +228,22 @@ def __init__(self, data_file, pid):
    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
    @@ -343,7 +256,7 @@ class MobiDeDRM(FileTypePlugin):
    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, 3) # The version number of this plugin
    version = (0, 1, 6) # 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

    @@ -355,7 +268,6 @@ def run(self, path_to_ebook):
    ar = PID.split(',')
    for i in ar:
    try:
    print "Trying to dedrm %s with %s" % (path_to_ebook, i)
    unlocked_file = DrmStripper(data_file, i).getResult()
    except DrmException:
    # ignore the error
    @@ -373,7 +285,7 @@ def run(self, path_to_ebook):
    return path_to_ebook

    def customization_help(self, gui=False):
    return 'Enter PID, iPhone UUID or Kindle serial number (separate multiple values with comma)'
    return 'Enter PID (separate multiple PIDs with comma)'

    if __name__ == "__main__":
    sys.stdout=Unbuffered(sys.stdout)
  2. avsej revised this gist Sep 24, 2012. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions mobidedrm.py
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,5 @@
    #!/usr/bin/python
    # GistID: 3775979
    #
    # This is a python script. You need a Python interpreter to run it.
    # For example, ActiveState Python, which exists for windows.
  3. avsej created this gist Sep 24, 2012.
    397 changes: 397 additions & 0 deletions mobidedrm.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,397 @@
    #!/usr/bin/python
    #
    # This is a python script. You need a Python interpreter to run it.
    # For example, ActiveState Python, which exists for windows.
    #
    # It can run standalone to convert files, or it can be installed as a
    # plugin for Calibre (http://calibre-ebook.com/about) so that
    # importing files with DRM 'Just Works'.
    #
    # To create a Calibre plugin, rename this file so that the filename
    # ends in '_plugin.py', put it into a ZIP file and import that Calibre
    # using its plugin configuration GUI.
    #
    # Changelog
    # 0.01 - Initial version
    # 0.02 - Huffdic compressed books were not properly decrypted
    # 0.03 - Wasn't checking MOBI header length
    # 0.04 - Wasn't sanity checking size of data record
    # 0.05 - It seems that the extra data flags take two bytes not four
    # 0.06 - And that low bit does mean something after all :-)
    # 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.13K - Support Kindle deDRM modes.

    __version__ = '0.13K'

    import sys
    import struct
    import binascii
    import hashlib
    import base64

    # This is from kindlepid by Igor Skochinsky
    letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"

    def crc32(s):
    return (~binascii.crc32(s,-1))&0xFFFFFFFF

    def pidFromSerial(s, l):
    crc = crc32(s)
    print "CRC is %x" % crc

    arr1 = [0]*l
    for i in xrange(len(s)):
    arr1[i%l] ^= ord(s[i])

    for i in xrange(l):
    print "arr1[%d] == %d" % (i, arr1[i])

    crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
    for i in xrange(l):
    arr1[i] ^= crc_bytes[i&3]

    pid = ""
    for i in xrange(l):
    b = arr1[i] & 0xff
    pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]

    return pid


    # This is MobiDeDRM

    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

    # Implementation of Pukall Cipher 1
    def PC1(key, src, decryption=True):
    sum1 = 0;
    sum2 = 0;
    keyXorVal = 0;
    if len(key)!=16:
    print "Bad key length!"
    return None
    wkey = []
    for i in xrange(8):
    wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))

    dst = ""
    for i in xrange(len(src)):
    temp1 = 0;
    byteXorVal = 0;
    for j in xrange(8):
    temp1 ^= wkey[j]
    sum2 = (sum2+j)*20021 + sum1
    sum1 = (temp1*346)&0xFFFF
    sum2 = (sum2+sum1)&0xFFFF
    temp1 = (temp1*20021+1)&0xFFFF
    byteXorVal ^= temp1 ^ sum2
    curByte = ord(src[i])
    if not decryption:
    keyXorVal = curByte * 257;
    curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
    if decryption:
    keyXorVal = curByte * 257;
    for j in xrange(8):
    wkey[j] ^= keyXorVal;
    dst+=chr(curByte)
    return dst

    def checksumPid(s):
    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

    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
    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 deriveK3PID(self, data_file, serial):
    """Attempt to derive a Kindle3 PID from the book and serial provided"""
    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)
    mh_start = 16
    mh_len, = struct.unpack('>L', sect[mh_start+4:mh_start+8])
    eh_start = mh_start + mh_len
    eh_id,eh_hlen,eh_count = struct.unpack('>LLL', sect[eh_start:eh_start+12])
    if eh_id != 0x45585448:
    raise DrmException("Book is missing its EXTH section?")

    # Find the key pointer block
    eh_rec = eh_start + 12
    key_block = ""
    for i in xrange(eh_count):
    rec_type, rec_len = struct.unpack('>LL', sect[eh_rec:eh_rec+8])
    if rec_type == 209:
    key_block = sect[eh_rec+8:eh_rec+rec_len]
    eh_rec = eh_rec + rec_len

    if key_block == "":
    raise DrmException("Unable to find key pointer block")

    # Now iterate the key pointer block and derive all the blocks
    key_buf = ""
    for i in xrange(len(key_block) / 5):
    rtype, = struct.unpack('>L', key_block[(i*5)+1:(i+1)*5])
    eh_rec = eh_start + 12
    for i in xrange(eh_count):
    rec_type, rec_len = struct.unpack('>LL', sect[eh_rec:eh_rec+8])
    if rec_type == rtype:
    key_buf = key_buf + sect[eh_rec+8:eh_rec+rec_len]
    eh_rec = eh_rec + rec_len

    # Built key data blocks, now calculate the PID for the book
    hasher = hashlib.sha1()
    hasher.update(serial)
    hasher.update(key_block)
    hasher.update(key_buf)
    digest = base64.b64encode(hasher.digest())

    pid = checksumPid(digest[0:8])
    return pid

    def __init__(self, data_file, pid):
    # if the pid smells like a kindle or iphone serial then try and convert it to a pid
    if len(pid) == 40:
    pid = checksumPid(pidFromSerial(pid, 8))
    elif pid.startswith("B001") or \
    pid.startswith("B101") or \
    pid.startswith("B002") or \
    pid.startswith("B003") or \
    pid.startswith("B004"):
    pid = checksumPid(pidFromSerial(pid,7)+"*")
    elif pid.startswith("B006") or \
    pid.startswith("B008") or \
    pid.startswith("B00A"):
    # K3 serial, calculate PID using book data also.
    pid = self.deriveK3PID(data_file, pid)

    if checksumPid(pid[0:-2]) != pid:
    raise DrmException("invalid PID checksum")

    # Strip checksum since it's no longer needed
    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:
    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...",
    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

    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, 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

    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:
    print "Trying to dedrm %s with %s" % (path_to_ebook, i)
    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, iPhone UUID or Kindle serial number (separate multiple values with comma)'

    if __name__ == "__main__":
    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)