class HexaPDF::CLI::Files
See: HexaPDF::Type::EmbeddedFile
Lists or extracts embedded files from a PDF file or attaches them.
def attach_files(doc)
def attach_files(doc) @attach_files.each {|file, desc| doc.files.add(file, description: desc) } end
def each_file(doc, &block) # :yields: obj, index
Iterates over all embedded files.
def each_file(doc, &block) # :yields: obj, index doc.files.each(search: @search).select(&:embedded_file?).each_with_index(&block) end
def execute(pdf, output = nil) #:nodoc:
def execute(pdf, output = nil) #:nodoc: if @indices.empty? && !@attach_files.empty? raise Error, "Missing output file" unless output maybe_raise_on_existing_file(output) end with_document(pdf, password: @password, out_file: output) do |doc| if @indices.empty? && @attach_files.empty? list_files(doc) elsif !@indices.empty? && !@attach_files.empty? raise Error, "Use either --attach or --extract but not both" elsif !@attach_files.empty? attach_files(doc) else extract_files(doc) end end end
def extract_files(doc)
def extract_files(doc) each_file(doc) do |obj, index| next unless @indices.include?(index + 1) || @indices.include?(0) maybe_raise_on_existing_file(obj.path) puts "Extracting #{obj.path}..." if command_parser.verbosity_info? File.open(obj.path, 'wb') do |file| fiber = obj.embedded_file_stream.stream_decoder while fiber.alive? && (data = fiber.resume) file << data end end end end
def initialize #:nodoc:
def initialize #:nodoc: super('files', takes_commands: false) short_desc("List and extract embedded files from a PDF or attach files") long_desc(<<~EOF) If neither the option --attach nor the option --extract is given, the available files are listed with their names and indices. The --extract option can then be used to extract one or more files. Or the --attach option can be used to attach files to the PDF. EOF options.on("--attach FILE", "-a FILE", String, "The file that should be attached. Can be used multiple times.") do |file| @attach_files << [file, nil] end options.on("--description DESC", "-d DESC", String, "Adds a description to the last file to be attached.") do |description| @attach_files[-1][1] = description end options.on("--extract [a,b,c,...]", "-e [a,b,c,...]", Array, "The indices of the files that should be extracted. Use 0 or no argument to " \ "extract all files.") do |indices| @indices = (indices ? indices.map(&:to_i) : [0]) end options.on("--[no-]search", "-s", "Search the whole PDF instead of the " \ "standard locations (default: false)") do |search| @search = search end options.on("--password PASSWORD", "-p", String, "The password for decryption. Use - for reading from standard input.") do |pwd| @password = (pwd == '-' ? read_password : pwd) end @attach_files = [] @indices = [] @password = nil @search = false end
def list_files(doc)
def list_files(doc) each_file(doc) do |obj, index| $stdout.write(sprintf("%4i: %s", index + 1, obj.path)) ef_stream = obj.embedded_file_stream if (params = ef_stream[:Params]) && !params.empty? data = [] data << "size: #{params[:Size]}" if params.key?(:Size) data << "md5: #{params[:CheckSum].unpack1('H*')}" if params.key?(:CheckSum) data << "ctime: #{params[:CreationDate]}" if params.key?(:CreationDate) data << "mtime: #{params[:ModDate]}" if params.key?(:ModDate) $stdout.write(" (#{data.join(', ')})") end $stdout.puts $stdout.puts(" #{obj[:Desc]}") if obj[:Desc] && !obj[:Desc].empty? end end