Es überwacht den angegebenen Dateipfad und benennt die PDF-Datei automatisch in den Titel des Buches um, wenn Sie dort eine PDF-Datei ablegen. :octocat:book_maker
macOS Catalina
$ brew install poppler
$ brew install tesseract
$ brew install tesseract-lang
$ python3 src/watch.py input_path [output_path] [*extensions]
Ich beeilte mich, eine Schneidemaschine und einen Scanner zu kaufen, weil ich viele Bücher im Haus meiner Eltern verdauen wollte. Ich habe jedoch oft gehört, dass Selbstversorgung problematisch ist, deshalb wollte ich einen gewissen Grad an Automatisierung erreichen, deshalb habe ich dieses Programm erstellt.
Ich habe es im folgenden Ablauf zusammengebaut.
Ich habe eine Bibliothek namens watchdog verwendet, um das Verzeichnis ständig zu überwachen. Die folgenden Dokumente und Artikel waren sehr hilfreich für die detaillierte Verwendung von "Watchdog". Vielen Dank.
--watchdog offizielles Dokument
Um "Watchdog" zu verwenden, benötigen Sie "Handler" und "Observer".
Handler
beschreibt, was zu tun ist und wie jedes Ereignis zu behandeln ist (Erstellen / Löschen / Verschieben / Ändern).
Dieses Mal wird nur die Funktion "on_created" definiert, die zum Zeitpunkt der Erstellung das Ereignis ist.
Diese "on_created" -Methode überschreibt die Methode in der "FileSystemEventHandler" -Klasse in "watchdog.event".
src/handler/handler.py
from watchdog.events import PatternMatchingEventHandler
class Handler(PatternMatchingEventHandler):
def __init__(self, input_path, output_path, patterns=None):
if patterns is None:
patterns = ['*.pdf']
super(Handler, self).__init__(patterns=patterns,
ignore_directories=True,
case_sensitive=False)
def on_created(self, event):
#Etwas tun
Es definiert eine Handler-Klasse und erbt "PatternMatchingEventHandler", wodurch der Mustervergleich ermöglicht wird. Auf diese Weise können Sie die Dateitypen einschränken, die von Ereignissen erkannt werden. Es gibt auch einen RegexMatchingEventHandler, mit dem Sie Muster für reguläre Ausdrücke verwenden können. Dieses Mal wollte ich eine Verarbeitung durchführen, die nur auf PDF beschränkt ist, also habe ich "pattern = ['* .pdf']" gesetzt. Ich habe "ignore_directories = True" gesetzt, um das Verzeichnis zu ignorieren, und ich wollte sowohl "* .pdf" als auch "* .PDF" erkennen können, also habe ich "case_sensitive = False" gesetzt.
Bereiten Sie als Nächstes "Observer" vor, der für die Überwachung des Handlers verantwortlich ist.
src/watch.py
from watchdog.observers import Observer
from src.handler.handler import Handler
def watch(input_path, output_path, extensions):
print([f'*.{extension}' for extension in extensions], flush=True)
event_handler = Handler(input_path=input_path,
output_path=output_path,
patterns=[f'*.{extension}' for extension in extensions])
observer = Observer()
observer.schedule(event_handler, input_path, recursive=False)
observer.start()
print('--Start Observer--', flush=True)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.unschedule_all()
observer.stop()
print('--End Observer--', flush=True)
observer.join()
Beschreiben Sie im erstellten Observer-Objekt, ob das Handler-Objekt, das überwachte Verzeichnis und die Unterverzeichnisse rekursiv überwacht und erstellt werden sollen.
Starten Sie die Überwachung mit Observer.start ()
und lassen Sie sie mit der Anweisung while
undtime.sleep (1)
laufen, um den Vorgang fortzusetzen.
Wenn "Strg + C" gedrückt wird, beendet "Observer.unschedule_all ()" die gesamte Überwachung, löst den Ereignishandler und "Observer.stop ()" weist den Thread an, anzuhalten.
Verwenden Sie abschließend Observer.join ()
, um zu warten, bis der Thread beendet ist.
Ich habe auf diesen Blog verwiesen. Vielen Dank.
Versuchen Sie beim Abrufen des ISBN-Codes, ihn aus dem Barcode abzurufen.
Diejenigen, die ich verwendet habe, um die Informationen aus dem PDF zu erhalten, sind "pdfinfo", "pdfimages" und "zbarimg".
pdfinfo
gibt die Gesamtzahl der Seiten im PDF an.
pdfimages
soll nur die erste und letzte Seite jpeg machen, basierend auf den Gesamtseiten, die von pdfinfo
erhalten wurden.
zbarimg
wurde verwendet, um den ISBN-Code aus dem von pdfimages
generierten JPEG abzurufen.
getISBN.sh
#!/bin/bash
# Number of pages to check in PDF
PAGE_COUNT=1
# File path
FILE_PATH="$1"
# If the file extension is not pdf
shopt -s nocasematch
if [[ ! $1 =~ .+(\.pdf)$ ]]; then
exit 1
fi
shopt -u nocasematch
# Delete all .image* generated by pdfimages
rm -f .image*
# Get total count of PDF pages
pages=$(pdfinfo "$FILE_PATH" | grep -E "^Pages" | sed -E "s/^Pages: +//") &&
# Generate JPEG from PDF
pdfimages -j -l "$PAGE_COUNT" "$FILE_PATH" .image_h &&
pdfimages -j -f $((pages - PAGE_COUNT)) "$FILE_PATH" .image_t &&
# Grep ISBN
isbnTitle="$(zbarimg -q .image* | sort | uniq | grep -E '^EAN-13:978' | sed -E 's/^EAN-13://' | sed 's/-//')" &&
# If the ISBN was found, echo the ISBN
[ "$isbnTitle" != "" ] &&
echo "$isbnTitle" && rm -f .image* && exit 0 ||
# Else, exit with error code
rm -f .image* && exit 1
Wenn der ISBN-Code erhalten wird, wird schließlich "echo" $ isbnTitle "als Standardausgabe auf der Python-Seite empfangen.
Auch das&&
Oder||
Ich habe die Bedeutung nicht gut verstanden, aber der folgende Artikel war hilfreich.
Vielen Dank.
Um vom Barcode pdf2image zu gelangen, um das PDF abzubilden, und um vom Barcode pyzbar zu gelangen. pyzbar) wurde verwendet.
Generieren Sie mit pdf2image
Bilder von jpeg
für 2 Seiten, die von der letzten Seite zählen, rufen Siedecode ()
with pyzbar
für diese Bilder auf und verwenden Sie das reguläre Ausdrucksmuster des ISBN-Codes ( Wenn es eine Zeichenfolge gibt, die mit ^ 978
) übereinstimmt, wird sie zurückgegeben.
Ich habe "TemporaryDirectory ()" verwendet, weil ich wollte, dass das Verzeichnis die generierten Bilder temporär macht.
src/isbn_from_pdf.py
import re
import sys
import tempfile
import subprocess
from pyzbar.pyzbar import decode
from pdf2image import convert_from_path
input_path = input_path
texts = []
cmd = f'echo $(pdfinfo "{input_path}" | grep -E "^Pages" | sed -E "s/^Pages: +//")'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
total_page_count = int(result.stdout.strip())
with tempfile.TemporaryDirectory() as temp_path:
last_pages = convert_from_path(input_path,
first_page=total_page_count - PAGE_COUNT,
output_folder=temp_path,
fmt='jpeg')
# extract ISBN from using barcode
for page in last_pages:
decoded_data = decode(page)
for data in decoded_data:
if re.match('978', data[0].decode('utf-8', 'ignore')):
return data[0].decode('utf-8', 'ignore').replace('-', '')
Eine andere Möglichkeit besteht darin, den ISBN-Code von der letzten Seite des Buches zu extrahieren, die Informationen wie den Herausgeber und die Ausgabe des Buches enthält.
Ich habe pyocr verwendet, um die Zeichenfolge aus dem Bild zu extrahieren.
Um pyocr
verwenden zu können, benötigen Sie das OCR-Tool. Daher müssen Sie Googles tesseract installieren.
src/isbn_from_pdf.py
import re
import sys
import pyocr
import tempfile
import subprocess
import pyocr.builders
from pdf2image import convert_from_path
input_path = input_path
texts = []
cmd = f'echo $(pdfinfo "{input_path}" | grep -E "^Pages" | sed -E "s/^Pages: +//")'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
total_page_count = int(result.stdout.strip())
with tempfile.TemporaryDirectory() as temp_path:
last_pages = convert_from_path(input_path,
first_page=total_page_count - PAGE_COUNT,
output_folder=temp_path,
fmt='jpeg')
tools = pyocr.get_available_tools()
if len(tools) == 0:
print('[ERROR] No OCR tool found.', flush=True)
sys.exit()
# convert image to string and extract ISBN
tool = tools[0]
lang = 'jpn'
for page in last_pages:
text = tool.image_to_string(
page,
lang=lang,
builder=pyocr.builders.TextBuilder(tesseract_layout=3)
)
texts.append(text)
for text in texts:
if re.search(r'ISBN978-[0-4]-[0-9]{4}-[0-9]{4}-[0-9]', text):
return re.findall(r'978-[0-4]-[0-9]{4}-[0-9]{4}-[0-9]', text).pop().replace('-', '')
Um die Informationen des Buches zu erhalten, habe ich zwei der Google Books-APIs und openBD verwendet. tat.
Beide sind im JSON-Format erhältlich. Da sie jedoch unterschiedliche Formen haben, wollte ich Code schreiben, der so häufig wie möglich ist. Deshalb habe ich eine Bibliothek namens Box verwendet. Ich tat.
Box
soll es Ihnen ermöglichen, das zu bekommen, was Sie normalerweise mit dict.get ('key')
unddict ['key']
with dict.key.another_key
erhalten würden. ..
Sie können auch "dict ['key']" verwenden.
Weitere Funktionen sind die Möglichkeit, dass "key" einen Kamelfall ("camelCase") in eine Python-Namenskonvention für Schlangenfälle ("snake_case") konvertiert und "key" ein Raum wie "persönliche Gedanken" ist. Es gibt auch eine praktische Funktion, mit der Sie wie "dict.personal_ Thoughts" darauf zugreifen können, wenn es eine gibt.
Unten ist der Code von openBD
.
src/bookinfo_from_isbn.py
import re
import json
import requests
from box import Box
OPENBD_API_URL = 'https://api.openbd.jp/v1/get?isbn={}'
HEADERS = {"content-type": "application/json"}
class BookInfo:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
return f'<{self.__class__.__name__}>{json.dumps(self.__dict__, indent=4, ensure_ascii=False)}'
def _format_title(title):
#Ersetzen Sie Klammern in voller Breite und Leerzeichen in voller Breite durch Leerzeichen in halber Breite
title = re.sub('[() ]', ' ', title).rstrip()
#Ersetzen Sie ein oder mehrere Felder mit halber Breite durch eines
return re.sub(' +', ' ', title)
def _format_author(author):
#Löschen Sie die Zeichenfolge nach / geschrieben
return re.sub('/.+', '', author)
def book_info_from_openbd(isbn):
res = requests.get(OPENBD_API_URL.format(isbn), headers=HEADERS)
if res.status_code == 200:
openbd_res = Box(res.json()[0], camel_killer_box=True, default_box=True, default_box_attr='')
if openbd_res is not None:
open_bd_summary = openbd_res.summary
title = _format_title(open_bd_summary.title)
author = _format_author(open_bd_summary.author)
return BookInfo(title=title, author=author)
else:
print(f'[WARNING] openBD status code was {res.status_code}', flush=True)
Da der Titel des erworbenen Buches und die Informationen des Autors mit voller und halber Breite gemischt werden, haben wir eine Funktion vorbereitet, um jedes zu korrigieren. (_format_title
・ _ format_author
)
Ich habe es noch nicht geschnitten und ausprobiert, daher müssen diese Funktionen angepasst werden.
In "Box", "camel_killer_box = True", das einen Kamelfall in einen Schlangenfall umwandelt, und "default_box = True" und "default_box_attr =", selbst wenn kein Wert vorhanden ist.
Stellen Sie beim Starten zunächst sicher, dass Sie einen Ordner erstellen, der nach dem Umbenennen der PDF-Datei verschoben werden soll.
src/handler/handler.py
import os
import datetime
from watchdog.events import PatternMatchingEventHandler
class Handler(PatternMatchingEventHandler):
def __init__(self, input_path, output_path, patterns=None):
if patterns is None:
patterns = ['*.pdf']
super(Handler, self).__init__(patterns=patterns,
ignore_directories=True,
case_sensitive=False)
self.input_path = input_path
# If the output_path is equal to input_path, then make a directory named with current time
if input_path == output_path:
self.output_path = os.path.join(self.input_path, datetime.datetime.now().strftime('%Y%m%d_%H%M%S'))
else:
self.output_path = output_path
os.makedirs(self.output_path, exist_ok=True)
# Create tmp directory inside of output directory
self.tmp_path = os.path.join(self.output_path, 'tmp')
os.makedirs(self.tmp_path, exist_ok=True)
Wenn der Prozess gestartet wird, wird ein Zielordner erstellt, der mit dem heutigen Datum oder einem angegebenen Zielordner formatiert ist. Erstellen Sie dann einen tmp-Ordner im Ausgabezielordner, der bei einem Fehler abgelegt werden soll (wenn dasselbe PDF-Buch vorhanden ist, wenn keine ISBN gefunden wird, wenn keine Buchinformationen vorhanden sind). ..
src/handler/handler.py
def __del__(self):
# Delete the tmp directory, when the directory is empty
tmp_files_len = len(os.listdir(self.tmp_path))
if tmp_files_len == 0:
os.rmdir(self.tmp_path)
# Delete the output directory, when the directory is empty
output_files_len = len(os.listdir(self.output_path))
if output_files_len == 0:
os.rmdir(self.output_path)
Wenn der Vorgang abgeschlossen ist, beschreiben Sie die Methode del, damit eine Datei im Ausgabezielordner / tmp-Ordner verbleibt und gelöscht wird, wenn sie nicht vorhanden ist.
src/handler/handler.py
import shutil
import subprocess
from src.isbn_from_pdf import get_isbn_from_pdf, NoSuchISBNException
from src.bookinfo_from_isbn import book_info_from_google, book_info_from_openbd, NoSuchBookInfoException
def on_created(self, event):
print('!Create Event!', flush=True)
shell_path = os.path.join(os.path.dirname(__file__), '../../getISBN.sh')
event_src_path = event.src_path
cmd = f'{shell_path} {event_src_path}'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
try:
if result.returncode == 0:
# Retrieve ISBN from shell
isbn = result.stdout.strip()
print(f'ISBN from Shell -> {isbn}', flush=True)
self._book_info_from_each_api(isbn, event_src_path)
else:
# Get ISBN from pdf barcode or text
isbn = get_isbn_from_pdf(event_src_path)
print(f'ISBN from Python -> {isbn}', flush=True)
self._book_info_from_each_api(isbn, event_src_path)
except (NoSuchISBNException, NoSuchBookInfoException) as e:
print(e.args[0], flush=True)
shutil.move(event_src_path, self.tmp_path)
print(f'Move {os.path.basename(event_src_path)} to {self.tmp_path}', flush=True)
Die Methode "on_created" beschreibt den Gesamtablauf im Workflow.
Führen Sie beim Ausführen der Shell die Shell mit "subprocess.run ()" aus, um die Standardausgabe zu erhalten, erhalten Sie den Shell-Status von "result.returncode" und empfangen Sie die Standardausgabe mit "result.stdout". Kann gemacht werden
Außerdem wird beim Abrufen von Buchinformationen aus dem ISBN-Code eine spezielle Ausnahme ausgelöst.
Vielen Dank, dass Sie so weit gelesen haben. Ich hatte Probleme mit dem Ort, an dem der Befehl und der Variablenname / Funktionsname gestartet werden sollten, aber ich habe es geschafft, ihn zur Mindestform zu machen. Derzeit wird nur PDF unterstützt, aber ich möchte epub unterstützen können. Ich möchte es auch unter Windows tun können.
Wenn es Tippfehler oder Fehler gibt, ist dies der richtige Weg! Bitte lassen Sie mich wissen, wenn Sie welche haben. Vielen Dank.
Recommended Posts