Skip to content

Instantly share code, notes, and snippets.

@tbk303
Last active August 9, 2018 02:48
Show Gist options
  • Save tbk303/e1ceaa3d17feded9807f to your computer and use it in GitHub Desktop.
Save tbk303/e1ceaa3d17feded9807f to your computer and use it in GitHub Desktop.
Carrierwave/Docsplit

Carrierwave + Docsplit

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

Tools

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

Architektur

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.

Backgrounder

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.

Code

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment