119 lines
4 KiB
Python
119 lines
4 KiB
Python
|
import tkinter
|
||
|
import tkinter.messagebox
|
||
|
import io
|
||
|
import re
|
||
|
import PIL.Image
|
||
|
import xml.etree.ElementTree
|
||
|
|
||
|
def find_cover_id(book):
|
||
|
for (id, href, mimetype) in book.manifest_iter():
|
||
|
properties = book.id_to_properties(id)
|
||
|
if properties == 'cover-image':
|
||
|
return id
|
||
|
|
||
|
metadata = xml.etree.ElementTree.fromstring(book.getmetadataxml())
|
||
|
cover = [x for x in metadata.findall('meta') if x.get('name') == 'cover']
|
||
|
if cover:
|
||
|
return cover[0].get('content')
|
||
|
|
||
|
return None
|
||
|
|
||
|
def fit_into_bounds(image_width, image_height, frame_width, frame_height, only_shrink=False):
|
||
|
'''
|
||
|
Given the w+h of the image and the w+h of the frame,
|
||
|
return new w+h that fits the image into the frame
|
||
|
while maintaining the aspect ratio.
|
||
|
|
||
|
(1920, 1080, 400, 400) -> (400, 225)
|
||
|
'''
|
||
|
width_ratio = frame_width / image_width
|
||
|
height_ratio = frame_height / image_height
|
||
|
ratio = min(width_ratio, height_ratio)
|
||
|
|
||
|
new_width = int(image_width * ratio)
|
||
|
new_height = int(image_height * ratio)
|
||
|
|
||
|
if only_shrink and (new_width > image_width or new_height > image_height):
|
||
|
return (image_width, image_height)
|
||
|
|
||
|
return (new_width, new_height)
|
||
|
|
||
|
def collect_images(book, do_cover=False):
|
||
|
images = []
|
||
|
if do_cover:
|
||
|
cover_id = None
|
||
|
else:
|
||
|
cover_id = find_cover_id(book)
|
||
|
|
||
|
for (id, href, mimetype) in book.manifest_iter():
|
||
|
if id == cover_id:
|
||
|
continue
|
||
|
|
||
|
if mimetype == 'image/jpeg':
|
||
|
images.append(id)
|
||
|
return images
|
||
|
|
||
|
def choose_options():
|
||
|
options = {}
|
||
|
t = tkinter.Tk()
|
||
|
t.grid_columnconfigure(1, weight=1)
|
||
|
t.grid_rowconfigure(3, weight=1)
|
||
|
t.title('imagecrunch')
|
||
|
do_cover_intvar = tkinter.IntVar()
|
||
|
do_cover_intvar.set(1)
|
||
|
do_cover_checkbox = tkinter.Checkbutton(t, text='Compress the cover?', variable=do_cover_intvar)
|
||
|
do_cover_checkbox.grid(row=0, column=0, columnspan=2, sticky='w')
|
||
|
tkinter.Label(t, text='max dimension').grid(row=1, column=0, sticky='w')
|
||
|
dimension_slider = tkinter.Scale(t, from_=100, to=2000, resolution=10, orient=tkinter.HORIZONTAL)
|
||
|
dimension_slider.grid(row=1, column=1, sticky='we')
|
||
|
dimension_slider.set(500)
|
||
|
tkinter.Label(t, text='jpeg quality').grid(row=2, column=0, sticky='w')
|
||
|
quality_slider = tkinter.Scale(t, from_=1, to=100, orient=tkinter.HORIZONTAL)
|
||
|
quality_slider.grid(row=2, column=1, sticky='we')
|
||
|
quality_slider.set(50)
|
||
|
def commit():
|
||
|
options['do_cover'] = do_cover_intvar.get()
|
||
|
options['quality'] = quality_slider.get()
|
||
|
options['max_dimension'] = dimension_slider.get()
|
||
|
t.destroy()
|
||
|
ok_button = tkinter.Button(t, text='OK', command=commit, bg='#00ff00')
|
||
|
ok_button.grid(row=3, column=0, columnspan=2, sticky='ews')
|
||
|
t.mainloop()
|
||
|
return options
|
||
|
|
||
|
def imagecrunch(book, options):
|
||
|
total_original_size = 0
|
||
|
total_new_size = 0
|
||
|
|
||
|
for id in collect_images(book, do_cover=options['do_cover']):
|
||
|
data = io.BytesIO(book.readfile(id))
|
||
|
original_size = len(data.read())
|
||
|
total_original_size += original_size
|
||
|
data.seek(0)
|
||
|
i = PIL.Image.open(data)
|
||
|
data = io.BytesIO()
|
||
|
# i = i.convert('L')
|
||
|
new_dimension = fit_into_bounds(*i.size, options['max_dimension'], options['max_dimension'], only_shrink=True)
|
||
|
i = i.resize(new_dimension, resample=PIL.Image.ANTIALIAS)
|
||
|
i.save(data, format='jpeg', quality=options['quality'])
|
||
|
data.seek(0)
|
||
|
new_size = len(data.read())
|
||
|
if new_size >= original_size:
|
||
|
total_new_size += original_size
|
||
|
continue
|
||
|
total_new_size += new_size
|
||
|
data.seek(0)
|
||
|
book.writefile(id, data.read())
|
||
|
print(id, 'shrunk from', int(original_size / 1024), 'K', 'to', int(new_size / 1024), 'K')
|
||
|
|
||
|
print('Total shrunk from', int(total_original_size / 1024), 'K', 'to', int(total_new_size / 1024), 'K')
|
||
|
|
||
|
def run(book):
|
||
|
options = choose_options()
|
||
|
print(options)
|
||
|
if not options:
|
||
|
return 1
|
||
|
|
||
|
imagecrunch(book, options)
|
||
|
return 0
|