質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
マージ

複数のデータベースやファイル、プログラムなどを決まった手順や規則に従って一つに結合すること。

PDF

PDF(Portable Document Format)とはISOによって国際標準として制定されている電子ドキュメント用の拡張子です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

1715閲覧

Pythonで偶数ページと奇数ページに分かれた2つのPDFをマージしたい

SatelliteStar

総合スコア2

マージ

複数のデータベースやファイル、プログラムなどを決まった手順や規則に従って一つに結合すること。

PDF

PDF(Portable Document Format)とはISOによって国際標準として制定されている電子ドキュメント用の拡張子です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2020/09/11 18:30

編集2020/09/13 00:02

まえがき

Python初心者です。
オライリージャパン「退屈なことはPythonにやらせよう」
を参考にして組んだプログラムです。

###実現したいこと
両面印刷された、多数の枚数で構成された文書があるとします。
しかし、いちいちスキャンして裏返してスキャンして、次の紙をセットしてスキャンして裏返してスキャンして次の紙...など、面倒でやってられません。
そこで、この文書を、フィーダーを使って表側をすべて連続スキャンし、表文書の表側(1,3,5,...ページ)がスキャンされたPDFを出力します。
次に、同様に裏側(2,4,6,...ページ)がスキャンされたPDFを出力します。
最後の仕上げとして、Pythonを用いて2つのPDFを、ページが1,2,3,...ページと正しく繋がった1つのPDFにマージする、という手法を思いついたと言う次第です。
これなら、両面同時コピーの機能はないがフィーダーがあって、片面を比較的高速に連続コピーやスキャンできるような家庭用の複合機で、大量の紙で構成される文書を素早くスキャンし、素早くPDFにマージできます。

と、いうわけで早速プログラムで組んだのですが、問題が発生しました。

発生している問題・エラーメッセージ

ソースコードを書き終わり、デバッグを進めていくうちに、私の力だけでは解読不可能なエラーが発生しました。
内容は、次の通りです。

Traceback (most recent call last): File "pdf_even_odd_merger.py", line 45, in <module> odd_pdf = PyPDF2.PdfFileReader(odd_object) File "C:\Users\whpc0\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\PyPDF2\pdf.py", line 1084, in __init__ self.read(stream) File "C:\Users\whpc0\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\PyPDF2\pdf.py", line 1689, in read stream.seek(-1, 2) OSError: [Errno 22] Invalid argument

どうも、Invalid argumentのエラーは、open関数やraw文字列に関連するエラーで、ファイルが見つからないなどのエラーであることは調べて分かりました。
とりあえず何か分かるかなと思って、エラーメッセージに書いてあるPyPDFの中にあるpdf.pyも読んでみましたが、何が何なのやらさっぱり...。

該当のソースコード

ちなみにですが、"even"はeven number:偶数、"odd"はodd number:奇数として書いてあります。

Python

1#! Python3 2# -*- coding: utf-8 -*- 3#pdf_even_odd_merger.py 4 5import PyPDF2, os, sys 6 7pdflist = [] 8evencounter = 0 9oddcounter = 0 10for filename in os.listdir(os.getcwd()): 11 #このファイルが置かれているディレクトリ内のファイルやフォルダを走査 12 13 if filename.endswith(r".pdf"): 14 #ファイルネーム末尾が".pdf"で終わるファイルは、ファイルネームをpdflistに追加する。 15 pdflist.append(filename) 16 17 if filename.lower().startswith(r"even_"): 18 #ファイル名が"even.pdf"で終わる場合(偶数ページのみがスキャンされているファイルの場合)、evencounterを足していく。 19 #大文字の拡張子の可能性も考慮し、lowerメソッドを組んでいる。 20 evencounter += 1 21 22 if filename.lower().startswith(r"odd_"): 23 #ファイル名が"odd.pdf"で終わる場合(奇数ページのみがスキャンされているファイルの場合)、oddcounterを足していく。 24 #大文字の拡張子の可能性も考慮し、lowerメソッドを組んでいる。 25 oddcounter += 1 26 27if evencounter != 1 or oddcounter != 1: 28 print("エラーが発生しました。") 29 print(r"このファイルに置かれているPDFファイルは、'even_~~.pdf'あるいは'odd_~~.pdf'の2つのファイルのみでなくてはなりません。") 30 print("プログラムを終了します。") 31 sys.exit() 32 33pdflist.reverse() 34#pdflistに格納されているファイルが["even_~~.pdf","odd_~~.pdf"]のみであったとして、リスト内をeven,oddからodd,evenの順番にするためにソートする。 35#key=str.lowerで、大文字小文字を区別しないようにしている。 36 37pdfwriter = PyPDF2.PdfFileWriter() 38#結合したPDFファイルを保持するために、オブジェクトを作成しておく。 39 40#奇数ページファイルの読み込みオブジェクト 41odd_file_name = os.getcwd() + "\" + pdflist[0] 42print(odd_file_name) 43odd_object = open(odd_file_name,"rb") #pdflist[0]には、oddのファイルがある。 44odd_pdf = PyPDF2.PdfFileReader(odd_object) 45 46#偶数ページファイルの読み込みオブジェクト 47even_file_name = os.getcwd() + "\" + pdflist[1] 48print(even_file_name) 49even_object = open(even_file_name,"rb") #pdflist[1]には、evenのファイルがある。 50even_pdf = PyPDF2.PdfFileReader(even_object) 51 52#evenとoddが同じ枚数の紙からスキャンしたオブジェクトなら、ページ数が揃うはず。その検証を行う。 53#evenのページ数を取得する。 54odd_num_page = odd_pdf.numPages 55even_num_page = even_pdf.numPages 56superchecker = 0 57#57行目のような状況に遭遇した場合の排他処理そするための識別コード:デフォルトは1で、if文に引っかかった場合に1とする。 58if odd_num_page != even_num_page: 59 print("異なる文書からスキャンた奇数ページファイル、偶数ページファイルを誤って組み合わせないようにするための、安全用プログラムが作動しました。") 60 print("同じ文書からスキャンして得られた偶数ページファイルと奇数ページファイルは、同じページ数になるはずです。") 61 print("しかし、同じ文書からスキャンした文書で、最後のページが真っ白だった場合にそのページをスキャンしなかった場合も考えられます。") 62 print("あなたは、上記の状況に該当しますか。する場合は、1を入力してください。") 63 checknum = int(input()) 64 if chenknum == 1: 65 superchecker = 1 66 #57行目の状況に該当するので、1にしておく。 67 else: 68 print("プログラムを終了します。") 69 sys.exit() 70 71#57行目にある状況を想定して、2つのファイルのページ数で、大きい方を格納する。 72if even_num_page > odd_num_page or even_num_page == odd_num_page: 73 maxpagenum = even_num_page 74else: 75 maxpagenum = odd_num_page 76 77#PDFを、いよいよマージする。 78if superchecker == 0: 79 #まずは、57行目のような状態でない場合(supercheckerが0の場合) 80 for pagenum in range(0,maxpagenum): 81 oddpageobject = odd_pdf.getPage(pagenum) 82 pdfwriter = addPage(oddpageobject) 83 evenpageobject = even_pdf.getPage(pagenum) 84 pdfwriter = addPage(evenpageobject) 85 86 #マージングが終了したので、最後に書き出す 87 pdfoutput = open(r"merged.pdf","wb") #"meiged.pdf"という名前のPDFを作成する 88 pdfwriter.write(pdfoutput) #書き出す 89 pdfoutput.close() #書き込みの終了 90 print("すべての処理が正常に完了しました。") 91 print(r'merged.pdfという名前のファイルが保存されているはずです。') 92 print("プログラムを終了します。") 93 94else: 95 #次は、57行目のような状態である場合(superchecker1の場合) 96 for pagenum in range(0,maxpagenum-1): 97 oddpageobject = odd_pdf.getPage(pagenum) 98 pdfwriter = addPage(oddpageobject) 99 evenpageobject = even_pdf.getPage(pagenum) 100 pdfwriter = addPage(evenpageobject) 101 if pagenum == maxpagenum-2: 102 lastpage = maxpagenum-1 103 lastpageobject = even_pdf.getPage(lastpage) 104 pdfwriter = addPage(lastpageobject) 105 106 #マージングが終了したので、最後に書き出す 107 pdfoutput = open(r"merged.pdf","wb") #"meiged.pdf"という名前のPDFを作成する 108 pdfwriter.write(pdfoutput) #書き出す 109 pdfoutput.close() #書き込みの終了 110 print("すべての処理が正常に完了しました。") 111 print(r"merged.pdfという名前のファイルが保存されているはずです。") 112 print("プログラムを終了します。") 113

試したこと

1.エスケープ文字関連で怪しいところは、すべてraw文字列記述にした
2.相対パスではなく、os.getcwd()を用いて絶対パスを入手する方法へ変更した。

補足情報(FW/ツールのバージョンなど)

Python 3.8
importしたもの:
os
sys
PyPDF2 (バージョンは1.26.0)

以上です。皆様のご回答、お待ちしております。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

1T2R3M4

2020/09/12 00:58

open(odd_file_name,"wb") wbで間違いないですか。
SatelliteStar

2020/09/12 05:16

ご指摘ありがとうございます。 rbに直したのですが、どうやらこれだけが原因ではないようです。 同じ箇所で同じエラーが発生しました。
guest

回答1

0

ベストアンサー

  • 質問へのコメントでも指摘されているように、odd_objecteven_objectのモードは`"rb"にしないと読み込めません。
  • forループでmaxpagenumという変数が突然出てくる。(偶数ページと奇数ページを比較して、より長くなる可能性がある)odd_pdf.numPagesに代える。
  • pdfwriterに書き込みを行なうaddPage()は代入ではなくメソッド呼び出しなので、それに見合う形にする。
  • 偶数ページのページ数は、奇数ページのページ数と同じか、1ページ少なくなるか(1,3,5ページと2,4ページなど)のどちらかである。1ページ少なくなる場合、getPage()しないようにeven_pdf.numPagesを使ったif文で処理する。

残った問題点は、

  • カレントディレクトリに存在するPDFファイルが2個以外のときの処理。たとえば、1個だけある場合や、3個以上ある場合にどうするか。現在はos.listdir()の末尾2個だけが処理される。

globを使って「even_.pdf」と「odd_.pdf」をそれぞれ別個にリストに読み込み、それぞれのリストの要素が1のときだけ続く処理を行なうように変更した。これで、他のPDFファイル(以前作成したmerged.pdfなど)が存在しても動作する。

Python

1import glob 2import sys 3import PyPDF2 4 5even_list = glob.glob("even_*.pdf") 6odd_list = glob.glob("odd_*.pdf") 7if len(even_list) != 1 or len(odd_list) != 1: 8 print("1組のPDFファイル(even_*.pdf, odd_*pdf)になっていません") 9 sys.exit(1) 10 11odd_file_name = odd_list[0] 12print("odd file: " + odd_file_name) 13odd_object = open(odd_file_name, "rb") 14odd_pdf = PyPDF2.PdfFileReader(odd_object) 15 16even_file_name = even_list[0] 17print("even file: " + even_file_name) 18even_object = open(even_file_name, "rb") 19even_pdf = PyPDF2.PdfFileReader(even_object) 20 21pdfwriter = PyPDF2.PdfFileWriter() 22for pagenum in range(0, odd_pdf.numPages): 23 oddpageobject = odd_pdf.getPage(pagenum) 24 pdfwriter.addPage(oddpageobject) 25 if pagenum < even_pdf.numPages: 26 evenpageobject = even_pdf.getPage(pagenum) 27 pdfwriter.addPage(evenpageobject) 28 29pdfoutput = open(r"merged.pdf", "wb") 30pdfwriter.write(pdfoutput) 31pdfoutput.close() 32odd_object.close() 33even_object.close() 34print("すべての処理が正常に完了しました。")

追加分:

  • PDFファイルの内容により、PyPDF2.PdfFileReader()で読み込むさいに「OSError: [Errno 22] Invalid argument」になるものがある。PyPDF2公式のGitHubの以下のページで議論されている(未解決)。

A certain PDF File triggers OSError: [Errno 22] Invalid argument · Issue #530 · mstamy2/PyPDF2

先頭の発言の「This file」のリンクからダウンロードしたNTB - LOI.pdf(印刷された書類をスキャンしたっぽい内容)をPyPDF2.PdfFileReader()で読み込むと、確かに「OSError(以下略)」が発生する。質問者がスキャンで作成したPDFファイルも、これと同じ問題が起きている可能性が高い。なお、こちらで検証に使っていたPDFファイルは、印刷した文書をスキャンしたものではなく、Wordで作成した文書を直接PC上でPDF化したものだった。

とりあえずの対策として、上記のスレッドでは、GhostScript(gs)を使ってPDFファイルを書き換えるためのコマンドラインが提案されている。

gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/printer -dNOPAUSE -dQUIET -dBATCH -sOutputFile="出力ファイル名" "入力ファイル名"

あいにくWindowsにGhostScriptを入れていなかったので、仮想Linuxマシンで上記のコマンドラインで変換を行なったところ、

terminal

1$ gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/printer -dNOPAUSE -dQUIET -dBATCH -sOutputFile="output.pdf" "input.pdf" 2 **** Error: An error occurred while reading an XREF table. 3 **** The file has been damaged. This may have been caused 4 **** by a problem while converting or transfering the file. 5 **** Ghostscript will attempt to recover the data. 6 **** However, the output may be incorrect.

とエラーを吐いたものの、変換後のPDFファイルは問題なくPyPDF2.PdfFileReader()で読み込むことができた。なお、PDFビューアーアプリでは、どちらのPDFファイルも表示できている。

ということで、

  1. 現在使っているPDFファイルではなく、Wordなどの文書を直接PDF化したものを用意して、コードの動作確認を行なう。
  2. 最終目的であるスキャンされたPDFファイルのマージを行なうために、GhostScriptのダウンロードとインストールを行なう。
  3. gsコマンドが利用可能になったら、上記のコマンドラインで変換を試み、生成されたPDFファイルを使って(動作確認済みの)コードでマージを行なう。

という手順が必要でしょう。

投稿2020/09/12 02:44

編集2020/09/12 16:41
Daregada

総合スコア11990

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SatelliteStar

2020/09/12 05:38 編集

ご指摘ありがとうございます。rbに修正済みですが、依然同じ場所で同じエラーが発生してしまいます。 maxpagenumが、まさにご指摘にあるとおりの、カレントディレクトリに存在する2つのPDFファイルのページ数が違う場合のときの処理に付いての変数です。他にも、カレントディレクトリにあるPDFが2つ以上である場合に強制的に処理を中止する部分も、本来のコードには記述してありますが、その部分はエラーとは関係ないであろうことと、ソースコードの煩雑さを防ぐために、その処理に関係する部分を削って投稿したつもりでしたが、消し忘れていたようです。この部分も修正しておきました。 最後に、"pdfwriterに書き込みを行なうaddPage()は代入ではなくメソッド呼び出しなので、それに見合う形にする。"の内容が理解できませんでした。お手数おかけしますが、もう少々詳細にご教示いただければ幸いです。
Daregada

2020/09/12 07:19

「本来のコード」で動かないと言われても、それを見ているのは「あなただけ」なのでどうしようもない。 まずは、以下のようにして検証を行なってください。 (1) 検証用のフォルダーを新規作成し、PDFファイルを2つだけ格納する (2) 回答のスクリプトを(内容をいっさい変更せず)に保存したファイルを(1)のフォルダーに作成する。 (3) (1)のフォルダーをカレントディレクトリにした端末(コマンドプロンプトなど)で、(2)のスクリプトを実行する (4) エラーが出る場合はエラーの内容、出なかった場合はマージされたPDFの内容を確認する。 これでエラーが出ないのなら(実際こちらでは出ないのですが)、質問に含まれていない部分の処理に問題があります。それは、こちらでは答えようがありません。
Daregada

2020/09/12 07:28

> 最後に、"pdfwriterに書き込みを行なうaddPage()は代入ではなくメソッド呼び出しなので、それに見合う形にする。"の内容が理解できませんでした。お手数おかけしますが、もう少々詳細にご教示いただければ幸いです。 「pdfwriter = addPage(oddpageobject)」ではエラーが出て動きません。 addPage()は、PyPDF2.PdfFileWriter()で得られたオブジェクト(今回のコードではpdfwriter)のメソッドとして呼び出さないと(「.」で結合して直後に書かないと)使えないということです。
SatelliteStar

2020/09/12 14:00

ご回答にあるコードで走らせましたが、同じエラーが発生してしまいます 質問に乗せているコードを、省略版ではない本来のコードに更新しておきました。 よろしくお願いします。
Daregada

2020/09/12 15:03

「本来のコード」ですが、エラーが多数あるのですけど、ちゃんと動いてます? ・「import os」を使っても、listdir()は「os.listdir()」と書かないと動かない。 ・filename.lower().startswithwith("even_")って、withが2つ書いてある。 ・odd_pdf = PyPDF2.PdfFileReader()って、()内にodd_objectを書かないと動かない。 ・even_pdf = PyPDF2.PdfFileReader()って()内にeven_objectを書かないと動かない。 ・if文にbreakは使えない。
Daregada

2020/09/12 16:19 編集

どうやら、ある種のPDFファイルは、PyPDF2.PdfFileReader()で読み込む際に、「OSError: [Errno 22] Invalid argument」を出すようです。回答に補足します。
SatelliteStar

2020/09/13 00:09

めちゃくちゃご丁寧に、ありがとうございます! 誤って、デバッグ前のスクリプトを投稿してしまっておりました。すみません。 新しいスクリプトに変えてありますので、こちらもご確認いただけますと幸いです。 PDFによってOS Errorを起こすのですね。大変興味深い情報を、ありがとうございます。 ご提示いただいた検証をしてみようと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問