前回までのあらすじ
「インターネットで人を集めましょう。SNS使って。それから活動していったほうがいいでしょう」。
お友達のお話を真に受けて、SNSでいろいろやっていました。
死ぬほどストレスです。
SNSは、目的を持ってやると、あんまり楽しくありません。

「静かな選挙をさせる会」から「しずかせんきょ」に名前を変えたのはいいけれど、
それのホームページのアイディアも思いつかないので、これまたストレスです。
最近だと、あんまりイラスト描くの好きじゃなくなって、むしろ苦痛になってしまっていたので、
イラストを描くのも大変なかんじになりました。
昔はばんばんアイディア思いついたのに。

という状況で、いろんな電子書籍が集まってきました。
電子書籍のくせに、積ん読してます。
それを整理しきれない状況になってきたので、Pythonで自動化しようと考えました。

事実のおさらい
目が疲れにくい(疲れないとは言ってない)、Kindle Paperwhiteというものを、昔買いました。
電子書籍を読む上では、けっこう重宝するので、けっこう使っています。

Kindle・・・で扱える電子書籍のファイルは、azw、azw3、mobi、pdfです。
epubは扱えません。
ただし、pdfはたいてい字が細かいので、老眼の人は読めません。
あと、mobiにしたものは、Calibreで別の形式に変換することができません(だと思った)。

AndroidやiPadなどには、それなりの電子書籍を読むためのアプリを別途インストールして、
いろいろ読みます。
アプリによって違いはあると思うけど、だいたいは、pdf、epubが読めます。
また、ものによってはazwやazw3が読めるものもあります。
iPadので、いいアプリはあんまりなかったような気がするけど、
Androidには、「ReadEra」というアプリがあります。
電子書籍の読み込みに時間かかるけど、とても使いやすいです。

そして実際に電子書籍を読むには、iPadよりも、Androidのほうが、
マイクロSDカードが使える分、たくさんの書籍を収蔵できます。

たくさんの電子書籍を別の電子書籍に変換するには
それには、種になる電子書籍が必要です。
azwでもepubでもpdfでもいいから、それを用意します。
そして、それをCalibreの力と、Pythonの力で、強力に変換します。
ただ、その前にやることがけっこうあります。
・・・画像じゃないファイルを無理やりpdfファイルにしているものを変換すると、
あまり上手に変換してくれません。

Calibreではタイトルがめちゃくちゃな英文字に変えられる
こんな恐ろしい仕様があり、これを変えることは、普通はできません(たぶん)。
これの対策として、Calibreの設定を変える必要があります。

ファイル名を題名に変えたいというか、そうしないと都合が悪い。
このページでは図解されていますが、
Calibreの電子書籍のファイル名を日本語にリネームする方法(なんだか紹介)
http://maxmur.blog.fc2.com/blog-entry-20.html
「環境設定」を選び、その中の「ディスクに保存」の項目を選びます。
「テンプレートの保存」のところに、次の言葉に置き換えます。
{title}
そして「適用」させて、Calibreを再起動します。

そのあと、どうにかしたい電子書籍を選んで(複数選択可)、
「ディスクに保存」を選びます。
任意のフォルダを選ぶと、そこに、作られたファイル全部が、
ファイル名が本の題名の名前になった形で、複製されます。

おもな複製の対象は、azw、azw3、epub、pdfなど。
あとは、フォルダの検索で、拡張子の条件をつけて検索すれば、
同じ拡張子のものばかり抽出して検索されます。
検索条件で抽出されたファイルを、別のフォルダにコピペすると、整理できます。
なお、mobiファイルは、ほかのファイル形式に変換できる保証がありません。

ここからがPythonの出番
種になるファイル(ここではazw、azw3、epub、pdfの4種類を想定)を、
特定のフォルダに入れることを想定しています。
「種本を入れるフォルダ」という名前にします。
そして、特定のフォルダに、azwとazw3(基本的にKindleで読めるので同じもの扱い)、epub、pdfを、
それぞれ振り分けて作ります。
ただし、pdfについては、全部のページが画像ファイルでできているものに限り、pdfを作ります。
(全部のページが画像ファイルのものではない場合は、pdfにしません)。
それぞれ「azw用」「epub用」「pdf用」と、振り分けます。
さらに、Calibreで作るpdfは、余計な余白を作ったり、画像が小さくなる場合があるので、
いったんzipファイルを作った上で、それを分解して、どの画像も、B5サイズにリサイズします。
そして、同じ拡張子のファイルの場合は、何もしないでファイルの移動だけさせます。
無駄な作業だから。

スクリプトを使う準備
Calibre力に全頼りですので、Calibreをインストールします。
そしてPythonを使う何かも。
やり方は割愛させていただきます。

ターミナルおよびコマンドプロンプトで、pip使ってモジュールをインストールします。
pip install img2pdf pillow
そして、それぞれの種本を、「種本を入れるフォルダ」に入れて、スクリプトを動かします。
手動でフォルダを作らなくても、スクリプトを起動すれば、フォルダを作ってくれるように作っています。

pdfは、B5サイズに仕上げます。
2024/2/29追記 もともとPDFだったファイルは、epubにしないでazw3に変換することにした。
2023/11/10追記 epubのフォルダにazw3が入っている変換ミスを修正
2023/11/11追記 画像のページが少ないとエラーになる場合があるのを力技で修正
2023/11/22追記 epubからもpdfを作れるように修正(条件さえあえば)。

このスクリプトは不完全です。いい線までいっているのですが。
完全版になるまで、調整は続くと思います。
PDF作りがうまくいかないものの原因を調査しています。

import os
import os
import shutil
import img2pdf
from PIL import Image
import subprocess
from natsort import natsorted

'''
このスクリプトは、それぞれのモジュールをインストールする必要もあるけど、
pip install img2pdf pillow
Calibreがないと動きません。
というわけで、Calibreをゲットよ。
https://calibre-ebook.com/download
'''

'''
このスクリプトの仕様
種本になるもの(azw、azw3、epub、pdf)を
「種本を入れるフォルダ」フォルダに入れて、
スクリプトを起動する。

基本的には、azw3、そしてepubかpdfのどちらかを作る。
ただし、azwファイルをazw3ファイルに作り直すようなことはしない。

pdfに関しては、zipの中身が、ページ数と画像の数が一致したときだけpdfにする。
一致するものは、ちゃんとしたpdfにできる。
つまり、Calibreでpdfにしたときに、
パソコン独自のフォントで本文が書かれるようなものはpdfにしない。
逆に、そういうものはepubのほうが読むのに向いている。

種本と同じ拡張子の書籍は作らないで、ただお目当てのフォルダに移動させるだけ。
コンピューターも上手にさぼらせてあげたほうが時間短縮になるし。
'''

#フォルダの中身を調べる
tane ="種本を入れるフォルダ/"
tanehon = []


#電子書籍を変換する
aaa="完成したazw3/"
eee="完成したepub/"
ppp="完成したpdf/"
itizi1="f背jぢおあwdpwjへふぇ/"
itizi2="さdふぇh誌魚絵wbhfgヴィ尾sdjぱじょいpdc/"


#フォルダがない場合はフォルダを用意する
if os.path.isdir("種本を入れるフォルダ")==False:
    os.mkdir("種本を入れるフォルダ")
if os.path.isdir("完成したazw3")==False:
    os.mkdir("完成したazw3")
if os.path.isdir("完成したepub")==False:
    os.mkdir("完成したepub")
if os.path.isdir("完成したpdf")==False:
    os.mkdir("完成したpdf")
dasigara="出し殻/"
if os.path.isdir(dasigara)==False:
    os.mkdir(dasigara)

#種本のフォルダの中身を調べる
def tanehon_nakami():
    global tanehon
    tanehon=[]
    for current_dir,sub_dirs,files_list in os.walk(tane):
        for file_name in files_list:
            tanehon.append(os.path.join(current_dir,file_name))

#epubに変換する
def not_pdf_henkan(hon):
    bufbuf=os.path.splitext(os.path.basename(hon))[0]
    bufbuf2=os.path.splitext(os.path.basename(hon))[1]
    print("{0}{1}は、pdfにできないっ!".format(bufbuf, bufbuf2))
    if bufbuf2==".epub" :
        subprocess.run("ebook-convert \"{0}\" \"{1}{2}.azw3\""\
                     .format(hon, aaa, bufbuf))
    elif bufbuf2==".azw" or bufbuf2==".azw3" or bufbuf2=="pdf":
        subprocess.run("ebook-convert \"{0}\" \"{1}{2}.epub\""\
                     .format(hon, eee, bufbuf))
    return "処理終わり"

def pdf_henkan(hon):
    #拡張子抜きの名前を調べる
    bufbuf=os.path.splitext(os.path.basename(hon))[0]
    bufbuf2=os.path.splitext(os.path.basename(hon))[1]
    
    #一時フォルダをつくる
    if os.path.isdir(itizi1)==True:
        shutil.rmtree(itizi1)
    os.mkdir(itizi1)
    if os.path.isdir(itizi2)==True:
        shutil.rmtree(itizi2)
    os.mkdir(itizi2)

    #すでにpdfがあるのならコピーして終了
    #azw3変換してもレイアウトくずれるだけ。どうせkindle機PDFで読めるし。
    if hon.endswith(".pdf")==True:
        shutil.copy2(hon,ppp+bufbuf+".pdf")
        return "処理終わり"
    #zipを一時フォルダ1に作る
    subprocess.run("ebook-convert \"{0}\" \"{1}{2}.zip\""\
                 .format(hon,itizi1,bufbuf))
    #一時フォルダをもう1つ作る
    if os.path.isdir(itizi2)==True:
        shutil.rmtree(itizi2)
    os.mkdir(itizi2)
    #zipを展開
    try:
        shutil.unpack_archive(itizi1+bufbuf+".zip",itizi2)
    except:
        not_pdf_henkan(hon)
        return "処理終わり"

    finally:
        #zip展開してからの処理
        hantei=True
        nakami=[]
        bufnakami=[]
        yougaaru=[]
        html_kazu=0
        gazo_kazu=0
        kekka=-749237
        #zipの中身全部リストに記録する
        for current_dir,sub_dirs,files_list in os.walk(itizi2):
            for file_name in files_list:
                nakami.append(os.path.join(current_dir,file_name))
        #ソートする
        nakami = natsorted(nakami)
        #pdfに適合するファイルの条件は、zipにしたときの中身が、
        #htmlと画像の数がほぼ一致していること
        cover=0
        i=0
        while i<len(nakami):
            if nakami[i].endswith(".html")==True or\
             nakami[i].endswith(".htm")==True :
                html_kazu+=1
                if nakami[i].find("_split_")!=-1:
                    hantei=False
            elif nakami[i].endswith(".gif")==True or\
                 nakami[i].endswith(".png")==True or\
                 nakami[i].endswith(".jpe")==True or\
                 nakami[i].endswith(".jpg")==True or\
                 nakami[i].endswith(".jpeg")==True or\
                 nakami[i].endswith(".tif")==True or\
                 nakami[i].endswith(".tiff")==True :
                gazo_kazu+=1
                if nakami[i].find("cover")!=-1:
                    cover+=1
                #bufnakami.append(nakami[i])
                yougaaru.append(nakami[i])
            i+=1
        #htmlと画像ファイルとの差が知りたいのである
        kekka = html_kazu - gazo_kazu
        print("html {0}".format(html_kazu))
        print("画像 {0}".format(gazo_kazu))
        #計算して、pdfを作るのにふさわしいか判定する
        if kekka <= 3 and kekka >= -1 and gazo_kazu != 0 and \
         html_kazu > 2 and hantei==True :
            hantei=True
        else:
            hantei=False
        
#        if bufbuf2==".epub" and html_kazu==1:
#            hantei=True
        if bufbuf2==".epub":
            hantei=False
            
        if hantei==False:
            not_pdf_henkan(hon)
            return "処理終わり"

#こういうpdf、一部だけだわあ

        #Calibreの作るzipは、たいてい表紙がcover.jpegかcover1.jpegとしてあるんだけど、
        #coverファイルがないときもある。
        #coverファイルがない場合は、最後のページが表紙になっている
#        i=0
#        if cover==0:
#            yougaaru.append(bufnakami[len(bufnakami)-1])
#            while i<len(bufnakami)-1:
#                yougaaru.append(bufnakami[i])
#                i+=1
#        else:
#            while i<len(bufnakami):
#                yougaaru.append(bufnakami[i])
#                i+=1

        #一見条件があっていても、
        #画像ファイルの大きさが極端に変わるのは、pdfにふさわしくない。
        tate=0
        yoko=0
        if hantei==False:
            not_pdf_henkan(hon)
            return "処理終わり"
        else:
            #画像ファイルの大きさを調べる
            try:
                gazo=Image.open(yougaaru[1])
                tate=gazo.height
                yoko=gazo.width
                gazo.close()
                i=1
                while i<6:
                    if i>=len(yougaaru):
                        gazo.close()
                        break
                    gazo=Image.open(yougaaru[i])
                    if tate-50 > gazo.height or \
                     tate+50 < gazo.height or \
                     yoko-50 > gazo.width or \
                     yoko+50 < gazo.width :
                        hantei=False
                        gazo.close()
                        break
                        gazo.close()
                    i+=1
            except:
                not_pdf_henkan(hon)
                return "処理終わり"
            if hantei==False:
                not_pdf_henkan(hon)
                return "処理終わり"
            else:
                    #縦長か横長かを判定して、B5のサイズを指定する
                if tate<yoko:
                    b5inpt = (img2pdf.mm_to_pt(257),img2pdf.mm_to_pt(182))
                    layout_fun = img2pdf.get_layout_fun(b5inpt)
                else:
                    b5inpt = (img2pdf.mm_to_pt(182),img2pdf.mm_to_pt(257))
                    layout_fun = img2pdf.get_layout_fun(b5inpt)
                #pdfを作る
                try:
                    with open(ppp+bufbuf+".pdf","wb") as f:
                        f.write(img2pdf.convert(yougaaru, layout_fun=layout_fun))
                    print("{0}.pdfがこんがりと焼けました。".format(bufbuf))
                except:
                    print("なぞのエラー")
                    return ""
        return "処理終わり"

def itizi_kesu():
    shutil.rmtree(itizi1)
    shutil.rmtree(itizi2)

def yaru():
    def yaru_nakami():
        i=0
        while i<len(tanehon):
            if tanehon[i].endswith("azw")==True or\
             tanehon[i].endswith("azw3")==True or\
             tanehon[i].endswith("epub")==True or\
             tanehon[i].endswith("pdf")==True :
                pdf_henkan(tanehon[i])
                itizi_kesu()
            if os.path.isdir(dasigara):
                if tanehon[i].endswith("azw")==True or \
                 tanehon[i].endswith("azw3")==True or \
                 tanehon[i].endswith("epub")==True or \
                 tanehon[i].endswith("pdf")==True :
                    shutil.copy2(tanehon[i], dasigara)
            os.remove(tanehon[i])
            i+=1

    tanehon_nakami()
    yaru_nakami()
        
    print("おしまいっ!")
    if len(tanehon)==0:
        print("「種本を入れるフォルダ」に種本入れて実行しなおしたら、いろいろやるよ~。")

yaru()



表紙が一番後ろに行ってしまう問題
ものによるのですが、一部のpdfでは、
表紙が一番後ろのページになってしまうことがあります。
原因は、よくわかりません。
起きる場合と、起きない場合がありますので、
この問題が起きてしまったpdfへの対処(直す)については、
後編に持ち越しにいたします。
やることが、ちょっとめんどうなので、1ページに収めるようなかんじにならない。

後編
https://ameblo.jp/araya-benki/entry-12828096019.html



参考資料

[解決!Python]例外を処理するには(IT MEDIA)
https://atmarkit.itmedia.co.jp/ait/articles/2306/06/news022.html

Calibre 電子書籍のフォーマット変換をコマンドで行うメモ(lisz-works)
https://www.lisz-works.com/entry/calibre-convert-memo

calibredb(calibre ユーザーマニュアル)
https://manual.calibre-ebook.com/ja/generated/ja/calibredb.html

コマンドの実行結果をファイルに出力する方法(PROJECT GROUP)
https://www.projectgroup.info/tips/Windows/cmd_0002.html

コマンドの実行結果・標準出力をファイルに保存(Webプログラミング入門・Linux入門)
https://webkaru.net/linux/redirect-output-to-file/#google_vignette

Pythonでディレクトリ名・ファイル名の一覧取得(os, glob)(パイワーーク)
https://python-work.com/file-get-list/

【Python】ファイルの拡張子を取得する(鎖プログラム)
https://pg-chain.com/python-endswith

Pythonでパス文字列からファイル名・フォルダ名・拡張子を取得、結合(note.nkmk.me)
https://note.nkmk.me/python-os-basename-dirname-split-splitext/

subprocessライブラリの使い方って結局何が正解なん(バイオベンチャーBIのtech blog)
https://snitch0.github.io/p/python-subprocess/

B5サイズの寸法、用途は?サイズ選びの基準もご紹介(よい印刷をより安く!gr@phic)
https://www.graphic.jp/feature/b5_size

PythonでZIPファイルを圧縮・解凍するzipfile(note.nkmk.me)
https://note.nkmk.me/python-zipfile/