sigilplugins/imagecrunch/plugin.py
2019-08-03 00:49:15 -07:00

118 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