Skip to content

Instantly share code, notes, and snippets.

@fireattack

fireattack/bw.py Secret

Forked from Aloxaf/rerange.py
Last active Oct 14, 2021
Embed
What would you like to do?
BOOKWALKER 图片提取
from pathlib import Path
import zipfile
import sys
import json
from shutil import copy2
from PIL import Image
# pip install Pillow -U
SETTING_SAVE_FORMAT = '.bmp'
SETTING_AUTO_CROP_ANIMEDIA = True
def load_json(filename):
filename = Path(filename)
with filename.open('r', encoding='utf-8') as f:
data = json.load(f)
return data
def main(input_file):
input_file = Path(input_file)
if input_file.is_file() and input_file.suffix == '.zip':
p = input_file.with_suffix('')
with zipfile.ZipFile(input_file, 'r') as zf:
zf.extractall(p)
elif input_file.is_dir():
p = input_file
else:
print(f'[E] Invalid input {input_file}.')
sys.exit(1)
coords = load_json(p / 'coords.json')
metadata = load_json(p / 'metadata.json')
def get_dummy(identifier):
dummy_width = 0
dummy_height = 0
keyword = f'/{identifier}.xhtml'
idx = None
for key, value in metadata['normal_default']['t1b'].items():
if keyword in key:
idx = value
break
assert idx is not None
info = metadata['normal_default']['files'][idx]['FileLinkInfo']['PageLinkInfoList'][0]['Page']
if 'DummyWidth' in info:
dummy_width = info['DummyWidth']
if 'DummyHeight' in info:
dummy_height = info['DummyHeight']
return dummy_width, dummy_height
raw = p / 'raw'
raw.mkdir(exist_ok=True)
for f in [f for f in p.iterdir() if f.suffix == '.jpeg']:
processed = False
print(f'[I] Processing {f.name}...')
img = Image.open(f)
new = Image.new("RGB", img.size)
try:
coord = coords[f.name]
if coord: # images that are NOT encoded have [] as coord
for pos in coord:
if isinstance(pos, dict) and 'srcX' in pos:
part = img.crop((pos['destX'], pos['destY'], pos['destX'] + pos['width'], pos['destY'] + pos['height']))
new.paste(part, (pos['srcX'], pos['srcY'], pos['srcX'] + pos['width'], pos['srcY'] + pos['height']))
else:
part = img.crop((pos[0], pos[1], pos[0] + pos[2], pos[1] + pos[3]))
new.paste(part, (pos[4], pos[5], pos[4] + pos[6], pos[5] + pos[7]))
processed = True
except Exception as e:
print(f.name, e)
dummy_width, dummy_height = get_dummy(f.stem)
if dummy_width > 0 or dummy_height > 0:
processed = True
print(f'[I] {f.name} has bleeding of ({dummy_width}, {dummy_height}). Cropping...')
new = new.crop(
(0, 0, new.size[0] - dummy_width, new.size[1] - dummy_height))
# Only re-save if the image was actually processed (cropped, re-arranged, etc.)
if processed:
f.rename(raw / f.name)
new.save(f.with_suffix(SETTING_SAVE_FORMAT))
else:
print(f'[W] {f.name} is not actually processed. Keep the original JPEG file.')
copy2(f, raw / f.name)
# automatically crop images from Seiyuu Animedia, Animedia and Megami (all from the publisher Gakken Plus)
if SETTING_AUTO_CROP_ANIMEDIA and any(mag in p.name for mag in ['アニメディア', 'メガミマガジン']):
backup = p / 'rearrange_only'
backup.mkdir(exist_ok=True)
print(f'[I] This book ({p.name}) is SeiA or Animedia. Cropping...')
for f in p.iterdir():
if f.suffix in ['.jpeg', SETTING_SAVE_FORMAT]:
print(f'Processing {f.name}...')
im = Image.open(f)
w, h = im.size
if w == 1851 and h == 2339:
im_cropped = im.crop((8, 18, w - 8, h - 2))
else:
print(f'[E] {f.name} has wrong dimension!')
continue
f.rename(backup / f.name)
im_cropped.save(f.with_suffix(SETTING_SAVE_FORMAT))
print('[I] Done!')
if __name__ == '__main__':
if len(sys.argv) == 2:
input_file = sys.argv[1]
main(input_file)
else:
print('Usage: bw.py <input zip file or folder>')
// ==UserScript==
// @name bookwalker 图片提取
// @namespace Aloaxf
// @version 2.1
// @description 提取 bookwaler 中的图片
// @author Aloxaf, fireattack
// @updateURL https://gist.github.com/fireattack/384f01e76c3e42f6e24d97e8b56a6387/raw/d676569681ee71b45cf3a724499cd3c702d527fa/userscript.user.js
// @match https://pcreader.bookwalker.com.tw/*
// @match https://viewer.bookwalker.jp/*
// @match https://viewer-subscription.bookwalker.jp/*
// @icon https://www.google.com/s2/favicons?domain=bookwalker.com.tw
// @require https://cdn.jsdelivr.net/gh/eligrey/FileSaver.js@2.0.4/dist/FileSaver.min.js
// @require https://cdn.jsdelivr.net/gh/Stuk/jszip@3.5.0/dist/jszip.min.js
// @require https://cdn.jsdelivr.net/gh/stuk/jszip-utils@0.1.0/dist/jszip-utils.min.js
// @grant unsafeWindow
// ==/UserScript==
function addToZip(name, url) {
let content = new Promise((resolve, reject) => {
JSZipUtils.getBinaryContent(url, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
imagepack.file(name, content, { binary: true });
}
function drawImageHook(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
if (image.src && image.src.indexOf('.jpeg') != -1) {
let name = image.src.match(/(?<=\/)[^/]+(?=\.xhtml)/)[0] + ".jpeg";
let count = Object.keys(coords).length + 1;
if (!coords[name]) {
console.log(`[${count}] downloading ${name}`);
addToZip(name, image.src);
coords[name] = new Set();
}
coords[name].add([sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight]);
}
return CanvasRenderingContext2D.prototype._drawImage.apply(this, arguments);
}
function setToJson(key, value) {
if (typeof value === 'object' && value instanceof Set) {
// remove duplicates
let arr = [];
let arr_str = [];
for (let item of value) {
if (!arr_str.includes(item.join(','))) {
arr.push(item);
arr_str.push(item.join(','));
}
}
return arr;
}
return value;
}
let imagepack = new JSZip();
let coords = {};
let autoscroll = false;
CanvasRenderingContext2D.prototype._drawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = drawImageHook;
unsafeWindow.saveAs = saveAs;
unsafeWindow.saveAsZip = () => {
console.log('Start saving zip file, it may take awhile...')
imagepack.file('metadata.json', JSON.stringify(NFBR.a6G.Initializer.F5W.menu.model.get('o3r')['content']));
imagepack.file('coords.json', JSON.stringify(coords, setToJson));
imagepack
.generateAsync({ type: "blob" })
.then(blob => {
saveAs(blob, NFBR.a6G.Initializer.F5W.menu.model.get('contentTitle') + " [BW].zip");
console.log('Done.');
});
};
setTimeout(function () {
if (confirm('Start auto-scroll?')) {
var totalPageCount = NFBR.a6G.Initializer.F5W.menu.model.get('F8O');
var index = {};
var o = NFBR.a6G.Initializer.F5W.menu.model.get('o3r').content.normal_default.t1b;
for (const property in o) {
index[o[property]] = property.match(/(?<=\/)[^/]+(?=\.xhtml)/)[0] + ".jpeg";
}
NFBR.a6G.Initializer.F5W.menu.a6l.moveToFirst();
setTimeout(function () {
var timer = setInterval(function () {
var currentPage = NFBR.a6G.Initializer.F5W.menu.model.get('viewerPage');
var nextPage = -1;
for (const i in index) {
if (!Object.keys(coords).includes(index[i])) {
nextPage = Number(i);
break;
}
}
if (nextPage > currentPage + 2) { //在首页只会自动load 0、1、2这三页,所以最多+2 否则会卡住
console.log('Changing page to ' + nextPage);
NFBR.a6G.Initializer.F5W.menu.a6l.moveToPage(Number(nextPage));
}
if (Object.keys(coords).length == totalPageCount) {
console.log('Downloaded all the pages!');
if (confirm('Parsing finished. Save zip?')) unsafeWindow.saveAsZip();
clearInterval(timer);
}
}, 200);
}, 1000); //+1s
}
}, 6000); //6秒后开始自动载入
// ==UserScript==
// @name bookwalker 图片提取
// @namespace fireattack
// @version 3.5
// @description 提取 Bookwalker 中的图片
// @author Aloxaf, fireattack
// @updateURL https://gist.github.com/fireattack/384f01e76c3e42f6e24d97e8b56a6387
// @match https://pcreader.bookwalker.com.tw/*
// @match https://viewer.bookwalker.jp/*
// @match https://viewer-subscription.bookwalker.jp/*
// @icon https://www.google.com/s2/favicons?domain=bookwalker.com.tw
// @require https://cdn.jsdelivr.net/gh/eligrey/FileSaver.js@2.0.4/dist/FileSaver.min.js
// @require https://cdn.jsdelivr.net/gh/Stuk/jszip@3.5.0/dist/jszip.min.js
// @require https://cdn.jsdelivr.net/gh/stuk/jszip-utils@0.1.0/dist/jszip-utils.min.js
// @grant unsafeWindow
// ==/UserScript==
function addToZip(name, url) {
let content = new Promise((resolve, reject) => {
JSZipUtils.getBinaryContent(url, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
imagepack.file(name, content, { binary: true });
}
let imagepack = new JSZip();
let coords = {};
let downloaded = [];
let timer;
unsafeWindow.saveAs = saveAs;
unsafeWindow.saveAsZip = () => {
console.log('Start saving zip file, it may take awhile...')
imagepack.file('metadata.json', JSON.stringify(NFBR.a6G.Initializer.F5W.menu.model.get('o3r')['content']));
imagepack.file('coords.json', JSON.stringify(coords));
imagepack
.generateAsync({ type: "blob" })
.then(blob => {
saveAs(blob, NFBR.a6G.Initializer.F5W.menu.model.get('contentTitle') + " [BW].zip");
console.log('Done.');
});
};
unsafeWindow.checkStatus = () => {
console.log(downloaded);
return downloaded;
}
unsafeWindow.stop = () => {
clearInterval(timer);
}
function hook() {
const backup = NFBR.a6G.a5x.prototype['U8j'];
NFBR.a6G.a5x.prototype['U8j'] = function () {
const [targetCanvas, page, image, drawRect, flag] = arguments;
if (image && image.src && image.src.indexOf('.jpeg') != -1) {
let name = image.src.match(/(?<=\/)[^/]+(?=\.xhtml)/)[0] + ".jpeg";
let count = Object.keys(coords).length + 1;
if (!coords[name]) {
console.log(`[${count}] downloading ${name}`);
addToZip(name, image.src);
if (page.u5T === undefined) coords[name] = [];
else coords[name] = NFBR.a6G.Initializer.F5W.renderer['n0v'](page, image.width, image.height);
downloaded.push(page.index);
}
}
return backup.apply(this, arguments);
};
console.log('Hook successful!')
}
setTimeout(function () {
hook();
if (confirm('Start auto-scroll?')) {
var totalPageCount = NFBR.a6G.Initializer.F5W.menu.model.get('F8O');
NFBR.a6G.Initializer.F5W.menu.a6l.moveToFirst();
setTimeout(function () {
timer = setInterval(function () {
let undownloaded = [...Array(totalPageCount).keys()].filter(x=>!downloaded.includes(x));
if (undownloaded.length === 0) {
console.log('Downloaded all the pages!');
if (confirm('Parsing finished. Save zip?')) unsafeWindow.saveAsZip();
clearInterval(timer);
} else {
let nextPage = Math.min(...undownloaded);
// console.log('Next undownloaded pages is', nextPage);
let currentPage = NFBR.a6G.Initializer.F5W.menu.model.get('viewerPage');
if (nextPage > currentPage + 1 || nextPage < currentPage) { //一次翻两页或者往回翻
console.log('Changing page to ' + nextPage);
NFBR.a6G.Initializer.F5W.menu.a6l.moveToPage(nextPage);
}
}
}, 2000);
}, 2000); //+1s
}
}, 6000); //6秒后开始自动载入
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment