picasa-downloader 0.0.1
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/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Rakefile +7 -0
- data/TODO.md +6 -0
- data/bin/picasa-downloader +33 -0
- data/fixtures/cassettes/google-login.yml +50 -0
- data/fixtures/cassettes/picasa-album.yml +119 -0
- data/fixtures/cassettes/picasa-albums.yml +99 -0
- data/fixtures/cassettes/picasa-photo.yml +23196 -0
- data/lib/picasa-downloader.rb +8 -0
- data/lib/picasa-downloader/album.rb +10 -0
- data/lib/picasa-downloader/album_persister.rb +27 -0
- data/lib/picasa-downloader/client.rb +53 -0
- data/lib/picasa-downloader/env_credentials.rb +15 -0
- data/lib/picasa-downloader/errors.rb +10 -0
- data/lib/picasa-downloader/photo.rb +12 -0
- data/picasa-downloader.gemspec +29 -0
- data/spec/album_persister_spec.rb +38 -0
- data/spec/client_spec.rb +50 -0
- data/spec/env_credentials_spec.rb +20 -0
- data/spec/test_helper.rb +23 -0
- metadata +211 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module PicasaDownloader
|
2
|
+
class AlbumPersister
|
3
|
+
def initialize(client, album, download_directory_root)
|
4
|
+
@client = client
|
5
|
+
@album = album
|
6
|
+
@download_directory_root = download_directory_root
|
7
|
+
FileUtils.mkdir_p(get_album_dir)
|
8
|
+
end
|
9
|
+
|
10
|
+
def download
|
11
|
+
@client.list_photos(@album.id).each { |photo|
|
12
|
+
photo_data = @client.download_photo(photo)
|
13
|
+
if photo_data
|
14
|
+
File.open(get_album_dir + photo.name, 'wb') { |file|
|
15
|
+
file.write(photo_data)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def get_album_dir
|
24
|
+
@download_directory_root + "/#{@album.title}/"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module PicasaDownloader
|
2
|
+
class Client
|
3
|
+
def initialize(credentials)
|
4
|
+
@gd = GData::Client::Photos.new
|
5
|
+
@gd.clientlogin(credentials.username, credentials.password)
|
6
|
+
end
|
7
|
+
|
8
|
+
def list_albums
|
9
|
+
doc = to_xml('https://picasaweb.google.com/data/feed/api/user/default')
|
10
|
+
doc.css('entry').map { |e|
|
11
|
+
id = e.xpath("id/text()").
|
12
|
+
select { |x| x.to_s.match(/^\d+$/) }.
|
13
|
+
map { |x| x.inner_text }.first
|
14
|
+
Album.new(e.css('title').first.inner_text, id)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def list_photos(album_id)
|
19
|
+
doc = to_xml(
|
20
|
+
"https://picasaweb.google.com/data/feed/api/user/default/albumid/#{album_id}?imgmax=d")
|
21
|
+
doc.css("entry").map { |e|
|
22
|
+
Photo.new(
|
23
|
+
e.xpath("group/content/@url").to_s,
|
24
|
+
# Google includes the milliseconds in the timestamp:
|
25
|
+
e.css("tags time").inner_text.to_i / 1000,
|
26
|
+
e.css("size").inner_text.to_i,
|
27
|
+
e.css("title").inner_text)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def download_photo(photo)
|
32
|
+
unless photo.url.include?"googlevideo"
|
33
|
+
uri = URI.parse(photo.url)
|
34
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
35
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
36
|
+
http.use_ssl = true
|
37
|
+
response = http.request(request)
|
38
|
+
raise "Response not ok, was #{response.code}" unless
|
39
|
+
response.code.to_i.between? 200, 299
|
40
|
+
response.body
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def to_xml(gdata_url)
|
47
|
+
response = @gd.get(gdata_url)
|
48
|
+
xml = response.to_xml.to_s
|
49
|
+
doc = Nokogiri::XML(xml)
|
50
|
+
doc.remove_namespaces!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module PicasaDownloader
|
2
|
+
class EnvCredentials
|
3
|
+
attr_reader :username, :password
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@username = ENV['PICASA_USERNAME']
|
7
|
+
@password = ENV['PICASA_PASSWORD']
|
8
|
+
end
|
9
|
+
|
10
|
+
def is_configured?
|
11
|
+
ENV['PICASA_USERNAME'].to_s != '' &&
|
12
|
+
ENV['PICASA_PASSWORD'].to_s != ''
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module PicasaDownloader
|
2
|
+
class PicasaDownloaderError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class EnvironmentalCredentialsNotSet < PicasaDownloaderError
|
6
|
+
def initialize(message = 'Cannot authenticate - environmental settings missing. Please set the environmental values PICASA_USERNAME and PICASA_PASSWORD')
|
7
|
+
super(message)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module PicasaDownloader
|
2
|
+
class Photo
|
3
|
+
attr_reader :url, :created_date, :size, :name
|
4
|
+
|
5
|
+
def initialize(url, created_date_since_epoch, size, name)
|
6
|
+
@url = url
|
7
|
+
@created_date = Time.at(created_date_since_epoch)
|
8
|
+
@size = size
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'picasa-downloader'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.license = 'MIT'
|
5
|
+
s.summary = 'Download your Google Picasa photo albums'
|
6
|
+
s.description =
|
7
|
+
'This Gem makes it convenient to download Picasa albums via command-line interface'
|
8
|
+
s.authors = ['Lauri Lehmijoki']
|
9
|
+
s.email = 'lauri.lehmijoki@iki.fi'
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.require_paths = ['lib']
|
12
|
+
s.executables << 'picasa-downloader'
|
13
|
+
s.homepage = 'https://github.com/laurilehmijoki/picasa-downloader'
|
14
|
+
|
15
|
+
s.add_dependency 'require_all'
|
16
|
+
if RUBY_VERSION < "1.9"
|
17
|
+
s.add_dependency 'gdata'
|
18
|
+
else
|
19
|
+
s.add_dependency 'gdata_19'
|
20
|
+
end
|
21
|
+
s.add_dependency 'require_relative'
|
22
|
+
s.add_dependency 'nokogiri'
|
23
|
+
s.add_dependency 'gli'
|
24
|
+
s.add_dependency 'rake'
|
25
|
+
|
26
|
+
s.add_development_dependency 'rspec'
|
27
|
+
s.add_development_dependency 'vcr'
|
28
|
+
s.add_development_dependency 'webmock'
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'vcr'
|
2
|
+
require 'test_helper'
|
3
|
+
require_relative '../lib/picasa-downloader'
|
4
|
+
|
5
|
+
module PicasaDownloader
|
6
|
+
describe AlbumPersister do
|
7
|
+
before :each do
|
8
|
+
VCR.use_cassette("google-login") do
|
9
|
+
@client = Client.new(EnvCredentials.new)
|
10
|
+
tempdir = ENV['TMPDIR']
|
11
|
+
tempdir = '' unless tempdir
|
12
|
+
@dir = tempdir + 'picasa-downloader-test'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
after :each do
|
17
|
+
FileUtils.rm_r(@dir)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "creates a directory for each album" do
|
21
|
+
with_cassettes {
|
22
|
+
@albums = @client.list_albums
|
23
|
+
AlbumPersister.new(@client, @albums.first, @dir).download
|
24
|
+
File.exists?(@dir + "/#{@albums.first.title}")
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "saves each photo into the album" do
|
29
|
+
with_cassettes {
|
30
|
+
@albums = @client.list_albums
|
31
|
+
AlbumPersister.new(@client, @albums.first, @dir).download
|
32
|
+
Dir.entries(@dir + "/#{@albums.first.title}/").any? { |file|
|
33
|
+
file.downcase.match(/jp(e)?g/)
|
34
|
+
}.should be true
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'vcr'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require_relative '../lib/picasa-downloader'
|
4
|
+
|
5
|
+
module PicasaDownloader
|
6
|
+
describe Client do
|
7
|
+
before :each do
|
8
|
+
VCR.use_cassette("google-login") do
|
9
|
+
@client = Client.new(EnvCredentials.new())
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "loads the album ids" do
|
14
|
+
VCR.use_cassette("picasa-albums") do
|
15
|
+
albums = @client.list_albums
|
16
|
+
albums.length.should be > 0
|
17
|
+
albums.first.id =~ /^\d+$/
|
18
|
+
albums.first.title.length.should be > 0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "lists photos for an album" do
|
23
|
+
VCR.use_cassette("picasa-albums") do
|
24
|
+
@albums = @client.list_albums
|
25
|
+
end
|
26
|
+
VCR.use_cassette("picasa-album") do
|
27
|
+
photos = @client.list_photos(@albums.first.id)
|
28
|
+
photos.length.should be > 0
|
29
|
+
photos.first.url =~ /^https/
|
30
|
+
photos.first.created_date.should be < Time.new
|
31
|
+
photos.first.created_date.should be > Time.at(0)
|
32
|
+
photos.first.size.should be > 0
|
33
|
+
photos.first.name.length.should be > 0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "downloads photos photos of an album" do
|
38
|
+
with_cassettes {
|
39
|
+
@albums = @client.list_albums
|
40
|
+
@photos = @client.list_photos(@albums.first.id)
|
41
|
+
def jpeg?(data)
|
42
|
+
# See http://www.astro.keele.ac.uk/oldusers/rno/Computing/File_magic.html
|
43
|
+
return data[0, 4] == "\xff\xd8\xff\xe0"
|
44
|
+
end
|
45
|
+
bytes = @client.download_photo(@photos.first)
|
46
|
+
jpeg?(bytes).should be true
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require_relative '../lib/picasa-downloader'
|
3
|
+
|
4
|
+
module PicasaDownloader
|
5
|
+
describe EnvCredentials do
|
6
|
+
it "knows when the credentials are not set" do
|
7
|
+
ENV['PICASA_USERNAME'] = ''
|
8
|
+
ENV['PICASA_PASSWORD'] = ''
|
9
|
+
creds = EnvCredentials.new
|
10
|
+
creds.is_configured?.should be false
|
11
|
+
end
|
12
|
+
|
13
|
+
it "knows when the credentials are set" do
|
14
|
+
ENV['PICASA_USERNAME'] = 'uzer'
|
15
|
+
ENV['PICASA_PASSWORD'] = 'pazz'
|
16
|
+
creds = EnvCredentials.new
|
17
|
+
creds.is_configured?.should be true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/test_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'require_relative'
|
2
|
+
|
3
|
+
VCR.configure do |config|
|
4
|
+
config.cassette_library_dir = "fixtures/cassettes"
|
5
|
+
config.hook_into :webmock
|
6
|
+
end
|
7
|
+
|
8
|
+
def with_cassettes
|
9
|
+
photo_matcher = lambda do |req_1, req_2|
|
10
|
+
[req_1, req_2].all? { |req|
|
11
|
+
req.uri.include?"googleusercontent"
|
12
|
+
}
|
13
|
+
end
|
14
|
+
VCR.use_cassette("picasa-albums") do
|
15
|
+
VCR.use_cassette("picasa-album") do
|
16
|
+
VCR.use_cassette("picasa-photo",
|
17
|
+
:match_requests_on => [photo_matcher],
|
18
|
+
:allow_playback_repeats => true) do
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: picasa-downloader
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Lauri Lehmijoki
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-07-18 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: require_all
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: gdata
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: require_relative
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: nokogiri
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: gli
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
type: :runtime
|
89
|
+
version_requirements: *id005
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: rake
|
92
|
+
prerelease: false
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
type: :runtime
|
103
|
+
version_requirements: *id006
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: rspec
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
type: :development
|
117
|
+
version_requirements: *id007
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: vcr
|
120
|
+
prerelease: false
|
121
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
type: :development
|
131
|
+
version_requirements: *id008
|
132
|
+
- !ruby/object:Gem::Dependency
|
133
|
+
name: webmock
|
134
|
+
prerelease: false
|
135
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 3
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
version: "0"
|
144
|
+
type: :development
|
145
|
+
version_requirements: *id009
|
146
|
+
description: This Gem makes it convenient to download Picasa albums via command-line interface
|
147
|
+
email: lauri.lehmijoki@iki.fi
|
148
|
+
executables:
|
149
|
+
- picasa-downloader
|
150
|
+
extensions: []
|
151
|
+
|
152
|
+
extra_rdoc_files: []
|
153
|
+
|
154
|
+
files:
|
155
|
+
- .gitignore
|
156
|
+
- .travis.yml
|
157
|
+
- Gemfile
|
158
|
+
- Rakefile
|
159
|
+
- TODO.md
|
160
|
+
- bin/picasa-downloader
|
161
|
+
- fixtures/cassettes/google-login.yml
|
162
|
+
- fixtures/cassettes/picasa-album.yml
|
163
|
+
- fixtures/cassettes/picasa-albums.yml
|
164
|
+
- fixtures/cassettes/picasa-photo.yml
|
165
|
+
- lib/picasa-downloader.rb
|
166
|
+
- lib/picasa-downloader/album.rb
|
167
|
+
- lib/picasa-downloader/album_persister.rb
|
168
|
+
- lib/picasa-downloader/client.rb
|
169
|
+
- lib/picasa-downloader/env_credentials.rb
|
170
|
+
- lib/picasa-downloader/errors.rb
|
171
|
+
- lib/picasa-downloader/photo.rb
|
172
|
+
- picasa-downloader.gemspec
|
173
|
+
- spec/album_persister_spec.rb
|
174
|
+
- spec/client_spec.rb
|
175
|
+
- spec/env_credentials_spec.rb
|
176
|
+
- spec/test_helper.rb
|
177
|
+
homepage: https://github.com/laurilehmijoki/picasa-downloader
|
178
|
+
licenses:
|
179
|
+
- MIT
|
180
|
+
post_install_message:
|
181
|
+
rdoc_options: []
|
182
|
+
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
none: false
|
187
|
+
requirements:
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
hash: 3
|
191
|
+
segments:
|
192
|
+
- 0
|
193
|
+
version: "0"
|
194
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
|
+
none: false
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
hash: 3
|
200
|
+
segments:
|
201
|
+
- 0
|
202
|
+
version: "0"
|
203
|
+
requirements: []
|
204
|
+
|
205
|
+
rubyforge_project:
|
206
|
+
rubygems_version: 1.8.13
|
207
|
+
signing_key:
|
208
|
+
specification_version: 3
|
209
|
+
summary: Download your Google Picasa photo albums
|
210
|
+
test_files: []
|
211
|
+
|