iphoto_backup 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2ab4c53aa5fd307714a1cc1343943344de35c367
4
- data.tar.gz: 6e07fda88244670e5e7887e2bbb0adcb98f40804
3
+ metadata.gz: b9dca72c4a6a1dde1fb22151dc7d109220848923
4
+ data.tar.gz: 6a4d33e9ef779e0ed4c6846ed1cb314f7e1ae7bf
5
5
  SHA512:
6
- metadata.gz: ff8f154a1fbebe3baf811df3830e642a2d406327e790a0e9ed083ca86709fa26f91f438f1c94102cfc0501bcdb062179ff265fcf8c94bc1841767c1a547fc186
7
- data.tar.gz: 12200098d14a2423850193173ac66034247014800d6a0eb0ee02bc5fb64c92db67bbbc15ec489dc7461524c1945e4dd7de14800f2a7ab16f15c65724e53b41c7
6
+ metadata.gz: 1967d543c0966f836b054a704a152f4b05e9229c3fa086ccd6373705e86c03096aa79c89af93c3b55fa573c17d553b6d58320689a710321380cd1b119ef8afc3
7
+ data.tar.gz: 3a8925d43561b689098c2cb026f8c873ae52a7dfa583c127a07a8452a3d12c88444603e9884e85f4197d0f90c4d99672c6c044f2e0f2756c1d17f2a97d1c8d02
data/.gitignore CHANGED
@@ -15,3 +15,6 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+
19
+ # OSX files
20
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - '2.0.0'
data/README.md CHANGED
@@ -1,26 +1,54 @@
1
- # IphotoBackup
1
+ [![Build Status](https://travis-ci.org/wireframe/iphoto_backup.png?branch=master)](https://travis-ci.org/wireframe/iphoto_backup)
2
2
 
3
- TODO: Write a gem description
3
+ # iphoto_backup
4
+
5
+ > Every photo deserves to live in a folder on the filesystem and not
6
+ > to be locked up in some cryptic and proprietary iPhoto metadata XML file.
7
+
8
+ iphoto_backup is a tool to simplify backups and archiving of your iPhoto images.
4
9
 
5
10
  [Originally implemented as a Python script](https://github.com/wireframe/dotfiles/blob/628b982d9fc4e7b4cc9e6ca806cae81b541f9bbd/home/bin/iphoto_export.py)
6
11
 
7
12
  ## Installation
8
13
 
9
- Add this line to your application's Gemfile:
14
+ ```bash
15
+ $ gem install iphoto_backup
16
+ ```
17
+
18
+ ## Usage
10
19
 
11
- gem 'iphoto_backup'
20
+ ```bash
21
+ $ iphoto_backup
12
22
 
13
- And then execute:
23
+ Processing Roll: Wedding Pics...
24
+ copying /iphoto/file.png to /my/custom/backup.png
25
+ ```
14
26
 
15
- $ bundle
27
+ ## Options
16
28
 
17
- Or install it yourself as:
29
+ #### --filter [REGEX]
18
30
 
19
- $ gem install iphoto_backup
31
+ *aliased to -e*
20
32
 
21
- ## Usage
33
+ Restrict exporting to only albums that match the given regular expression. Albums that do not match the regex will be printed in the log output as well.
34
+
35
+ example:
36
+ ```bash
37
+ $ iphoto_backup -e Summer
38
+
39
+
40
+ Processing Roll: Summer Pics...
41
+ copying /iphoto/file.png to /my/custom/backup.png
42
+
43
+ Winter Pics does not match the filter: /Summer/
44
+ ```
45
+
46
+ #### --output [/path/to/directory]
47
+
48
+ *aliased to -o*
49
+ *default to ~/Google Drive/Dropbox*
22
50
 
23
- TODO: Write usage instructions here
51
+ Customize the path for archiving photos.
24
52
 
25
53
  ## Contributing
26
54
 
@@ -6,17 +6,18 @@ module IphotoBackup
6
6
  class CLI < Thor
7
7
  IPHOTO_ALBUM_PATH = "~/Pictures/iPhoto Library.photolibrary/AlbumData.xml"
8
8
  DEFAULT_OUTPUT_DIRECTORY = "~/Google Drive/Dropbox"
9
+ IPHOTO_EPOCH = Time.new(2001, 1, 1)
9
10
 
10
11
  desc "export iPhoto albums", "exports iPhoto albums into target directory"
11
12
  option :filter, aliases: '-e', default: '.*'
12
13
  option :output, aliases: '-o', default: DEFAULT_OUTPUT_DIRECTORY
14
+ option :config, aliases: '-c', default: IPHOTO_ALBUM_PATH
15
+ option :'date-prefix', aliases: '-d', default: false
13
16
  def export
14
17
  each_album do |folder_name, album_info|
15
18
  say "\n\nProcessing Roll: #{folder_name}..."
16
19
 
17
- album_images = value_for_dictionary_key('KeyList', album_info).css('string').map(&:content)
18
- album_images.each do |image_id|
19
- image_info = info_for_image image_id
20
+ each_image(album_info) do |image_info|
20
21
  source_path = value_for_dictionary_key('ImagePath', image_info).content
21
22
 
22
23
  target_path = File.join(File.expand_path(options[:output]), folder_name, File.basename(source_path))
@@ -41,6 +42,17 @@ module IphotoBackup
41
42
  albums.each do |album_info|
42
43
  folder_name = value_for_dictionary_key('RollName', album_info).content
43
44
 
45
+ if options[:'include-date-prefix'] && folder_name !~ /^\d{4}-\d{2}-\d{2} /
46
+ album_date = nil
47
+ each_image album_info do |image_info|
48
+ next if album_date
49
+ photo_interval = value_for_dictionary_key('DateAsTimerInterval', image_info).content.to_i
50
+ album_date = (IPHOTO_EPOCH + photo_interval).to_date
51
+ end
52
+ say "Automatically adding #{album_date} prefix to folder: #{folder_name}"
53
+ folder_name = "#{album_date} #{folder_name}"
54
+ end
55
+
44
56
  if folder_name.match(album_filter)
45
57
  yield folder_name, album_info
46
58
  else
@@ -49,6 +61,14 @@ module IphotoBackup
49
61
  end
50
62
  end
51
63
 
64
+ def each_image(album_info, &block)
65
+ album_images = value_for_dictionary_key('KeyList', album_info).css('string').map(&:content)
66
+ album_images.each do |image_id|
67
+ image_info = info_for_image image_id
68
+ yield image_info
69
+ end
70
+ end
71
+
52
72
  def info_for_image(image_id)
53
73
  value_for_dictionary_key image_id, master_images
54
74
  end
@@ -78,7 +98,8 @@ module IphotoBackup
78
98
 
79
99
  def root_dictionary
80
100
  @root_dictionary ||= begin
81
- file = File.expand_path IPHOTO_ALBUM_PATH
101
+ file = File.expand_path options[:config]
102
+ say "Loading AlbumData: #{file}"
82
103
  doc = Nokogiri.XML(File.read(file))
83
104
  doc.child.children.find {|n| n.name == 'dict' }
84
105
  end
@@ -1,3 +1,3 @@
1
1
  module IphotoBackup
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,163 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <plist version="1.0">
3
+ <dict>
4
+ <key>List of Rolls</key>
5
+ <array>
6
+ <dict>
7
+ <key>RollID</key>
8
+ <integer>640</integer>
9
+ <key>RollName</key>
10
+ <string>Summer Party</string>
11
+ <key>RollDateAsTimerInterval</key>
12
+ <real>21600.000000</real>
13
+ <key>KeyPhotoKey</key>
14
+ <string>1001</string>
15
+ <key>PhotoCount</key>
16
+ <integer>4</integer>
17
+ <key>KeyList</key>
18
+ <array>
19
+ <string>1001</string>
20
+ <string>1002</string>
21
+ </array>
22
+ </dict>
23
+ <dict>
24
+ <key>RollID</key>
25
+ <integer>646</integer>
26
+ <key>ProjectUuid</key>
27
+ <string>vU0ooK6bSX2Ih3PpnghUxA</string>
28
+ <key>RollName</key>
29
+ <string>2013-10-10 Fall Supper</string>
30
+ <key>RollDateAsTimerInterval</key>
31
+ <real>21600.000000</real>
32
+ <key>KeyPhotoKey</key>
33
+ <string>2002</string>
34
+ <key>PhotoCount</key>
35
+ <integer>1</integer>
36
+ <key>KeyList</key>
37
+ <array>
38
+ <string>2001</string>
39
+ <string>2002</string>
40
+ </array>
41
+ </dict>
42
+ </array>
43
+ <key>Master Image List</key>
44
+ <dict>
45
+ <key>1001</key>
46
+ <dict>
47
+ <key>Caption</key>
48
+ <string>IMG_3511</string>
49
+ <key>Comment</key>
50
+ <string> </string>
51
+ <key>GUID</key>
52
+ <string>oh0lvaosT7y3i1ykESTalQ</string>
53
+ <key>Roll</key>
54
+ <integer>636</integer>
55
+ <key>Rating</key>
56
+ <integer>0</integer>
57
+ <key>ImagePath</key>
58
+ <string><%= PROJECT_DIR %>/spec/fixtures/image.jpg</string>
59
+ <key>MediaType</key>
60
+ <string>Image</string>
61
+ <key>ModDateAsTimerInterval</key>
62
+ <real>404275004.228640</real>
63
+ <key>DateAsTimerInterval</key>
64
+ <real>402413114.000000</real>
65
+ <key>DateAsTimerIntervalGMT</key>
66
+ <real>402395114.000000</real>
67
+ <key>MetaModDateAsTimerInterval</key>
68
+ <real>404275024.317730</real>
69
+ <key>OriginalPath</key>
70
+ <string><%= PROJECT_DIR %>/spec/fixtures/image.jpg</string>
71
+ <key>ThumbPath</key>
72
+ <string><%= PROJECT_DIR %>/spec/fixtures/image.jpg</string>
73
+ </dict>
74
+ <key>1002</key>
75
+ <dict>
76
+ <key>Caption</key>
77
+ <string>IMG_3511</string>
78
+ <key>Comment</key>
79
+ <string> </string>
80
+ <key>GUID</key>
81
+ <string>oh0lvaosT7y3i1ykESTalQ</string>
82
+ <key>Roll</key>
83
+ <integer>636</integer>
84
+ <key>Rating</key>
85
+ <integer>0</integer>
86
+ <key>ImagePath</key>
87
+ <string><%= PROJECT_DIR %>/spec/fixtures/image2.jpg</string>
88
+ <key>MediaType</key>
89
+ <string>Image</string>
90
+ <key>ModDateAsTimerInterval</key>
91
+ <real>404275004.228640</real>
92
+ <key>DateAsTimerInterval</key>
93
+ <real>402413114.000000</real>
94
+ <key>DateAsTimerIntervalGMT</key>
95
+ <real>402395114.000000</real>
96
+ <key>MetaModDateAsTimerInterval</key>
97
+ <real>404275024.317730</real>
98
+ <key>OriginalPath</key>
99
+ <string><%= PROJECT_DIR %>/spec/fixtures/image2.jpg</string>
100
+ <key>ThumbPath</key>
101
+ <string><%= PROJECT_DIR %>/spec/fixtures/image2.jpg</string>
102
+ </dict>
103
+ <key>2001</key>
104
+ <dict>
105
+ <key>Caption</key>
106
+ <string>IMG_3511</string>
107
+ <key>Comment</key>
108
+ <string> </string>
109
+ <key>GUID</key>
110
+ <string>oh0lvaosT7y3i1ykESTalQ</string>
111
+ <key>Roll</key>
112
+ <integer>636</integer>
113
+ <key>Rating</key>
114
+ <integer>0</integer>
115
+ <key>ImagePath</key>
116
+ <string><%= PROJECT_DIR %>/spec/fixtures/image.jpg</string>
117
+ <key>MediaType</key>
118
+ <string>Image</string>
119
+ <key>ModDateAsTimerInterval</key>
120
+ <real>404275004.228640</real>
121
+ <key>DateAsTimerInterval</key>
122
+ <real>402413114.000000</real>
123
+ <key>DateAsTimerIntervalGMT</key>
124
+ <real>402395114.000000</real>
125
+ <key>MetaModDateAsTimerInterval</key>
126
+ <real>404275024.317730</real>
127
+ <key>OriginalPath</key>
128
+ <string><%= PROJECT_DIR %>/spec/fixtures/image.jpg</string>
129
+ <key>ThumbPath</key>
130
+ <string><%= PROJECT_DIR %>/spec/fixtures/image.jpg</string>
131
+ </dict>
132
+ <key>2002</key>
133
+ <dict>
134
+ <key>Caption</key>
135
+ <string>IMG_3511</string>
136
+ <key>Comment</key>
137
+ <string> </string>
138
+ <key>GUID</key>
139
+ <string>oh0lvaosT7y3i1ykESTalQ</string>
140
+ <key>Roll</key>
141
+ <integer>636</integer>
142
+ <key>Rating</key>
143
+ <integer>0</integer>
144
+ <key>ImagePath</key>
145
+ <string><%= PROJECT_DIR %>/spec/fixtures/image2.jpg</string>
146
+ <key>MediaType</key>
147
+ <string>Image</string>
148
+ <key>ModDateAsTimerInterval</key>
149
+ <real>404275004.228640</real>
150
+ <key>DateAsTimerInterval</key>
151
+ <real>402413114.000000</real>
152
+ <key>DateAsTimerIntervalGMT</key>
153
+ <real>402395114.000000</real>
154
+ <key>MetaModDateAsTimerInterval</key>
155
+ <real>404275024.317730</real>
156
+ <key>OriginalPath</key>
157
+ <string><%= PROJECT_DIR %>/spec/fixtures/image2.jpg</string>
158
+ <key>ThumbPath</key>
159
+ <string><%= PROJECT_DIR %>/spec/fixtures/image2.jpg</string>
160
+ </dict>
161
+ </dict>
162
+ </dict>
163
+ </plist>
Binary file
Binary file
@@ -1,8 +1,17 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe IphotoBackup::CLI do
4
+ TMP_OUTPUT_DIRECTORY = 'tmp/backup'
5
+ PROJECT_DIR = File.expand_path(File.join(File.expand_path(__dir__), '../../../'))
6
+
4
7
  let(:args) { [] }
5
- let(:options) { {} }
8
+ let(:options) {
9
+ {
10
+ config: 'tmp/AlbumData.xml',
11
+ output: TMP_OUTPUT_DIRECTORY,
12
+ filter: '.*'
13
+ }
14
+ }
6
15
  let(:config) do
7
16
  {
8
17
  pretend: true
@@ -10,12 +19,66 @@ describe IphotoBackup::CLI do
10
19
  end
11
20
  let(:cli) { IphotoBackup::CLI.new(args, options, config) }
12
21
 
22
+ before do
23
+ template = File.read('spec/fixtures/AlbumData.xml.erb')
24
+ erb = ERB.new(template)
25
+ FileUtils.mkdir('tmp') unless File.exists?('tmp')
26
+ File.open('tmp/AlbumData.xml', 'w+') do |f|
27
+ f << erb.result(binding)
28
+ end
29
+ end
30
+ after do
31
+ FileUtils.rm_rf TMP_OUTPUT_DIRECTORY
32
+ end
33
+
13
34
  describe '#export' do
14
35
  before do
15
36
  cli.export
16
37
  end
17
- it 'should run expected commands' do
18
- should meet_expectations
38
+ context 'with basic options' do
39
+ it 'creates folder for first event' do
40
+ expect(File.exists?('tmp/backup/Summer Party')).to be_true
41
+ end
42
+ it 'creates folder for second event' do
43
+ expect(File.exists?('tmp/backup/2013-10-10 Fall Supper')).to be_true
44
+ end
45
+ it 'copies images for first event' do
46
+ expect(Dir.glob('tmp/backup/Summer Party/*.jpg').length).to eq 2
47
+ end
48
+ it 'copies images for second event' do
49
+ expect(Dir.glob('tmp/backup/2013-10-10 Fall Supper/*.jpg').length).to eq 2
50
+ end
51
+ end
52
+ context 'when filter only matches first event' do
53
+ let(:options) {
54
+ {
55
+ config: 'tmp/AlbumData.xml',
56
+ output: TMP_OUTPUT_DIRECTORY,
57
+ filter: 'Summer'
58
+ }
59
+ }
60
+ it 'creates folder for first event' do
61
+ expect(File.exists?('tmp/backup/Summer Party')).to be_true
62
+ end
63
+ it 'does not createfolder for second event' do
64
+ expect(File.exists?('tmp/backup/2013-10-10 Fall Supper')).to be_false
65
+ end
66
+ end
67
+ context 'with --include-date-prefix option', focus: true do
68
+ let(:options) {
69
+ {
70
+ config: 'tmp/AlbumData.xml',
71
+ output: TMP_OUTPUT_DIRECTORY,
72
+ filter: '.*',
73
+ :'include-date-prefix' => true
74
+ }
75
+ }
76
+ it 'creates folder for first event and adds date prefix' do
77
+ expect(File.exists?('tmp/backup/2013-10-02 Summer Party')).to be_true
78
+ end
79
+ it 'creates folder for second event and uses existing date prefix' do
80
+ expect(File.exists?('tmp/backup/2013-10-10 Fall Supper')).to be_true
81
+ end
19
82
  end
20
83
  end
21
84
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iphoto_backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Sonnek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-28 00:00:00.000000000 Z
11
+ date: 2013-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -105,6 +105,7 @@ files:
105
105
  - .gitignore
106
106
  - .rspec
107
107
  - .ruby-version
108
+ - .travis.yml
108
109
  - Gemfile
109
110
  - LICENSE.txt
110
111
  - README.md
@@ -114,6 +115,9 @@ files:
114
115
  - lib/iphoto_backup.rb
115
116
  - lib/iphoto_backup/cli.rb
116
117
  - lib/iphoto_backup/version.rb
118
+ - spec/fixtures/AlbumData.xml.erb
119
+ - spec/fixtures/image.jpg
120
+ - spec/fixtures/image2.jpg
117
121
  - spec/lib/iphoto_backup/cli_spec.rb
118
122
  - spec/spec_helper.rb
119
123
  - spec/support/meet_expectations_matcher.rb
@@ -142,6 +146,9 @@ signing_key:
142
146
  specification_version: 4
143
147
  summary: copy files out of iPhoto into a backup directory
144
148
  test_files:
149
+ - spec/fixtures/AlbumData.xml.erb
150
+ - spec/fixtures/image.jpg
151
+ - spec/fixtures/image2.jpg
145
152
  - spec/lib/iphoto_backup/cli_spec.rb
146
153
  - spec/spec_helper.rb
147
154
  - spec/support/meet_expectations_matcher.rb