Ziel: Wir wollen beliebige Dateien auf das System schmeißen und diese im Volltext durchsuchbar machen sowie Vorschaubilder generieren.
Carrierwave: File Uploads für Rails und einfaches Processing (primär Bildverarbeitung) Docsplit: Dokumente auseinandernehmen, Texte, Bilder und einzelne Seiten extrahieren Carrierwave-Backgrounder: Carrierwave Processing in Background-Jobs auslagern
Docsplit ist eigentlich nur ein schmales Ruby-Layer zu folgenden Tools:
- LibreOffice - Div. Formate in PDF umwandeln
- Graphicsmagick - Bilder erzeugen
- Poppler - PDFs erzeugen
- Ghostscript - PDFs verarbeiten
- tesseract - OCR
- pdftk - PDFs aufteilen/zusammenführen
Carrierwave bietet Bildberarbeitung via Imagemagick/Graphicsmagick durch folgende Gems:
- RMagick
- MiniMagick
Sobald wir ein PDF haben, ist alles gut!
Docsplit: Wandelt um in ein PDF und extrahiert Text Carrierwave: Generiert Vorschaubilder aus dem PDF
Worst Case: MS Word-Dokument mit eingebetteten Bildern, die Text enthalten.
foo.doc
|
v
+-------------+
| LibreOffice |
+-------------+
|
v
------------- foo.pdf
| |
| v
| +-------------+
| | GhostScript |-----> foo.txt
| +-------------+
| |
| v
| foo-*.png
| |
| v
| +-------------+
| | tesseract |-----> more-foo.txt
| +-------------+
|
| +----------------+
-------> | GraphicsMagick |---> foo-preview.png
+----------------+
Das alles braucht ne Menge Zeit. Und man hat immer so LibreOffice Prozesse herumhängen, die ne Menge RAM fressen. Auch tesseract ist nicht gerade schlank und braucht einiges an Rechenpower.
Größere Dokumente brauchen durchaus mal 30 Sekunden oder mehr.
Also: Geht nur im Background.
Carrierwave-Backgrounder klinkt sich in Carrierwave ein um das Processing auszulagern. Bringt auch Features mit wie automatisches Update von "processing"-Flags am Model, so das man immer weiß, ob die Verarbeitung noch läuft.
class AttachmentUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
include CarrierWave::MimeTypes
include CarrierWave::Backgrounder::Delay
process :set_content_type
process :extract_text
version :thumb do
process resample: [300, 200, 282] # A4 aspect ratio
end
version :preview do
process resample: [300, 730, 1032] # A4 aspect ratio
end
def resample(dpi, width, height)
unless imageable?
begin
Docsplit.extract_pdf(current_path, output: store_dir)
pdf_path = store_dir.join "#{File.basename(current_path, File.extname(current_path))}.pdf"
raise Docsplit::ExtractionFailed, 'Conversion to PDF failed' unless File.exist? pdf_path
File.rename(pdf_path, current_path)
rescue Docsplit::ExtractionFailed => e
attachment_logger.info "Attachment (id=#{model.id}) PDF conversion: #{e.class.name} #{e.message}"
# Remove this version file to disable the version
File.unlink current_path
return
end
end
manipulate! do |img|
img.format('png') do |cmd|
cmd.density(dpi)
cmd.quality(100)
end
img.resize "#{width}x#{height}"
img
end
end
def extract_text
out = File.join store_dir, 'tmp'
FileUtils.mkdir_p out
begin
Docsplit.extract_text current_path, language: 'deu', clean: false, output: out
text_file = Dir.glob(File.join(out, '*.txt')).first
text = File.read text_file
model.raw_text = text
rescue Docsplit::ExtractionFailed => e
attachment_logger.info "Attachment (id=#{model.id}) text extraction: #{e.class.name} #{e.message}"
ensure
FileUtils.rm_r out
end
end
end