require "picasa" require "thread" class Imagery < Thor include Thor::Actions desc "upload [-c] [-t ] DIR", "Uploads all photos and videos from given directory (pass --continue to continue uploading)" method_option :continue, type: :boolean, default: false, aliases: "-c", desc: "continue aborted upload" method_option :threads, type: :numeric, default: 8, aliases: "-t", desc: "specify threads number" def upload(dir = File.basename(Dir.getwd)) require_credentials inside(dir, verbose: true) do extensions = Picasa::File::KnownExtensions.join("|") entries = Dir.entries(".").select { |e| e =~ /\.(#{extensions})$/i }.sort album_name = File.basename(dir) if options[:continue] # We're looking for existing album and photos already uploaded albums = client.album.list(fields: "entry(title,gphoto:id)") album = albums.entries.find { |a| a.title == album_name } if album say "Album #{album_name} found - continuing upload" else say "Album #{album_name} not found in your collection - aborting" and exit end photo_titles = client.album.show(album.id, fields: "entry/title").entries.map &:title entries.reject! { |e| photo_titles.include?(File.basename(e, ".*")) } else say("Creating album: #{album_name}") album = create_album(album_name) end mutex = Mutex.new entries_size = entries.size number = 0 options[:threads].times.map do Thread.new(entries) do |files| while file = mutex.synchronize { files.pop } mutex.synchronize { number += 1 } say("Uploading file #{file} to album #{album.title} - #{number}/#{entries_size}") create_photo(album, file_path: file, mime_type: guess_mime_type(file)) end end end.each(&:join) say "Finished uploading #{number} files" end end desc "resize [-w ] [-h ] DIR", "Resizes photos to given dimensions if exceeded" method_option :width, type: :numeric, default: 2048, aliases: "-w", desc: "max width" method_option :height, type: :numeric, default: 2048, aliases: "-h", desc: "max height" def resize(dir = ".") # Requires imagemagick library require "RMagick" inside(dir, verbose: true) do entries = Dir.entries(".").select { |e| e =~ /\.(jpg|jpeg|png|gif|bmp)$/i }.sort processed = 0 entries.each do |file| photo = Magick::Image.read(file).first processed += 1 GC.start # problems with memory leaking by RMagick if photo.columns > options[:width] || photo.rows > options[:height] say "Resizing #{file} to fit #{options[:width]}x#{options[:height]} - #{processed}/#{entries.size}" photo.resize_to_fit!(options[:width], options[:height]) photo.write(File.basename(file)) # Release memory photo.destroy! else say "Skipping #{file} - #{processed}/#{entries.size}" end end end end no_tasks do def client @client ||= Picasa::Client.new( user_id: ENV["GOOGLE_USER_ID"], access_token: ENV["GOOGLE_ACCESS_TOKEN"] ) end def require_credentials say "You must specify GOOGLE_USER_ID env variable" and exit if ENV["GOOGLE_USER_ID"].nil? if ENV["GOOGLE_ACCESS_TOKEN"].nil? say "You must specify GOOGLE_ACCESS_TOKEN env variable" and exit end end def create_album(title) client.album.create(title: title) end def create_photo(album, options = {}) client.photo.create(album.id, options) end def guess_mime_type(file) %x{file --mime-type -b #{file}}.chomp rescue end end end