flickr-tools 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2010 Jens Krämer, jk@jkraemer.net
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+
23
+
@@ -0,0 +1,55 @@
1
+ Flickr toolchain
2
+ ================
3
+
4
+ Collection of simple command line tools I use to handle my flickr needs.
5
+
6
+ Use at your own risk, ymmv.
7
+
8
+ Authorization
9
+ -------------
10
+
11
+ First of all you need to get an API key at flickr.com. Put the key and the secret into ~/.flickr-tools/flickr.yml:
12
+
13
+ key: your-flickr-api-key
14
+ secret: your-flickr-secret
15
+ token_cache: token_cache.yml
16
+
17
+
18
+ Now call
19
+
20
+ flickr-tools Auth username
21
+
22
+ This will prompt you to visit an URL at flickr.com to authorize write access for the application. Do so and press Enter.
23
+ The token received from flickr will be stored in ~/.flickr-tools/username.yml for further use.
24
+
25
+
26
+
27
+ Listing sets
28
+ ------------
29
+
30
+ flickr-tools GetSet username
31
+
32
+ will list all sets in your flickr account.
33
+
34
+
35
+ Download whole set
36
+ ------------------
37
+
38
+ flickr-tools GetSet username setname
39
+
40
+ Use this command to download all pictures (in 'original' size) of the named set. The result is a single zip file containing all images.
41
+ You can use the set id (as printed out by the set listing from above) or a fragment/regexp matching the set title. The first matching
42
+ set will be exported.
43
+
44
+
45
+ Upload Picture
46
+ --------------
47
+
48
+ flickr-tools Upload username /path/to/picture
49
+
50
+ I intend to use this command to directly upload images to flickr from a batch output queue in [Bibble](http://bibblelabs.com/).
51
+ Flickr metadata (privacy, tags, title, description) is extracted from iptc metadata contained in the picture. See lib/flickr-tools/upload.rb
52
+ for how it's mapped (and modify to suit your needs).
53
+
54
+ NOTE: Upload doesn't work, seems I'll have to fix flickr_fu first to make it work...
55
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'flickr-tools'
4
+ include FlickrTools
5
+
6
+ cmd = ARGV.shift
7
+ app = FlickrTools::const_get(cmd).new ARGV
8
+
9
+ # puts "running #{app.inspect}"
10
+ unless cmd == 'Auth'
11
+ FlickrTools::Auth.new(ARGV).check_token
12
+ end
13
+ app.run
14
+
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+
3
+ # sample shell script to be called from asset management programs like bibble
4
+ # takes the filename as argument
5
+
6
+ source $HOME/.rvm/scripts/rvm
7
+ rvm use 1.9.2
8
+ flickr-tools Upload jk $1 >> /Users/jk/Pictures/flickr_upload/log 2>&1
9
+
@@ -0,0 +1,3 @@
1
+ key: your-flickr-api-key
2
+ secret: your-flickr-secret
3
+ token_cache: token_cache.yml
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ gem 'bundler'
3
+ require 'bundler'
4
+
5
+
6
+ require 'flickr_fu_ext'
7
+ require 'flickr-tools/core_ext'
8
+ require 'flickr-tools/auth'
9
+ require 'flickr-tools/get_set'
10
+ require 'flickr-tools/upload'
@@ -0,0 +1,49 @@
1
+ require 'flickr-tools/command'
2
+
3
+ module FlickrTools
4
+
5
+ # USAGE:
6
+ # flickr-tools Auth username
7
+ #
8
+ # the username argument is just a shorthand to identify the same user in further calls to other flickr-tools commands, must not equal your flickr username.
9
+ class Auth < Command
10
+
11
+ def run
12
+ authorize
13
+ end
14
+
15
+ def check_token(required_perms = :write)
16
+ res = flickr.auth.check_token
17
+ allowed_perms = case required_perms
18
+ when :read
19
+ %w(read write delete)
20
+ when :write
21
+ %w(write delete)
22
+ when :delete
23
+ %w(delete)
24
+ end
25
+ perm = res.auth.perms.to_s
26
+ if allowed_perms.include?(perm)
27
+ true
28
+ else
29
+ puts "insufficient permissions: #{perm}, required was: #{required_perms}"
30
+ false
31
+ end
32
+ rescue Exception
33
+ puts "check_token failed: #{$!}\n#{$!.backtrace.join("\n")}"
34
+ # authorize
35
+ end
36
+
37
+
38
+ def authorize
39
+ @flickr = nil
40
+ FileUtils.rm_f @token_cache
41
+ puts "please visit the following url, then press <enter> once you have authorized:"
42
+ # request permissions
43
+ puts flickr.auth.url(:delete)
44
+ STDIN.gets
45
+ flickr.auth.cache_token
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,38 @@
1
+ module FlickrTools
2
+
3
+ class Command
4
+
5
+ def self.find_config_dir
6
+ config_dir = File.expand_path('../../../config', __FILE__)
7
+ unless File.directory?(config_dir)
8
+ config_dir = File.join(ENV['HOME'], '.flickr-tools')
9
+ FileUtils.mkdir_p config_dir
10
+ end
11
+ return config_dir
12
+ end
13
+
14
+
15
+ def initialize(argv)
16
+ @config_dir = self.class.find_config_dir
17
+ @args = argv.dup
18
+ @name = @args.shift
19
+ @token_cache = File.join(@config_dir, "#{@name}.yml")
20
+ @flickr_yml = File.join @config_dir, 'flickr.yml'
21
+ unless File.readable?(@flickr_yml)
22
+ FileUtils.cp File.expand_path('../../../doc/flickr.yml.example', __FILE__), @flickr_yml
23
+ puts "Please get a flickr API key and secret from flickr.com and edit #{@flickr_yml} accordingly."
24
+ exit 1
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ def flickr
31
+ @flickr ||= Flickr.new(@flickr_yml, :token_cache => @token_cache)
32
+ end
33
+
34
+
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def to_filename
3
+ gsub /\W+/, '_'
4
+ end
5
+ end
@@ -0,0 +1,103 @@
1
+ require 'flickr_fu'
2
+ require 'zip/zipfilesystem'
3
+ require 'open-uri'
4
+
5
+ require 'flickr-tools/command'
6
+
7
+ # USAGE:
8
+ # flickr-tools GetSet jk
9
+ # flickr-tools GetSet jk setname
10
+ module FlickrTools
11
+ class GetSet < Command
12
+
13
+ def run
14
+ set = @args.shift
15
+
16
+ if set.nil? || set.blank?
17
+ show_sets
18
+ else
19
+ get_set set
20
+ end
21
+
22
+ end
23
+
24
+ def show_sets
25
+ puts "getting sets for #{@name}"
26
+ flickr.photosets.get_list.each do |set|
27
+ puts set_description(set)
28
+ end
29
+ end
30
+
31
+ def get_set(id, size = :original)
32
+ if set = find_set(id)
33
+ puts "getting set #{set_description set}"
34
+ dir = set.title.to_filename
35
+ FileUtils.mkdir_p dir
36
+ puts "downloading to #{dir}/"
37
+ per_page = 200
38
+ @queue = Queue.new
39
+ 1.upto((set.num_photos.to_i / per_page) + 1) do |page|
40
+ puts "."
41
+ set.get_photos(:page => page, :per_page => per_page).each do |p|
42
+ @queue.enq({ :url => p.url(size), :filename => "#{dir}/#{p.title.to_filename}.jpg" })
43
+ end
44
+ end
45
+ puts "found #{@queue.size} photos"
46
+ spawn_workers
47
+ wait_for_workers
48
+ else
49
+ puts "no set matching #{id} found."
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ def find_set(id)
56
+ flickr.photosets.get_list.find{|set| set.id == id || set.title =~ /#{id}/i }
57
+ end
58
+
59
+ def set_description(set)
60
+ "#{set.id} #{set.title} (#{set.num_photos} photos)"
61
+ end
62
+
63
+
64
+ def spawn_workers(n = 5)
65
+ @workers = []
66
+ n.times do
67
+ t = Thread.new do
68
+ while !@stop && p = @queue.deq
69
+ begin
70
+ puts "downloading #{p[:url]}"
71
+ open( p[:url] ){ |f| (File.open(p[:filename], "w") << f.read).close }
72
+ rescue Exception
73
+ retries = (p[:retries] || 0) + 1
74
+ if retries < 3
75
+ @queue.enq p
76
+ else
77
+ puts "giving up downloading #{p.inspect}:\n#{$!}.message"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ @workers << t
83
+ end
84
+ end
85
+
86
+ def wait_for_workers
87
+ while !@queue.empty?
88
+ puts "#{@queue.size} items left..."
89
+ sleep 30
90
+ end
91
+ puts "killing workers..."
92
+ @stop = true
93
+ @workers.each { |w| w.join }
94
+ puts "Done."
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+
101
+
102
+
103
+
@@ -0,0 +1,83 @@
1
+ require 'flickr_fu'
2
+ # require 'exifr'
3
+ require 'iptc'
4
+ require 'pp'
5
+
6
+ require 'flickr-tools/command'
7
+
8
+ module FlickrTools
9
+
10
+ # Upload one or more image files to flickr.
11
+ #
12
+ # USAGE:
13
+ # flickr-tools Upload jk path/to/image ...
14
+ #
15
+ # Flickr metadata is determined from the picture's IPTC metadata:
16
+ # title - iptc/Headline
17
+ # description - iptc/Caption
18
+ # tags - iptc/Keywords minus IGNORE_KEYWORDS and PRIVACY_KEYWORDS
19
+ # privacy - privacy level is set to the given PRIVACY_KEYWORD if also the 'privacy' keyword is present in iptc keywords, public otherwise.
20
+ #
21
+ # The keyword-to-tags treatment might seem strange, the reason is that Bibble uses hierarchical keyword trees which get flattened when written
22
+ # to IPTC meta data. That's why my pictures have these abstract first level keywords like 'place' and 'year' which I don't want to appear on flickr.
23
+ class Upload < Command
24
+
25
+ # list of keywords not to be used as flickr tags
26
+ IGNORE_KEYWORDS = %w(place year privacy subject)
27
+
28
+ # list of keywords used to determine the flickr privacy level. Photos are public by default.
29
+ PRIVACY_KEYWORDS = %w(private friends family friends_and_family)
30
+
31
+ def run
32
+ @uploader = Flickr::Uploader.new flickr
33
+ @args.each { |f| upload_file f }
34
+ puts "done."
35
+ end
36
+
37
+ protected
38
+
39
+ def upload_file(file)
40
+ if File.readable?(file)
41
+ meta = { :content_type => :photo, :safety_level => :safe, :hidden => false }.merge(metadata_for(file))
42
+ puts "uploading #{file} with\n#{meta.inspect}"
43
+ response = @uploader.upload file, meta
44
+
45
+ puts response.photoid
46
+ options = { :photo_id => response.photoid, :tags => meta[:tags] }
47
+ flickr.send_request('flickr.photos.setTags', options, :post)
48
+ else
49
+ puts "file not found: #{file}"
50
+ end
51
+ rescue Exception
52
+ puts "Error uploading #{file}: #{$!}\n#{$!.backtrace.join("\n")}"
53
+ end
54
+
55
+ def metadata_for(file)
56
+ iptc = iptc(file)
57
+ keywords = iptc["iptc/Keywords"].value rescue ''
58
+ {
59
+ :title => (iptc["iptc/Headline"].value.first rescue File.basename(file)),
60
+ :description => (iptc["iptc/Caption"].value.first rescue ''),
61
+ :tags => keywords_to_tags(keywords).join(' '),
62
+ :privacy => privacy(keywords)
63
+ }
64
+ end
65
+
66
+ def privacy(keywords)
67
+ privacy = ((keywords.include?('privacy') ? (keywords & PRIVACY_KEYWORDS).first : nil ) || 'public').to_sym
68
+ end
69
+
70
+ def keywords_to_tags(keywords)
71
+ keywords.map(&:downcase) - IGNORE_KEYWORDS - PRIVACY_KEYWORDS
72
+ end
73
+
74
+ def iptc(file)
75
+ IPTC::JPEG::Image.new(file).values
76
+ end
77
+
78
+ # def exif(file)
79
+ # pp EXIFR::JPEG.new(file).exif
80
+ # end
81
+
82
+ end
83
+ end
@@ -0,0 +1,3 @@
1
+ module FlickrTools
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'flickr_fu'
2
+
3
+ module Flickr
4
+ class Auth
5
+ def check_token
6
+ rsp = @flickr.send_request('flickr.auth.checkToken')
7
+ if rsp[:stat] == 'ok'
8
+ rsp
9
+ else
10
+ raise "#{rsp.err[:code]}: #{rsp.err[:msg]}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,132 @@
1
+ =begin rdoc
2
+
3
+ == tempdir.rb - provides class Tempdir
4
+
5
+ === Synopsis
6
+ In order to
7
+
8
+ 1. create a temporary directory with a name starting with "hoge",
9
+ 2. show the name of that directory, and
10
+ 3. show its properties with the system "ls -dl" command:
11
+
12
+ require 'tempdir'
13
+
14
+ d = Tempdir.new("hoge")
15
+ p d.to_s
16
+ system("ls", "-dl", d.to_s)
17
+
18
+ After the run, the directory will appear to be removed. That is: if you
19
+ are not running inside it, which may provide you a means to create a
20
+ directory with a unique name that will be available after the run.
21
+
22
+ === Description
23
+ <i>tempdir.rb</i> provides the Tempdir class, which is analogous to the
24
+ Tempfile class, but manipulates temporary directories. Derived from a
25
+ proposal of nobu.nokada@softhome.net. He added the class to
26
+ tempfile.rb, but since it does not seem to appear in cvs, I'll keep
27
+ it apart in tempdir.rb.
28
+
29
+ =end
30
+
31
+ require 'tmpdir'
32
+
33
+ module AutoRemoval #:nodoc: all
34
+ MAX_TRY = 10
35
+ @@cleanlist = []
36
+
37
+ private
38
+
39
+ def make_tmpname(basename, n)
40
+ sprintf('%s%d.%d', basename, $$, n)
41
+ end
42
+ private :make_tmpname
43
+
44
+ def createtmp(basename, tmpdir=Dir::tmpdir) # :nodoc:
45
+ if $SAFE > 0 and tmpdir.tainted?
46
+ tmpdir = '/tmp'
47
+ end
48
+
49
+ lock = nil
50
+ n = failure = 0
51
+
52
+ begin
53
+ Thread.critical = true
54
+ begin
55
+ tmpname = File.join(tmpdir, make_tmpname(basename, n))
56
+ n += 1
57
+ end until !@@cleanlist.include?(tmpname) and yield(tmpname)
58
+ rescue
59
+ p $!
60
+ failure += 1
61
+ retry if failure < MAX_TRY
62
+ raise "cannot generate tempfile `%s'" % tmpname
63
+ ensure
64
+ Thread.critical = false
65
+ end
66
+
67
+ tmpname
68
+ end
69
+
70
+ def self.callback(path, clear) # :nodoc:
71
+ @@cleanlist << path
72
+ data = [path]
73
+ pid = $$
74
+ return Proc.new {
75
+ if pid == $$
76
+ path, tmpfile = *data
77
+
78
+ print "removing ", path, "..." if $DEBUG
79
+
80
+ tmpfile.close if tmpfile
81
+
82
+ # keep this order for thread safeness
83
+ if File.exist?(path)
84
+ clear.call(path)
85
+ end
86
+ @@cleanlist.delete(path)
87
+
88
+ print "done\n" if $DEBUG
89
+ end
90
+ }, data
91
+ end
92
+
93
+ def self.unregister(path)
94
+ @@cleanlist.delete(path)
95
+ end
96
+ end
97
+
98
+ require 'pathname'
99
+ class Tempdir < Pathname #:nodoc: all
100
+ include AutoRemoval
101
+
102
+ def initialize(*args)
103
+ require 'fileutils'
104
+
105
+ tmpname = createtmp(*args) do |tmpname|
106
+ unless File.exist?(tmpname)
107
+ Dir.mkdir(tmpname, 0700)
108
+ end
109
+ end
110
+
111
+ super(tmpname)
112
+ @clean_proc, = AutoRemoval.callback(tmpname, FileUtils.method(:rm_rf))
113
+ ObjectSpace.define_finalizer(self, @clean_proc) end
114
+
115
+ def open(basename, *modes, &block)
116
+ File.open(self+basename, *modes, &block)
117
+ end
118
+
119
+ def clear
120
+ FileUtils.rm_rf(@tmpname)
121
+ @clean_proc.call
122
+ ObjectSpace.undefine_finalizer(self)
123
+ end
124
+ end
125
+
126
+ if __FILE__ == $0
127
+ # $DEBUG = true
128
+ d = Tempdir.new("hoge")
129
+ p d.to_s
130
+ system("ls", "-dl", d.to_s)
131
+ p d
132
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flickr-tools
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.2
6
+ platform: ruby
7
+ authors:
8
+ - Jens Kraemer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-27 00:00:00 +02:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rubyzip
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: xml-magic
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: tomk32-flickr_fu
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.3.4
47
+ type: :runtime
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: iptc
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 0.0.2
58
+ type: :runtime
59
+ version_requirements: *id004
60
+ description: You're definitely going to want to replace a lot of this
61
+ email:
62
+ - jk@jkraemer.net
63
+ executables:
64
+ - flickr-tools
65
+ extensions: []
66
+
67
+ extra_rdoc_files: []
68
+
69
+ files:
70
+ - lib/flickr-tools/auth.rb
71
+ - lib/flickr-tools/command.rb
72
+ - lib/flickr-tools/core_ext.rb
73
+ - lib/flickr-tools/get_set.rb
74
+ - lib/flickr-tools/upload.rb
75
+ - lib/flickr-tools/version.rb
76
+ - lib/flickr-tools.rb
77
+ - lib/flickr_fu_ext.rb
78
+ - lib/tempdir.rb
79
+ - bin/flickr-tools
80
+ - LICENSE
81
+ - README.md
82
+ - doc/flickr-upload.sh
83
+ - doc/flickr.yml.example
84
+ has_rdoc: true
85
+ homepage: http://github.com/carlhuda/newgem
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 1.3.6
105
+ requirements: []
106
+
107
+ rubyforge_project: newgem
108
+ rubygems_version: 1.5.3
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: flickr utitlity library
112
+ test_files: []
113
+