ここで言う「私でもできる!」は筆者でもできるという意味であり、これを読んだ全ての人ができるという意味ではないので注意
結論
背景
discordでファイルを共有するとプレビュー(スマホ版だとリンク先で確認できる)ができず、送信されたファイルをクリックすると強制的にダウンロードされてしまうのでbotにプレビューっぽいものを実装してPCにダウンロードせずに中身を確認をする。
要件
-
利用者目線
- 色々な発火(コマンドの起動)方法が欲しい
- pdfを画像に変換できる
- チャットを汚したくないのでスレッドを作ってそこに送りたい
- ついでに文字起こしもしたい
-
開発者目線
- 仮想環境で運用
- poetryを使いたい
実装
コマンドの発火まで
まず実行されたのがスレッドなのかそれ以外なのかを判断する。
try excpet
はあまりよろしくないが、とりあえず気合いの実装
スレッドがあるかどうかを判断してる
async def pdf(ctx, *args):
try:
checkId = ctx.message.channel.parent.id
threadId = ctx.message.channel.id
thFlug = 1
except:
thFlug = 0
スレッドを作って変換関数の実行
スレッドがあるかどうかを判断して、無いなら日付
+conversion image
でスレッドを作る。
あるならスレッドの中にスレッドは作れないのでそのままそこに送る。
完全に別スレッドに画像ファイルを隔離したいぜ!みたいな人は上のスレッドの有無は要らなくてただ単にスレッドをチャット内に生成すればOK
if thFlug == 0:
thread = await ctx.message.create_thread(
name=(f"{datetime.datetime.now().strftime('%Y%m%d%H%M')}conversion image"),
auto_archive_duration=10080,
slowmode_delay=0
)
elif thFlug == 1:
thread = client.get_channel(threadId)
# 以下の処理でmessageはmessageオブジェクトを指しているので
message = ctx.message
for attachment in message.attachments:
# pdf以外を除外
if attachment.content_type != "application/pdf":
continue
# 処理関数の実行
await conv_pdf(attachment, message, thread)
pdfを画像に変換する関数
pdfを画像に変換して送信する部分[1]は既に実装されたものがあったのでそれを関数化
async def conv_pdf(attachment, message, thread):
# ファイルの保存
await attachment.save(f"{message.id}.pdf")
images = pdf2image.convert_from_path(f"./{message.id}.pdf")
for index, image in enumerate(images):
# 画像変換
image.save(f"{message.id}-{str(index+1)}.jpg")
# 画像送信
await thread.send(file=discord.File(f"{message.id}-{str(index+1)}.jpg"))
# 画像削除
os.remove(f"{message.id}-{str(index+1)}.jpg")
# pdfの削除
os.remove(f"{message.id}.pdf")
文字起こしの関数
関数以外は大体同じなので関数だけ
Nitro以外は2000文字まで送信できるので1900文字で切って繰り返し送信するように実装
読み取り処理の部分はこのサイト[2]を参考に作成
async def conv_text(attachment, message, thread):
tools = pyocr.get_available_tools()
if len(tools) == 0:
await ctx.reply("OCRが起動してません。管理者に問い合わせてください")
tool = tools[0]
await attachment.save(f"{message.id}.pdf")
images = pdf2image.convert_from_path(f"./{message.id}.pdf")
#lang = 'eng'
lang = 'jpn'
text = ""
# 画像オブジェクトからテキストに
for image in images:
tmp = ""
tmp = tool.image_to_string(
image,
lang=lang,
builder=pyocr.builders.TextBuilder()
)
text = text + tmp
for i in range(-(len(text)//-1900)):
await thread.send(text[:1900])
text = text[1900:]
残りのところ
ファイルが添付されたメッセージのリクエスト形式が
- 返信
ex)添付されたメッセージに返信して!pdf
- メンション
ex)ファイルを添付して!pdf
- discordのURL添付
ex)discordのURLを添付して!pdf
に分かれるのでそれぞれの場合に応じて
- 返信
返信先のメッセージ取得
添付ファイルの取得 - discordのURL添付
URLリンク先のメッセージの取得
添付ファイルの取得 - メンション
添付ファイルの取得
みたいな処理が必要になる。
これを気合いで実装する
Dockerfileを作る
pythonが動く環境とpoetryとpopplerやocrのインストール
FROM python:3.11
# 定義するもの
ENV PATH="/root/.local/bin:$PATH"
ENV POETRY_HOME=/opt/poetry
WORKDIR /RePdf
#実行するもの
RUN wget -O - https://install.python-poetry.org | python3 - \
&& cd /usr/local/bin \
&& ln -s /opt/poetry/bin/poetry \
&& poetry config virtualenvs.create false \
&& apt update \
&& apt install poppler-utils poppler-data -y \
&& apt install tesseract-ocr libtesseract-dev poppler-utils tesseract-ocr-jpn -y \
compose.yamlを作る
ファイルは保存して変換後に消すので永続的に保存する必要がない。なので永続化する必要なし
services:
python:
build: .
volumes:
- .:/RePdf
tty: true
実行
sudo docker compose build
docker compose up -d
コンテナ内に入ったら.env
を作成して
DISCORD_TOKEN=TOKEN
を作成する。
.env
ファイルがあるディレクトリで
poetry shell
python __init__.py
で動く
困ったこと
-
.env
ファイル作っただけだとうまくenvファイルの参照ができなくて困った
dotenv_path = join(dirname(__name__), '.env')
load_dotenv(verbose=True, dotenv_path=dotenv_path)
を追加してenvファイルを読み込む必要があった
- 急にpdfから画像への変換ができなくなった
同じディレクトリでもパスを書かなきゃいけないらしく
images = pdf2image.convert_from_path(f"{message.id}.pdf")
ここの
"{message.id}.pdf"
を"./{message.id}.pdf"
にする必要がある
参考文献
[1]Discordに送信されたPDFファイルを画像ファイルに変換して送信
[2]Pythonでpdfを画像として認識しテキストを抽出を試してみる(pyocr)