1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
| from PIL import Image, ImageDraw, ImageFont import httpx from typing import List from io import BytesIO from pathlib import Path from botoy import logger import time from tenacity import Retrying, RetryError, stop_after_attempt
curFileDir = Path(__file__).parent
cookies = { "ipb_member_id": "", "ipb_pass_hash": "", "yay": "louder", "igneous": "" }
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62", "Referer": "https://exhentai.org/", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"
}
class Draw: def __init__(self, image_urls, texts): self.ONE_LINE_MAX = 3 self.GAP_X = 15 self.pic_with_text_gap = 8 self.font = ImageFont.truetype(str(curFileDir / "files" / "LXGWWenKaiMono-Regular.ttf"), 17)
self.image_urls: List[str] = image_urls self.texts: List[str] = texts self.images: List[Image.Image] = [] self.processed_texts: List[str] = [] self.text_sizes: List[tuple] = [] self.pic_coordinates: List[tuple] = [] self.text_coordinates: List[tuple] = []
def download_all_pic(self) -> bool: """ 下载所有封面 :return: """ print("开始下载") with httpx.Client() as client: for url in self.image_urls: try: for attempt in Retrying(stop=stop_after_attempt(3)): with attempt: pic_bytes = client.get(url, headers=headers, cookies=cookies).content except RetryError: logger.error(f"ex:下载{url}错误") self.images.append(Image.open(curFileDir / "files" / "error_page.jpg")) continue self.images.append(Image.open(BytesIO(pic_bytes))) print("下载完成") print(len(self.images)) if len(self.images) == len(self.image_urls): return True return False
def build_coordinate(self) -> (List[tuple], List[tuple]): """ 生成瀑布流坐标 :param pics: 图片列表 :param self.text_sizes: 文本的实际占用大小 :return: 图片坐标列表,文本坐标列表 """ print(len(self.images)) for i in range(len(self.images)): if i < self.ONE_LINE_MAX: coordinate_x = self.GAP_X + \ (self.pic_coordinates[i - 1][0] + self.images[i - 1].size[0] if i != 0 else 0) coordinate_y = 20 elif i != 0 and i % self.ONE_LINE_MAX == 0: print("换行") coordinate_x = self.GAP_X coordinate_y = self.text_sizes[i - self.ONE_LINE_MAX][1] + 2 * self.pic_with_text_gap + \ self.pic_coordinates[i - self.ONE_LINE_MAX][1] + \ self.images[i - self.ONE_LINE_MAX].size[1] else: coordinate_x = self.pic_coordinates[i - self.ONE_LINE_MAX][0] coordinate_y = self.text_sizes[i - self.ONE_LINE_MAX][1] + 2 * self.pic_with_text_gap + \ self.pic_coordinates[i - self.ONE_LINE_MAX][1] + \ self.images[i - self.ONE_LINE_MAX].size[1] print(f"NOW :{i}") self.pic_coordinates.append((coordinate_x, coordinate_y)) self.text_coordinates.append((coordinate_x, coordinate_y + self.pic_with_text_gap + self.images[i].size[1]))
def process_text(self): """ 处理长文本 :param pics: 文本对应上方的图片列表 :param text_list: 待处理的文本 :return: 处理完的文本 """ for i in range(len(self.texts)): text_tmp: str = "" text_finally: str = "" for char in self.texts[i]: text_tmp += char text_width, text_height = self.font.getsize(text_tmp) if text_width > self.images[i].size[0]: text_finally += (text_tmp[:-1] if text_finally == "" else text_tmp[1:-1]) + "\n" + text_tmp[-1] text_tmp = text_tmp[-1] if text_tmp[1:] != "": text_finally += text_tmp[1:] self.processed_texts.append(text_finally)
def get_text_sizes(self): """ 获取渲染后的文本实际大小 :param texts: 文本 :return: 渲染后文本的实际size """ for text in self.processed_texts: img = Image.new("RGB", (1, 1)) draw = ImageDraw.Draw(img) size = draw.textsize(text, self.font) self.text_sizes.append(size)
def get_background_size(self): X_MAX = 0 Y_MAX = 0 for i in range(0, len(self.images), self.ONE_LINE_MAX): imgs = self.images[i:i + self.ONE_LINE_MAX] X_tmp = sum([img.size[0] for img in imgs]) + (len(imgs) + 1) * self.GAP_X if X_tmp > X_MAX: X_MAX = X_tmp for line in range(self.ONE_LINE_MAX): y_tmp = 20 for i in range(line, len(self.images), 3): print(i) y_tmp += (self.images[i].size[1] + self.text_sizes[i][1] + self.pic_with_text_gap * 2) if y_tmp > Y_MAX: Y_MAX = y_tmp return X_MAX, Y_MAX
def main(self): if not self.download_all_pic(): return start_time = time.time() self.process_text() self.get_text_sizes()
bg_x, bg_y = self.get_background_size() background = Image.new('RGB', (bg_x, bg_y), (255, 255, 255)) self.build_coordinate() for i in range(len(self.images)): background.paste(self.images[i], (self.pic_coordinates[i][0], self.pic_coordinates[i][1])) draw = ImageDraw.Draw(background) for i in range(len(self.images)): draw.text(self.text_coordinates[i], text=self.processed_texts[i], font=self.font, fill='black') print(time.time() - start_time) background.show() with BytesIO() as bf: background.save(bf, format="JPEG", quality=80) return bf
if __name__ == '__main__': urls = [ 'https://exhentai.org/t/69/fb/69fb6d072e74f5d86b566a028fed07a5259d64fb-2683152-4400-3500-jpg_250.jpg', 'https://exhentai.org/t/c0/9b/c09b8c2fce0938ff0e2ca5917fcb4e9d58bfb536-215016-828-1280-jpg_250.jpg', 'https://exhentai.org/t/e8/3f/e83f75ed0e835b5f504eacf5e614e00432418c75-494024-872-1088-jpg_250.jpg', 'https://exhentai.org/t/c3/a5/c3a525882e5fa6b5ada3ba4a595d4f4c5c418f21-367626-898-784-jpg_250.jpg', 'https://exhentai.org/t/12/91/1291faa5d37f92be38cca6082f83ab55ba9f7563-1546320-1359-1920-jpg_250.jpg', 'https://exhentai.org/t/97/c8/97c8e1719c92aa46768f44f7e28512456a3382b6-1184155-1063-1500-jpg_250.jpg', 'https://exhentai.org/t/51/c8/51c84ef796e1ac91a2f13c8d1e8b7e6a1d96475a-553270-1200-675-jpg_250.jpg', 'https://exhentai.org/t/17/d9/17d9977eac6cd3717c750051b7e955ee9410b19e-394252-1200-675-jpg_250.jpg', 'https://exhentai.org/t/89/59/8959ece7934f525dba6fa2fc99bb54c383b7826e-313170-1200-675-jpg_250.jpg', 'https://exhentai.org/t/83/dc/83dcb40fa317f6aaa8be36c1162e3a3c3dedf00a-319202-1200-675-jpg_250.jpg', 'https://exhentai.org/t/77/23/7723048a985fa419d07d89d605eae9000ca04eb4-334997-1200-675-jpg_250.jpg', 'https://exhentai.org/t/9d/6c/9d6c5f825d44bae12303da239a7c5e6c79ce4428-459684-1200-675-jpg_250.jpg', 'https://exhentai.org/t/f4/66/f46618de2c1a1db184a84db8544180dc09ca1163-346335-1200-675-jpg_250.jpg', 'https://exhentai.org/t/5e/47/5e477c56f5476ce770cc2bdebdd911817cb11c3b-541571-1200-675-jpg_250.jpg', 'https://exhentai.org/t/ad/9c/ad9c1518a8f772f758cfd8c4f82c3cabf78384ca-386148-1200-675-jpg_250.jpg', 'https://exhentai.org/t/57/e3/57e3a18edee853e8e7896869acbc017857c3c78b-337336-1200-675-jpg_250.jpg', 'https://exhentai.org/t/85/88/85884214c4e2da1fe535aba2f7ceb0a9099b2cf0-426860-1200-675-jpg_250.jpg', 'https://exhentai.org/t/77/1a/771a4c3d41aa4c402ce1bbb05a2b8bb2b2c76104-494156-1200-675-jpg_250.jpg', 'https://exhentai.org/t/41/ab/41ab7891073e8fb07ab6fe73a3e8430d228ad025-556780-1200-675-jpg_250.jpg', 'https://exhentai.org/t/1a/12/1a12bfc5c1270c3ec05e52d7dd168817057a2100-295172-1200-675-jpg_250.jpg', 'https://exhentai.org/t/4b/4a/4b4ab01305648fcbcde875a60e210a3278f2a85e-4401307-4961-7016-jpg_250.jpg', 'https://exhentai.org/t/c5/06/c5063a6d896a56fc7eb7ba33e10d0f722883546e-2834338-1308-1864-png_250.jpg', 'https://exhentai.org/t/08/a7/08a79fc3aa9702f7d1b8029547b94f6b2724a521-383247-800-1159-jpg_250.jpg', 'https://exhentai.org/t/f4/63/f46387049afcecb47375278afcb69571b0f73e17-214700-800-1252-jpg_250.jpg', 'https://exhentai.org/t/a8/36/a8365d1cf7b13d3d96636b6774cb9424b9ef28a8-287840-729-980-jpg_250.jpg']
txts = ['[FANBOX] dangonesan [2021-12-24]', '[phrannd] Overstay(ongoing) [Chinese] [lostecho个人汉化]', '[tokunocin (Tokuno Yuika)] Mousou Shoujo Kikuri-chan 想治治妹妹这个臭丫头的样子!(妄想少女篇) [Chinese] [无糖·漫画组]', '[Shimahara] Uchi no Neko ga Onnanoko de Kawaii 我家的猫猫是可爱的女孩子! [Chinese] [无糖·漫画组][绿茶汉化组]', '[Aomushi] Petted Girl 被饲养的女孩 (COMIC Shitsurakuten 2021-08) [夜空下的萝莉x真不可视汉化组]', '[Aomushi] Pet Girl 饲养女孩 (COMIC Shitsurakuten 2021-07) [夜空下的萝莉x真不可视汉化组]', 'Spirit sacrificeⅠ—— EP22', 'Spirit sacrificeⅠ—— EP21', 'Spirit sacrificeⅠ—— EP20', 'Spirit sacrificeⅠ—— EP19', 'Spirit sacrificeⅠ—— EP18', 'Spirit sacrificeⅠ—— EP17', 'Spirit sacrificeⅠ—— EP16', 'Evil Metropolis Ⅳ —— Return to Golden City 6', 'Evil Metropolis Ⅳ —— Return to Golden City 5', 'Evil Metropolis Ⅳ —— Return to Golden City 4', 'Evil Metropolis Ⅳ —— Return to Golden City 3', 'Evil Metropolis Ⅳ —— Return to Golden City 2', 'Evil Metropolis Ⅲ —— Penalty Golden City 12', 'Evil Metropolis Ⅳ —— Return to Golden City 1', '[安堂流] お母さんいただきます。2 連載 P1-35 [空気系☆漢化]', '[Unmei no Ikasumi (Harusame)] Suwarete Dame nara Suttemiro! (Touhou Project) [Chinese] [現場目睹全過程的古明地戀個人漢化] [Digital]', 'Chinese 神契 幻奇谭(刘冲L.Dart)02', 'Chinese 神契 幻奇谭(刘冲L.Dart)01', '[Pixiv] ebiblue (379606) [Chinese] [白杨汉化组]']
test = Draw(urls, txts) pic_bytes = test.main()
|