~mro/Photos2Atom

9c31e74f84b46b551ffa5b763ff2e9f62ff41ce0 — Marcus Rohrmoser 8 years ago 0645509
look up and use adjusted image binary. refs #1
1 files changed, 53 insertions(+), 28 deletions(-)

M Photos2Atom.rb
M Photos2Atom.rb => Photos2Atom.rb +53 -28
@@ 1,7 1,7 @@
#!/usr/bin/env ruby
# encoding: UTF-8
#
# iPhoto2Atom, extract images from iPhoto™ libraries
# iPhoto2Atom, extract images from Apple™ Photos™ libraries
# Copyright (C) 2015-2016  Marcus Rohrmoser, http://purl.mro.name/iPhoto2Atom
#
# This program is free software: you can redistribute it and/or modify


@@ 23,8 23,8 @@ if ARGV[0].nil? || '-h' == ARGV[0] || '-?' == ARGV[0]
Usage: #{__FILE__} <Fotos-Mediathek.photoslibrary> <album name> <base url> <atom file output>

<Fotos-Mediathek.photoslibrary> complete filesystem path, usually inside $HOME/Pictures/
<album name>                    Fotos album to extract as a Atom photo feed
<base url>                      base url after deployment
<album name>                    Fotos album name to extract as a Atom photo feed
<base url>                      final base url (after deployment)
<atom file>                     local Atom file to create (cache)

EOF


@@ 107,7 107,9 @@ module MRO

    def initialize fotosMediathekFilePath
      t0 = Time.now
      @DB = SQLite3::Database.new(File.join(fotosMediathekFilePath, 'Database', 'Library.apdb'))
      @DB = SQLite3::Database.new(File.join(fotosMediathekFilePath, 'Database', 'Library.apdb'), :readonly => true)
      @DB.execute("ATTACH DATABASE ? AS ImageProxies", File.join(fotosMediathekFilePath, 'Database', 'ImageProxies.apdb'))
      # get & check DB version?
      @LibraryPath = fotosMediathekFilePath
    end



@@ 115,22 117,24 @@ module MRO
      @LibraryPath
    end

    def album name
      raise "Not supported."
    end

    def find_images_for_album album_name, &block
      sql = <<END_OF_SQL
SELECT
  version.name AS imageName
  , master.name AS masterName
  , version.latitude AS latitude
  , version.longitude AS longitude
  , datetime(master.imageDate + 978307200, 'unixepoch', 'localtime') AS imageDate
  , datetime(version.imageDate + 978307200, 'unixepoch', 'localtime') AS imageDate -- NSDate, Jan 1st 2001
  , master.imagePath
  , master.uuid
  , master.modelId
  , version.modelId
--  , keyword.name
  , version.adjustmentUuid
  , note_str.value AS description
  , img_prx_res.resourceUuid AS adjusted_resourceUuid
  , img_prx_res.fileName AS adjusted_fileName
  , version.rotation
  --  , keyword.name
FROM RKMaster AS master
INNER JOIN RKVersion as version
ON version.masterId = master.modelId


@@ 138,12 142,16 @@ INNER JOIN RKAlbumVersion as album4version
ON album4version.versionId = version.modelId
INNER JOIN RKAlbum as album
ON album.modelId = album4version.albumId
-- INNER JOIN RKKeywordForVersion as keyword4version
-- ON keyword4version.versionId = version.modelId
-- INNER JOIN RKKeyword as keyword
-- ON keyword.modelId = keyword4version.keywordId
WHERE album.name = ?
ORDER BY imageDate DESC
LEFT OUTER JOIN RKVersion_stringNote AS note_str
ON  version.modelId = note_str.attachedToId
AND 669 = note_str.keyPath -- where is this constant defined?
LEFT OUTER JOIN ImageProxies.RKModelResource AS img_prx_res
ON  'UNADJUSTEDNONRAW' != version.adjustmentUuid
AND version.adjustmentUuid = img_prx_res.resourceTag
WHERE album.name = ? -- version.name LIKE '%7665%'
ORDER BY
  imageDate DESC
  , adjusted_resourceUuid DESC
END_OF_SQL

      @DB.execute(sql, album_name) {|row| yield row}


@@ 191,19 199,33 @@ END_OF_SQL
        f.generator = Atom::Generator.new(:uri => 'http://purl.mro.name/iPhoto2Atom', :name => 'iPhoto2Atom')
        f.authors << Atom::Person.new(:name => 'John Doe')
        f.id = @BASE_URL # "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6"
        prev = nil
        @IPHOTO.find_images_for_album(@ALBUM_NAME) do |row|
          # prevent duplicates:
          next if prev == row[8]
          prev = row[8]
          # $stderr.puts "#{img[:created]} #{img['Caption']}"
          # unless 2 == row[MediaType]
          #   $stderr.write "-(#{img['MediaType']})"
          #   next
          # end
          src = File.join @IPHOTO.libraryPath, 'Masters', row[4]
          src = nil
          Dir.glob(File.join(@IPHOTO.libraryPath,'resources','modelresources','*','*',row[11],row[12])) {|fn| src = fn} unless row[11].nil?
          src ||= File.join(@IPHOTO.libraryPath, 'Masters', row[5]) # fallback to unmodified master image
          raise src unless src.start_with?( @IPHOTO.libraryPath )
          dst_fmt = case File.extname(src)
            when /png/i then :png
            else :jpeg
            when /png/i   then :png
            when /jpe?g/i then :jpeg
            when /jp2/i   then :jpeg
            when /mov/i   then :mov
            else raise "odd extension: '#{File.extname(src)}'"
          end
          if :mov == dst_fmt
            $stderr.write 'o'
            next
          end
          sha = Digest::SHA1.hexdigest(src[(@IPHOTO.libraryPath.length+1)..-1])   # (destination) file extension not part of sha.
          # $stderr.puts "sha('#{row[5]}') "
          sha = Digest::SHA1.hexdigest(row[5])   # (destination) file extension not part of sha.
          # $stderr.puts "  #{img['ThumbPath']}"
          # $stderr.puts "  #{img['OriginalPath']}"
          begin


@@ 211,11 233,11 @@ END_OF_SQL
              e.id = @BASE_URL + ('#_' + sha)
              # use GUID?
              # e.id = "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a"
              create_date = Time.parse row[3]
              create_date = Time.parse row[4]
              e.updated = create_date
              maxDate = create_date if create_date > maxDate
              e.title = row[0]
              e.summary = ' '
              e.title = row[0] || row[1] # fallback to master name
              e.summary = row[10]
              # use Rating ?
              dir2pxls.each do |dir,h|
                dst = File.join(@ATOM_DIR, dir, "#{sha}.#{dst_fmt}")              # (destination) file extension not part of sha.


@@ 223,7 245,7 @@ END_OF_SQL
                # unless FileUtils.uptodate?(dst, [src])                            # wrong, compare timestamp to img[:created]
                if !File.exist?(dst) || File.mtime(dst) < create_date
                  $stderr.write '+'
                  FileUtils.cp(src, dst)
                  # FileUtils.cp(src, dst)
                  unless h[:pxls].nil?
                    # @todo - keep original pngs in high-res
                    # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/sips.1.html


@@ 231,17 253,20 @@ END_OF_SQL
                    properties = {
                      :copyright      => "© #{cyear}, Marcus Rohrmoser, http://mro.name/me",
                      :artist         => 'http://mro.name/me',
                      :description    => [row[0], nil].flatten.compact.collect{|s| '' == s ? nil : s}.flatten.collect{|s| s.strip}.join(' / '),
                      :description    => [e.title, e.summary].collect{|s| '' == s ? nil : s}.compact.collect{|s| s.strip}.join(' / '),
                      :format         => dst_fmt,
                      :formatOptions  => h[:quality],
                    }
                    cmd = "sips #{properties.map{|k,v| "--setProperty #{k} #{sh_escape(v)}"}.join(' ')} -Z #{h[:pxls]} '#{dst}' > /dev/null 2> /dev/null"
                    rotation = row[11].nil? ? row[13] : 0 # rotate only if unmodified
                    cmd = "sips --rotate #{rotation} #{properties.map{|k,v| "--setProperty #{k} #{sh_escape(v)}"}.join(' ')} -Z #{h[:pxls]} '#{src}' --out '#{dst}' > /dev/null 2> /dev/null"
                    # $stderr.puts cmd
                    unless system(cmd)
                      $stderr.puts "  failed: #{cmd}"
                      FileUtils.rm dst
                    end
                    # @todo optional run pngquant / optipng
                  else
                    FileUtils.cp(src, dst)
                  end
                  FileUtils.touch dst, :mtime => create_date+1
                else


@@ 251,10 276,10 @@ END_OF_SQL
              end
              e.content = Atom::Content::External.new :src => (@ATOM_URL + ('1600p' + '/') + "#{sha}.#{dst_fmt}").to_s, :type => "image/#{dst_fmt}"
              e.media_thumbnail = Media::Thumbnail.new :url => (@ATOM_URL + ('200p' + '/') + "#{sha}.#{dst_fmt}")
              @IPHOTO.find_keywords_for_image_version_id(row[7]) do |keywor|
              @IPHOTO.find_keywords_for_image_version_id(row[8]) do |keywor|
                e.categories << Atom::Category.new( :scheme => @BASE_URL, :term => keywor, :label => keywor )
              end
              e.georss_point = "#{row[1]} #{row[2]}" if row[1] or row[2]
              e.georss_point = "#{row[2]} #{row[3]}" if row[2] or row[3]
            end
          rescue Exception => e
            $stderr.puts row