rapinoe 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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +19 -0
- data/LICENSE.md +21 -0
- data/README.md +29 -0
- data/Rakefile +134 -0
- data/lib/rapinoe/keynote.rb +70 -0
- data/lib/rapinoe/slide.rb +45 -0
- data/lib/rapinoe.rb +12 -0
- data/rapinoe.gemspec +33 -0
- data/test/fixtures/ice-cream.key +0 -0
- data/test/test_keynote.rb +45 -0
- data/test/test_rapinoe.rb +12 -0
- data/test/test_slide.rb +26 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a7b0b8295ed07411999367b9d49c5f04afbfc62e
|
4
|
+
data.tar.gz: 2086de2bda02ddd0adca922539ba33a51e49c3ea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5a8b0fad50b816d783c76a50176e6bd9668d12511d49983065d130e8ea475fa8c5a638cab90c7dbf671120c596ba219e3ea08ab130947ca84d879dc6395e3433
|
7
|
+
data.tar.gz: e37ae7a82f002d55a31831cb2ebb9a4169c8f78c915894d84a1f7c1fdbd627605ac68bbc82a796221d01f1c488108aa2c61edc004f2b9f1f2b393357bda7bc30
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) Zach Holman, http://zachholman.com
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
# Rapinoe
|
4
|
+
|
5
|
+
Rapinoe helps you parse Apple Keynote files.
|
6
|
+
|
7
|
+
Primarily it's designed to help you access the simple metadata of a Keynote file: return how many slides there are, extract the slide previews baked into the file, tell you various details about the file itself on disk, and so on.
|
8
|
+
|
9
|
+
## Install
|
10
|
+
|
11
|
+
```sh
|
12
|
+
gem install rapinoe
|
13
|
+
```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
rapinoe = Rapinoe.new('talks/Literal Best Player Ever.key')
|
19
|
+
# => <Rapinoe::Keynote: @name="Literal Best Player Ever", @path="talks/Literal Best Player Ever.key", @data=[…]>
|
20
|
+
|
21
|
+
rapinoe.write_preview_to_file("my_dope_talk.jpg")
|
22
|
+
# => Writes the higher quality 1024px-width preview of your entire deck to a file
|
23
|
+
|
24
|
+
rapinoe.slides
|
25
|
+
# => [<Rapinoe::Slide>, <Rapinoe::Slide>, <Rapinoe::Slide>, <Rapinoe::Slide>]
|
26
|
+
|
27
|
+
rapinoe.slides.first.write_preview_to_file("/tmp/slide-preview.jpg")
|
28
|
+
# => writes the Keynote-generated slide preview to a file
|
29
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
test.libs << 'lib' << 'test' << '.'
|
51
|
+
test.pattern = 'test/**/test_*.rb'
|
52
|
+
test.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Open an irb session preloaded with this library"
|
56
|
+
task :console do
|
57
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
58
|
+
end
|
59
|
+
|
60
|
+
#############################################################################
|
61
|
+
#
|
62
|
+
# Custom tasks (add your own tasks here)
|
63
|
+
#
|
64
|
+
#############################################################################
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
#############################################################################
|
69
|
+
#
|
70
|
+
# Packaging tasks
|
71
|
+
#
|
72
|
+
#############################################################################
|
73
|
+
|
74
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
75
|
+
task :release => :build do
|
76
|
+
unless `git branch` =~ /^\* master$/
|
77
|
+
puts "You must be on the master branch to release!"
|
78
|
+
exit!
|
79
|
+
end
|
80
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
81
|
+
sh "git tag v#{version}"
|
82
|
+
sh "git push origin master"
|
83
|
+
sh "git push origin v#{version}"
|
84
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "Build #{gem_file} into the pkg directory"
|
88
|
+
task :build => :gemspec do
|
89
|
+
sh "mkdir -p pkg"
|
90
|
+
sh "gem build #{gemspec_file}"
|
91
|
+
sh "mv #{gem_file} pkg"
|
92
|
+
end
|
93
|
+
|
94
|
+
desc "Generate #{gemspec_file}"
|
95
|
+
task :gemspec => :validate do
|
96
|
+
# read spec file and split out manifest section
|
97
|
+
spec = File.read(gemspec_file)
|
98
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
99
|
+
|
100
|
+
# replace name version and date
|
101
|
+
replace_header(head, :name)
|
102
|
+
replace_header(head, :version)
|
103
|
+
replace_header(head, :date)
|
104
|
+
#comment this out if your rubyforge_project has a different name
|
105
|
+
replace_header(head, :rubyforge_project)
|
106
|
+
|
107
|
+
# determine file list from git ls-files
|
108
|
+
files = `git ls-files`.
|
109
|
+
split("\n").
|
110
|
+
sort.
|
111
|
+
reject { |file| file =~ /^\./ }.
|
112
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
113
|
+
map { |file| " #{file}" }.
|
114
|
+
join("\n")
|
115
|
+
|
116
|
+
# piece file back together and write
|
117
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
118
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
119
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
120
|
+
puts "Updated #{gemspec_file}"
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "Validate #{gemspec_file}"
|
124
|
+
task :validate do
|
125
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
126
|
+
unless libfiles.empty?
|
127
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
128
|
+
exit!
|
129
|
+
end
|
130
|
+
unless Dir['VERSION*'].empty?
|
131
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
132
|
+
exit!
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Rapinoe
|
2
|
+
class Keynote
|
3
|
+
# The path where we can find the .key file.
|
4
|
+
attr_accessor :path
|
5
|
+
|
6
|
+
# The name of the file.
|
7
|
+
attr_accessor :name
|
8
|
+
|
9
|
+
# The (first stage) of uncompressed Keynote data in memory.
|
10
|
+
attr_accessor :data
|
11
|
+
|
12
|
+
# Create a new Keynote instance.
|
13
|
+
#
|
14
|
+
# path - The path to the .key file on disk.
|
15
|
+
#
|
16
|
+
# Returns a Keynote.
|
17
|
+
def initialize(path)
|
18
|
+
@path = path
|
19
|
+
@name = File.basename(path, ".key")
|
20
|
+
extract_key_file
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the file size of the Keynote file in bytes.
|
24
|
+
def size
|
25
|
+
File.size(path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def slides
|
29
|
+
@data.glob("Data/st*").map do |preview_jpg_data|
|
30
|
+
Slide.new(preview_jpg_data.get_input_stream.read)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# The binary data associated with the .jpg Keynote writes to make a
|
35
|
+
# high(ish) quality preview version of the deck. You likely will want to
|
36
|
+
# access this via #write_preview_to_file, unless you have specific needs for
|
37
|
+
# the binary data.
|
38
|
+
#
|
39
|
+
# Returns the contents of preview.jpg.
|
40
|
+
def preview_data
|
41
|
+
@data.find_entry("preview.jpg").get_input_stream.read
|
42
|
+
end
|
43
|
+
|
44
|
+
# Writes Keynote's preview.jpg to disk somewhere.
|
45
|
+
#
|
46
|
+
# path - The path to the new file you want to write.
|
47
|
+
#
|
48
|
+
# Returns nothing.
|
49
|
+
def write_preview_to_file(path)
|
50
|
+
File.open(path, 'wb') do |out|
|
51
|
+
out.write(preview_data)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def inspect
|
56
|
+
"<Rapinoe::Keynote: @name=\"#{@name}\", @path=\"#{@path}\", @data=[…]>"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# .key files basically just try to masquerade as .zip files. Once we extract
|
62
|
+
# the main directory structure (in memory), we can start looking into what's
|
63
|
+
# inside.
|
64
|
+
#
|
65
|
+
# Returns a complex data structure that maps to Zip::File.
|
66
|
+
def extract_key_file
|
67
|
+
@data = Zip::File.open(path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Rapinoe
|
2
|
+
# `Slide` is a really vague approximation. Keynote stores its data in a
|
3
|
+
# zip-compressed file that has a bunch of .iwa files in it, which is where it
|
4
|
+
# stores its data. Each of those .iwa files is a file that's been compressed
|
5
|
+
# with a non-standard version of Google's Snappy compression format, and in
|
6
|
+
# turn the resulting data is stored in Google's Protobuf interchange format.
|
7
|
+
#
|
8
|
+
# I think we could probably get pretty deep into this, but I couldn't get the
|
9
|
+
# Ruby Snappy bindings to decompress the .iwa data (presumably due to the
|
10
|
+
# lacking Stream Identifier chunk). Might be something we look into later and
|
11
|
+
# see how many details we can glean from the full dataset.
|
12
|
+
#
|
13
|
+
# For more information, see @obriensp's excellent docs:
|
14
|
+
# https://github.com/obriensp/iWorkFileFormat/blob/master/Docs/index.md
|
15
|
+
#
|
16
|
+
# For now, we're going to instead look at the generated jpeg previews inside
|
17
|
+
# the `Data/` subdirectory. It's a nasty approximation, but it should map
|
18
|
+
# fairly well to the actual number of slides in the file.
|
19
|
+
class Slide
|
20
|
+
# The binary data representing the jpeg representation of this slide.
|
21
|
+
attr_accessor :data
|
22
|
+
|
23
|
+
def initialize(data)
|
24
|
+
@data = data
|
25
|
+
end
|
26
|
+
|
27
|
+
# The contents of the preview jpeg, loaded into memory.
|
28
|
+
#
|
29
|
+
# Returns a string of the jpeg's binary data.
|
30
|
+
def preview_data
|
31
|
+
@data
|
32
|
+
end
|
33
|
+
|
34
|
+
# Writes the preview for this slide to disk.
|
35
|
+
def write_preview_to_file(path)
|
36
|
+
File.open(path, 'wb') do |out|
|
37
|
+
out.write(preview_data)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
"<Rapinoe::Slide>"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/rapinoe.rb
ADDED
data/rapinoe.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'rapinoe'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.licenses = ['MIT']
|
5
|
+
s.summary = "Parse Keynote files in Ruby."
|
6
|
+
s.description = "Rapinoe is designed to parse the native files for Apple's Keynote presentation software. Its main focus is to help extract previews and general metadata about the presentation."
|
7
|
+
s.authors = ["Zach Holman"]
|
8
|
+
s.email = 'zach@zachholman.com'
|
9
|
+
s.files = ["lib/rapinoe.rb"]
|
10
|
+
s.homepage = 'https://github.com/holman/rapinoe'
|
11
|
+
s.require_paths = %w[lib]
|
12
|
+
s.add_runtime_dependency 'rubyzip', '~> 1.1', '>= 1.1.7'
|
13
|
+
|
14
|
+
# = MANIFEST =
|
15
|
+
s.files = %w[
|
16
|
+
Gemfile
|
17
|
+
Gemfile.lock
|
18
|
+
LICENSE.md
|
19
|
+
README.md
|
20
|
+
Rakefile
|
21
|
+
lib/rapinoe.rb
|
22
|
+
lib/rapinoe/keynote.rb
|
23
|
+
lib/rapinoe/slide.rb
|
24
|
+
rapinoe.gemspec
|
25
|
+
test/fixtures/ice-cream.key
|
26
|
+
test/test_keynote.rb
|
27
|
+
test/test_rapinoe.rb
|
28
|
+
test/test_slide.rb
|
29
|
+
]
|
30
|
+
# = MANIFEST =
|
31
|
+
|
32
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
33
|
+
end
|
Binary file
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'lib/rapinoe'
|
3
|
+
include Rapinoe
|
4
|
+
|
5
|
+
class TestKeynote < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@keynote = Keynote.new('test/fixtures/ice-cream.key')
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_path
|
11
|
+
assert_equal 'test/fixtures/ice-cream.key', @keynote.path
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_name
|
15
|
+
assert_equal "ice-cream", @keynote.name
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_size
|
19
|
+
assert_equal 327378, @keynote.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_slides
|
23
|
+
assert_kind_of Rapinoe::Slide, @keynote.slides.first
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_data
|
27
|
+
assert_not_nil @keynote.data
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_preview_data
|
31
|
+
assert_equal 32858, @keynote.preview_data.size
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_write_preview_to_file
|
35
|
+
tempfile = "/tmp/rapinoe-test.jpg"
|
36
|
+
|
37
|
+
refute File.exist?(tempfile)
|
38
|
+
@keynote.write_preview_to_file(tempfile)
|
39
|
+
|
40
|
+
assert File.exist?(tempfile)
|
41
|
+
assert_operator File.size(tempfile), :>, 32858
|
42
|
+
ensure
|
43
|
+
File.delete tempfile
|
44
|
+
end
|
45
|
+
end
|
data/test/test_slide.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'lib/rapinoe'
|
3
|
+
include Rapinoe
|
4
|
+
|
5
|
+
class TestSlide < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@keynote = Keynote.new('test/fixtures/ice-cream.key')
|
8
|
+
@slide = @keynote.slides.first
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_preview_data
|
12
|
+
assert_equal 1484, @slide.preview_data.size
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_write_preview_to_file
|
16
|
+
tempfile = "/tmp/rapinoe-slide-preview.jpg"
|
17
|
+
|
18
|
+
refute File.exist?(tempfile)
|
19
|
+
@slide.write_preview_to_file(tempfile)
|
20
|
+
|
21
|
+
assert File.exist?(tempfile)
|
22
|
+
assert_operator File.size(tempfile), :>, 1484
|
23
|
+
ensure
|
24
|
+
File.delete tempfile
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rapinoe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Zach Holman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rubyzip
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.1.7
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.1.7
|
33
|
+
description: Rapinoe is designed to parse the native files for Apple's Keynote presentation
|
34
|
+
software. Its main focus is to help extract previews and general metadata about
|
35
|
+
the presentation.
|
36
|
+
email: zach@zachholman.com
|
37
|
+
executables: []
|
38
|
+
extensions: []
|
39
|
+
extra_rdoc_files: []
|
40
|
+
files:
|
41
|
+
- Gemfile
|
42
|
+
- Gemfile.lock
|
43
|
+
- LICENSE.md
|
44
|
+
- README.md
|
45
|
+
- Rakefile
|
46
|
+
- lib/rapinoe.rb
|
47
|
+
- lib/rapinoe/keynote.rb
|
48
|
+
- lib/rapinoe/slide.rb
|
49
|
+
- rapinoe.gemspec
|
50
|
+
- test/fixtures/ice-cream.key
|
51
|
+
- test/test_keynote.rb
|
52
|
+
- test/test_rapinoe.rb
|
53
|
+
- test/test_slide.rb
|
54
|
+
homepage: https://github.com/holman/rapinoe
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 2.5.1
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: Parse Keynote files in Ruby.
|
78
|
+
test_files:
|
79
|
+
- test/test_keynote.rb
|
80
|
+
- test/test_rapinoe.rb
|
81
|
+
- test/test_slide.rb
|