flickr-tools 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+