2022/12/16

【Python】画像のみの固定レイアウトのePubをPDFに変換する

だいぶ前から漫画などの固定レイアウトEpubを目次付きPDFに変換するプログラムをPythonで作りたい、ということで色々と検討してきました。 前回前々回の記事で紹介した内容になりますが、PythonのライブラリのEbookLibimg2pdfが使い勝手が良さそうだということがわかりました。

そこで本記事では、流行りの「ChatGPT」に、プログラムを作成してもらいました。
「画像のみの固定レイアウトepubを、PDFに変換するプログラムをPythonのebooklibとimg2pdfを使って作成してください」とチャットに送信した結果、以下の動画のようになりました。実際には、複数回実行して、ある程度欲しい結果になるまで繰り返し実行しています。



ChatGPTに何回か出力させたプログラムを組み合わせて、さらに足りてない部分などを加えて、修正したものが以下になります。

import img2pdf
import ebooklib
from ebooklib import epub
# 1. EPUBを読み込む
book = epub.read_epub('input.epub')
# 2. EPUBの各ページを画像として取得する
items = []
for item in book.get_items():
if item.get_type() == ebooklib.ITEM_COVER:
items.append(item.get_content())
elif item.get_type() == ebooklib.ITEM_IMAGE:
items.append(item.get_content())
# 3. 画像をPDFに変換する
with open('output.pdf', 'wb') as f:
f.write(img2pdf.convert(items))

かなり実行速度が速く、体感的には一瞬で変換してくれました。まだ、目次の移植や右綴じへの変換などは出来ていないので、追々検討しようと思います。

ChatGPTが、吐き出したプログラムが実際には動かないプログラムというケースもありますが、今回の事例では良い精度で実行したい内容を動くプログラムにしてくれたので驚いています。 今後は、知りたいことを検索エンジンで似たようなことをやっている記事を探すのではなく、チャットで正解例を教わるような時代になるのでしょうか?
今後の進展が気になります。

追記:2022/12/18
pikepdfで右綴じにする部分を追加しました。
import io
import pikepdf
import img2pdf
import ebooklib
from ebooklib import epub
# 1. EPUBを読み込む
book = epub.read_epub('input.epub')
# 2. EPUBの各ページを画像として取得する
items = []
for item in book.get_items():
if item.get_type() == ebooklib.ITEM_COVER:
items.append(item.get_content())
elif item.get_type() == ebooklib.ITEM_IMAGE:
items.append(item.get_content())
# 3. 画像をPDF形式に変換する
file_obj = io.BytesIO(img2pdf.convert(items))
# 4. PDFを右綴じ(R2L)にする
with pikepdf.Pdf.open(file_obj) as pdf:
pdf.Root.PageLayout = pikepdf.Name.TwoPageRight
pdf.Root.ViewerPreferences = pikepdf.Dictionary()
pdf.Root.ViewerPreferences.Direction = pikepdf.Name.R2L
pdf.save('output_R2L.pdf')

追記:2023/01/09
ePubからの目次を移植する部分を追加しました。
import io
import pikepdf
import img2pdf
import ebooklib
from ebooklib import epub, utils
from pikepdf import Pdf, OutlineItem
# 1. ePubを読み込む
book = epub.read_epub('input.epub')
# 2. ePubの各ページのファイル名と画像を取得する
page_names = []
page_items = []
for item in book.get_items():
if item.get_type() == ebooklib.ITEM_COVER:
page_names.append(item.get_name())
page_items.append(item.get_content())
elif item.get_type() == ebooklib.ITEM_IMAGE:
page_names.append(item.get_name())
page_items.append(item.get_content())
# 3. ePub内の画像をPDF形式に変換する
file_obj = io.BytesIO(img2pdf.convert(page_items))
# 4. ePubの目次情報を取得し、画像リストと照らし合わせる
index_numbers = []
for item in book.get_items_of_type(ebooklib.ITEM_DOCUMENT):
body = utils.parse_html_string(item.get_body_content())
for elem in body.iter():
if 'xlink:href' in elem.attrib:
for chapter in book.toc:
if item.get_name() == chapter.href:
image_href = elem.attrib.get('xlink:href')[3:]
index_number = page_names.index(image_href)
index_numbers.append(index_number)
elif 'src' in elem.attrib:
for chapter in book.toc:
if item.get_name() == chapter.href:
image_href = elem.attrib.get('src')[3:]
index_number = page_names.index(image_href)
index_numbers.append(index_number)
# 5. PDFに目次情報を書き込み、右綴じ(R2L)にする
with pikepdf.Pdf.open(file_obj) as pdf:
outitem = []
for chapter, index_number in zip(book.toc, index_numbers):
outitem.append(OutlineItem(chapter.title, index_number))
with pdf.open_outline() as outline:
outline.root.extend(outitem)
pdf.Root.PageLayout = pikepdf.Name.TwoPageRight
pdf.Root.ViewerPreferences = pikepdf.Dictionary()
pdf.Root.ViewerPreferences.Direction = pikepdf.Name.R2L
pdf.save('output_outline_R2L.pdf')
追追記:2023/02/24
Ebooklibを使わずに、BeautifulSoupを使うバージョンを作成しました。
コマンドライン実行を想定していて、綴じ方なども指定できるように変更しました。


今後は、逆に画像のみのPDFから固定レイアウトのePubへの変換プログラム作成に取り組みたいと思います。

2022/11/22

【Python】DEMデータをvtkに変換して、ParaViewで表示する

ParaViewは、2次元・3次元データの可視化のためのオープンソースソフトウェアです。可視化機能が強力でフリーで使えるため人気があります。
Pythonの可視化ライブラリには、matplotlibやplotlyなどがありますが、基本的には2次元までデータの可視化向きで、3次元データの可視化にはParaViewを使った方が動作も軽いです。

本記事では、以前の記事と同様に、国土地理院の基盤地図情報(数値標高モデル)のDEMデータから変換したGeoTIFFを用いました。DEMデータの結合方法やGDALの導入については、本記事でも割愛します。
また今回は、3次元表示に当たり軸の単位が揃っていた方が都合が良いため、GeoTIFFへの変換時にUTM座標に変換したデータを用いました。

実行環境:MacBook Air(M2、2022、メモリ:16GB)

ParaViewでは、GeoTIFF形式のDEMデータも読み込めるようですが、下記の例では、DEMデータの指定範囲での切り出しや解像度変更、vtk(Visualization Toolkit)形式への変換を試しました。

スクリプト例:
import numpy as np
import pyvista as pv
from osgeo import gdal
from pyproj import Transformer
from scipy.interpolate import griddata
# UTM座標に変換済の標高モデルを読み込む
ds = gdal.Open('./merge_utm.tif', gdal.GA_ReadOnly)
zz = ds.GetRasterBand(1).ReadAsArray()
nrows, ncols = zz.shape
x0, dx, dxdy, y0, dydx, dy = ds.GetGeoTransform()
x1 = x0 + dx * ncols
y1 = y0 + dy * nrows
xx, yy = np.meshgrid(np.linspace(x0, x1, ncols), np.linspace(y1, y0, nrows))
# 緯度経度(JGD2011)=>UTM座標系(JGD2011)
epsg6668_to_epsg6690 = Transformer.from_crs('epsg:6668', 'epsg:6690')
# 緯度経度で切り出し範囲を指定する
lim_x0, lim_y0 = epsg6668_to_epsg6690.transform(34.685, 133)
lim_x1, lim_y1 = epsg6668_to_epsg6690.transform(35.96, 135)
# 切り出した範囲の解像度を指定する
new_ncols = 1400
new_nrows = 1800
# 指定した設定でメッシュを構築する
new_xx, new_yy = np.meshgrid(np.linspace(lim_x0, lim_x1, new_ncols), np.linspace(lim_y1, lim_y0, new_nrows))
new_zz = griddata(points=np.stack([xx.reshape(-1,), yy.reshape(-1,)], 1), values=np.flipud(zz).reshape(-1,), xi=(new_xx, new_yy), method='nearest')
# geotiffへの変換の際に海域の標高を-9999としているため、NaNに置換する
new_zz[new_zz < -999] = np.nan
# vtk形式に格納する
points = np.stack((new_xx.ravel(order='F'), new_yy.ravel(order='F'), new_zz.ravel(order='F')), axis=1)
grid = pv.StructuredGrid()
grid.points = points
grid.dimensions = 1, new_nrows, new_ncols
grid['Elevation'] = new_zz.ravel(order='F')
# NaNを含むため、Binary形式で保存する
grid.save('./test_DEM.vtk', binary=True)

出力例:Scaleの設定からZ軸方向を10倍に強調
参考:

また、以下のようにカラーマップを作成すると好きなカラーマップを追加できます。

スクリプト例:前回の記事と同様にwiki-schwarzwald-contを設定
import requests
pg = requests.get('http://soliton.vm.bytemark.co.uk/pub/cpt-city/wkp/schwarzwald/wiki-schwarzwald-cont.pg')
clist = [list(map(int, txt.split())) for txt in pg.text.splitlines()][::-1]
with open('./wiki-schwarzwald-cont.xml', mode='w') as f:
f.write('<ColorMaps>\n')
f.write('<ColorMap name="wiki-schwarzwald-cont" space="RGB">\n')
for c in clist:
point = '<Point x="'+str(c[0]/1500)+'" o="1" r="'+str(c[1]/255)+'" g="'+str(c[2]/255)+'" b="'+str(c[3]/255)+'"/>\n'
f.write(point)
f.write('</ColorMap>\n')
f.write('</ColorMaps>')

出力例:
関連記事

2022/08/08

【Python】PDF内の表をExcelに変換する

本記事のタイトルの内容で検索して出てくるのは、tabula-pyを使用した例が多いです。tabula-pyは、Javaのライブラリのラッパーであるため、Javaのインストールや設定が必要とのことで、どうにかtabula-pyを使わずにやる方法がないか検討しました。

そこで思いついたのが、以前紹介したwin32comを使用する方法です。win32comを使うことで、Microsoft Officeを操作できるため、Pythonから下記の動作を実行することができます。
  1. PDFファイルをWordで開く
  2. 表を選択してコピー
  3. Excelに貼り付け、名前をつけて保存
これで、PDF内の表をExcelファイルに変換する作業の自動化が可能です。xlsxにした後は、Pandasで読み込むなど自由に操作可能です。

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
view raw pdf_word.ipynb hosted with ❤ by GitHub

注意 : 使用できる環境等に条件があります。
  • win32comを使用するため、WindowsにMicrosoft Officeがインストールされている環境であること
  • PDFファイルの「セキュリティ設定」でコピーが禁止されていないこと
参考:

2022/06/19

calibreで出力したepubをKindleGenで処理できるように再変換する

以前の記事でKindleGenなどの使い方を紹介しましたが、Kindleもepub対応するとニュースになっていました。メール転送でepubが使えるようになるだけみたいなので、個人的にはもうしばらくはKindleGenも使って行きたいと思っています。

電子書籍管理ソフトウェアの「Calibre」で変換したepubが、KindleGenで変換できないケースがあります。Calibreでmobiなりazw3なりに変換すれば良いのでしょうが、設定が正しくないためか、レイアウトが崩れ表示が壊れるケースに見舞われたので、Pythonで変換スクリプトを書きました。

と言っても、画像中の左のファイル構成になっているものを、右のファイル構成に変換するのが主な処理です。万能なコードではなく、KindleGenが上手く処理できない一部のケースのみに対応しているので、ご留意ください。

日本語の縦書き書籍(漫画などではない)を想定しており、スタイルシートは、「電書協 EPUB 3 制作ガイド ver.1.1.3 2015年1月1日版」から一部のファイルをコピーして使用しています。事前にダウンロードして解凍して同一階層に必要なファイルを置いてください。

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

バグ等もあるかと思いますので、参考程度にご利用いただけると幸いです。

関連記事

2022/03/20

【Python】foliumで複数の地図タイルを切り替えて表示する方法

以前、「【Python】foliumで地質図を表示する方法」という記事を書きましたが、実用していく上で、地図タイルを切り替えられると便利だなと思い当たりました。

本記事では、複数の地図タイルを切り替えて表示する方法を紹介します。

以前の記事で紹介した地理院地図のタイルとシームレス地質図のタイルを例として、地図タイルの切り替えを行います。

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
出力結果


関連記事

2022/03/15

【Python】pptx形式のPowerPointファイルからテキストデータを抽出する

PowerPointファイルからテキストデータを抽出することで、テキストマイニングや文書ファイルの検索といった応用が可能になります。
本記事では、pptx形式のPowerPointファイルからテキストデータを抽出するPythonスクリプトについて、三種類の手法を比較して紹介します。

python-pptxを使用する


python powerpoint テキスト 抽出」などで検索してよくヒットするのが、「python-pptx」を使用する方法です。
python-pptxは、pptx形式のPowerPointファイルを操作できるpythonライブラリで、Pythonによるスライド作成の自動化などの記事をよく見かけますが、作成だけでなく読み取りも可能です。
注意点として、読み取り・書き込みパスワードや権限が設定されている場合は利用出来ないという問題があります。また、「x」のついていない方の.ppt形式のPowerPointファイルは開けないので、何らかの方法で.pptxに変換する必要があります。

例1:
import pptx
def pptx2text(file_path):
texts = [] # 抽出したテキストデータを格納する空リスト
prs = pptx.Presentation(file_path)
# スライドごとにテキストデータを抽出する
for sld in prs.slides:
for shape in sld.shapes:
# shapeに含まれるテキストデータを抽出
if shape.has_text_frame:
for text in shape.text.splitlines():
texts.append(text)
# tableに含まれるテキストデータを抽出
if shape.has_table:
for cell in shape.table.iter_cells():
for text in cell.text.splitlines():
texts.append(text)
return texts

Pythonの標準ライブラリを使用する


Office 2007 以降で保存可能になった.docx, .xlsx, .pptx といった拡張子のOfficeファイルは、XMLファイルなどをzip圧縮したものです。したがって、「re」や「zipfile」、「xml.etree.ElementTree」といった標準ライブラリだけを用いて読み取ることができます。
python-pptxを使用する場合と同様に、読み取り・書き込みパスワードや権限が設定されている場合はzipが解凍できないため利用出来ないという問題があります。また、.ppt形式の場合も、zipファイルではないので開けません。
ライブラリを追加したくない場合などはこの手法が使えると思います。

例2:
import re
import zipfile
import xml.etree.ElementTree as ET
def pptx2text(file_path):
namespaces = {'a':'http://schemas.openxmlformats.org/drawingml/2006/main'}
texts = []
with zipfile.ZipFile(file_path, 'r') as pptx:
files = [name for name in pptx.namelist() if re.match('ppt/slides/slide\d+.xml', name)]
files = sorted(files, key=lambda x:int((re.search(r"[0-9]+", x)).group(0)))
for file in files:
root = ET.fromstring(pptx.read(file))
text_nodes = root.findall('.//a:t', namespaces=namespaces)
for text_node in text_nodes:
text_node = ET.tostring(text_node, encoding='utf-8').decode('utf-8')
text_node = re.sub('<a:t>.*?</a:t>', '', text_node)
text_node = re.sub('<.*?>', '', text_node)
if text_node == '':
continue
texts.append(text_node)
return texts
参考:

pywin32(win32com)を使用する


Windows環境で実行するという条件がつきますが、この方法であればパスワードや権限が設定されているPowerPointファイルからもテキストが取り出せます。win32comとかPyWin32とかPython for Windows extensionsとか呼ばれているライブラリを用いる方法です。
VBAで操作することに慣れている場合は、細かいところまで手が届くので良いかと思います。この方法だと、PowerPointのアプリケーションを経由して情報が抽出されるので、PowerPointアプリにアカウント設定がされていれば、権限設定のある.pptx形式のみならず、.ppt形式のPowerPointファイルの場合も操作可能です。
問題として、WindowsPowerPointアプリのインストール・設定が必要となるので、環境構築の手間がかかります。

例3:
import win32com.client
def pptx2text(file_path):
texts = []
pptx = win32com.client.gencache.EnsureDispatch('PowerPoint.Application')
pptx.Visible = True # アプリが開く(0, Falseにするとエラー)
pptx.DisplayAlerts = False # 警告OFF
try:
pptx_file = pptx.Presentations.Open(file_path, True) # 読み取り専用で開く
slide_count = len(pptx_file.Slides) + 1
for i in range(1, slide_count):
slides = pptx_file.Slides(i)
shape_num = slides.Shapes.Count + 1
for j in range(1, shape_num):
try:
texts.append(slides.Shapes(j).TextFrame.TextRange.Text)
texts.append(slides.NotesPage.Shapes(j).TextFrame.TextRange.Text)
except:
pass
pptx_file.Close()
finally:
pptx.Quit()
return texts
参考:

win32comを使用することで、.pptファイルを.pptxファイルやpdfに変換することもできるので、上記の手法などと組み合わせて使うというのも一つの手かもしれません。

ここまで紹介した例では、「ノート」の中身のテキスト抽出について触れていませんでしたが、やり方を紹介されている方を見つけたのでリンクを掲載させていただきます。


関連記事: