acsm-calibre-plugin/calibre-plugin/libpdf.py

148 lines
5.1 KiB
Python
Raw Normal View History

import os, zlib, base64
from lxml import etree
def read_reverse_order(file_name):
# Open file for reading in binary mode
with open(file_name, 'rb') as read_obj:
# Move the cursor to the end of the file
read_obj.seek(0, os.SEEK_END)
# Get the current position of pointer i.e eof
pointer_location = read_obj.tell()
# Create a buffer to keep the last read line
buffer = bytearray()
# Loop till pointer reaches the top of the file
while pointer_location >= 0:
# Move the file pointer to the location pointed by pointer_location
read_obj.seek(pointer_location)
# Shift pointer location by -1
pointer_location = pointer_location -1
# read that byte / character
new_byte = read_obj.read(1)
# If the read byte is new line character then it means one line is read
if new_byte == b'\n':
# Fetch the line from buffer and yield it
yield buffer.decode()[::-1]
# Reinitialize the byte array to save next line
buffer = bytearray()
else:
# If last read character is not eol then add it in buffer
buffer.extend(new_byte)
# As file is read completely, if there is still data in buffer, then its the first line.
if len(buffer) > 0:
# Yield the first line too
yield buffer.decode()[::-1]
def deflate_and_base64_encode( string_val ):
zlibbed_str = zlib.compress( string_val )
compressed_string = zlibbed_str[2:-4]
return base64.b64encode( compressed_string )
def prepare_string_from_xml(xmlstring, title, author):
b64data = deflate_and_base64_encode(xmlstring.encode("utf-8")).decode("utf-8")
adobe_fulfill_response = etree.fromstring(xmlstring)
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
adNS = lambda tag: '{%s}%s' % ('http://ns.adobe.com/adept', tag)
resource = adobe_fulfill_response.find("./%s/%s" % (adNS("licenseToken"), adNS("resource"))).text
return "<</Length 128/EBX_TITLE(%s)/Filter/EBX_HANDLER/EBX_AUTHOR(%s)/V 4/ADEPT_ID(%s)/EBX_BOOKID(%s)/ADEPT_LICENSE(%s)>>" % (title, author, resource, resource, b64data)
def patch_drm_into_pdf(filename_in, drm_string, filename_out):
ORIG_FILE = filename_in
trailer = ""
trailer_idx = 0
for line in read_reverse_order(ORIG_FILE):
trailer_idx += 1
trailer = line + "\n" + trailer
if (line == "trailer"):
if (trailer_idx > 20):
print("trailer_idx is very large (%d). Usually it's 10 or less. File might be corrupted." % trailer_idx)
break
r_encrypt_offs1 = 0
r_encrypt_offs2 = 0
root_str = None
next_startxref = False
startxref = None
for line in trailer.split('\n'):
#print(line)
if ("R/Encrypt" in line):
root_str = line
line_split = line.split(' ')
next = 0
for element in line_split:
if element == "R/Encrypt":
next = 2
continue
if next == 2:
r_encrypt_offs1 = element
next = 1
continue
if next == 1:
r_encrypt_offs2 = element
next = 0
continue
if "startxref" in line:
next_startxref = True
continue
if next_startxref:
startxref = line
next_startxref = False
continue
filesize_str = str(os.path.getsize(ORIG_FILE))
filesize_pad = filesize_str.zfill(10)
additional_data = "\r"
additional_data += r_encrypt_offs1 + " " + r_encrypt_offs2 + " " + "obj" + "\r"
additional_data += drm_string
additional_data += "\r"
additional_data += "endobj"
ptr = int(filesize_str) + len(additional_data)
additional_data += "\rxref\r" + r_encrypt_offs1 + " " + str((int(r_encrypt_offs2) + 1)) + "\r"
additional_data += filesize_pad + " 00000 n" + "\r\n"
additional_data += "trailer"
additional_data += "\r"
arr_root_str = root_str.split('/')
did_prev = False
for elem in arr_root_str:
if elem.startswith("Prev"):
did_prev = True
additional_data += "Prev " + startxref
#print("Replacing prev from '%s' to '%s'" % (elem, "Prev " + startxref))
elif elem.startswith("ID[<"):
additional_data += elem.replace("><", "> <")
else:
additional_data += elem
additional_data += "/"
if not did_prev:
# remove two >> at end
additional_data = additional_data[:-3]
additional_data += "/Prev " + startxref + ">>" + "/"
#print("Faking Prev %s" % startxref)
additional_data = additional_data[:-1]
additional_data += "\r" + "startxref\r" + str(ptr) + "\r" + "%%EOF"
inp = open(ORIG_FILE, "rb")
out = open(filename_out, "wb")
out.write(inp.read())
out.write(additional_data.encode("latin-1"))
inp.close()
out.close()