iphoto_backup 0.0.1 → 1.0.0

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.
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