castaway 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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +123 -0
- data/bin/castaway +5 -0
- data/lib/castaway.rb +1 -0
- data/lib/castaway/animation.rb +37 -0
- data/lib/castaway/box.rb +77 -0
- data/lib/castaway/cli/build.rb +83 -0
- data/lib/castaway/cli/main.rb +24 -0
- data/lib/castaway/cli/script.rb +28 -0
- data/lib/castaway/cli/version.rb +17 -0
- data/lib/castaway/effect.rb +59 -0
- data/lib/castaway/element/base.rb +280 -0
- data/lib/castaway/element/matte.rb +20 -0
- data/lib/castaway/element/pointer.rb +35 -0
- data/lib/castaway/element/still.rb +31 -0
- data/lib/castaway/element/text.rb +76 -0
- data/lib/castaway/interpolation.rb +23 -0
- data/lib/castaway/interpolation/linear.rb +26 -0
- data/lib/castaway/point.rb +63 -0
- data/lib/castaway/production.rb +124 -0
- data/lib/castaway/production/audio.rb +161 -0
- data/lib/castaway/production/class_methods.rb +148 -0
- data/lib/castaway/production/scenes.rb +39 -0
- data/lib/castaway/range.rb +70 -0
- data/lib/castaway/relative_to.rb +18 -0
- data/lib/castaway/scene.rb +145 -0
- data/lib/castaway/size.rb +47 -0
- data/lib/castaway/timeline.rb +80 -0
- data/lib/castaway/times.rb +33 -0
- data/lib/castaway/version.rb +3 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a112c82c587c36cded9e5a12357b32afccda2b23
|
4
|
+
data.tar.gz: 9d309a240a441890b9cd8fc21d7385d96c28de67
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d8c360c1446343694d4ad4cf7b869dd60f338759d81e2d5101d4bae9ba7eebf393b83b4afc48565c7839d0a6341a7a15bf59f31660074ef02b8adc99ce7347b2
|
7
|
+
data.tar.gz: 6ee19bbf94daa6a12d7155c4219be4e96bb84d613df584d5c9d567b73a40153b05999beebec6c48db58a819bc2667d463c1f470493be786e122a9988b1d8b45c
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 Jamis Buck
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# Castaway
|
2
|
+
|
3
|
+
Want to create an online presentation or screencast, but are frustrated by
|
4
|
+
complicated interfaces or expensive tools? Maybe you're using one that _almost_
|
5
|
+
does what you need, but that one feature it's missing is a deal-breaker?
|
6
|
+
|
7
|
+
_Castaway_ to the rescue! Write your scripts, mix your audio, and render your
|
8
|
+
video, all via a simple-yet-powerful DSL.
|
9
|
+
|
10
|
+
Want to re-render your video with a different resolution or frame rate? No
|
11
|
+
problem--just run the script with different parameters.
|
12
|
+
|
13
|
+
Is that arrow pointing at the wrong point, or does that animation start at the
|
14
|
+
wrong time? Easy-peasy. Change the position or timing in your script, and rerun
|
15
|
+
it.
|
16
|
+
|
17
|
+
Screen-casting just got a whole lot easier.
|
18
|
+
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Castaway depends on a few external tools to do the heavy lifting. You'll need to
|
23
|
+
make sure you have the following tools installed:
|
24
|
+
|
25
|
+
* [ImageMagick](https://www.imagemagick.org/script/binary-releases.php) is used
|
26
|
+
to render video frames and special effects. _(Tested with version 6.9.5)_
|
27
|
+
* [Sox](https://sourceforge.net/projects/sox/files/sox/) is used to edit and mix
|
28
|
+
audio. _(Tested with version 14.4.2)_
|
29
|
+
* [FFmpeg](https://ffmpeg.org/download.html) is used to combine the frames and
|
30
|
+
audio into a single video file. _(Tested with version 3.2.2)_
|
31
|
+
|
32
|
+
Once you've met those requirements, installing Castaway itself is simple:
|
33
|
+
|
34
|
+
$ gem install castaway
|
35
|
+
|
36
|
+
And you're good to go!
|
37
|
+
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
_Watch a four-minute introduction to Castaway--created with Castaway!--on YouTube, here: [https://www.youtube.com/watch?v=F5ShAdLvVIk](https://www.youtube.com/watch?v=F5ShAdLvVIk) ._
|
42
|
+
|
43
|
+
Scripts are written in a DSL in Ruby. You declare scenes and sound clips,
|
44
|
+
and describe what comprises those scenes and sound clips. Here's a simple
|
45
|
+
example:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
soundclip :theme, resource('music.wav')
|
49
|
+
|
50
|
+
soundtrack do |clip|
|
51
|
+
clip.in soundclip(:theme)
|
52
|
+
# fade in the theme music
|
53
|
+
clip.chain.fade(5, type: :linear)
|
54
|
+
end
|
55
|
+
|
56
|
+
scene 'Title Screen' do
|
57
|
+
start '0:00'
|
58
|
+
script 'Hello, and welcome to our new screencast!'
|
59
|
+
|
60
|
+
plan do
|
61
|
+
# start with a black screen
|
62
|
+
matte(:black).
|
63
|
+
exit(1)
|
64
|
+
|
65
|
+
# dissolve-in our title screen
|
66
|
+
still('title.png').
|
67
|
+
enter(0.5).
|
68
|
+
in(:dissolve, speed: 0.5)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
finish '0:10'
|
73
|
+
```
|
74
|
+
|
75
|
+
This declares a sound track that fades in over five seconds, as well as a
|
76
|
+
single scene that displays a still frame, dissolved in at the 0.5 second mark.
|
77
|
+
The whole finishes at the ten second mark. If this were saved as `script.rb`,
|
78
|
+
you could generate the video like so:
|
79
|
+
|
80
|
+
$ castaway build script.rb
|
81
|
+
|
82
|
+
This will generate the frames, mix the audio, and compose the whole together
|
83
|
+
into a video called `script.mp4` (it uses the name of the script file as the
|
84
|
+
default for naming the video).
|
85
|
+
|
86
|
+
To name it something else:
|
87
|
+
|
88
|
+
$ castaway build -o movie.mp4 script.rb
|
89
|
+
|
90
|
+
By default, the video will be rendered at 540p (960x540 pixels). Change this
|
91
|
+
with the `--resolution` parameter:
|
92
|
+
|
93
|
+
$ castaway build --resolution 1080p script.rb
|
94
|
+
|
95
|
+
You can specify either HD-style resolutions (1080p, 540p, etc.) or WIDTHxHEIGHT
|
96
|
+
resolutions (e.g. 960x540).
|
97
|
+
|
98
|
+
Also by default, video will be rendered at NTSC-standard 29.97 frames/second.
|
99
|
+
To change the number of frames per second, use the `--fps` parameter:
|
100
|
+
|
101
|
+
$ castaway build --fps 10 script.rb
|
102
|
+
|
103
|
+
This can be useful for previewing a build quickly, before building the final
|
104
|
+
movie.
|
105
|
+
|
106
|
+
|
107
|
+
## Caveats
|
108
|
+
|
109
|
+
This is a work in progress, and will probably not do everything you need just
|
110
|
+
yet. Documentation and examples are severely lacking.
|
111
|
+
|
112
|
+
But stay tuned!
|
113
|
+
|
114
|
+
|
115
|
+
## Author
|
116
|
+
|
117
|
+
Castaway was written by Jamis Buck (jamis@jamisbuck.org).
|
118
|
+
|
119
|
+
|
120
|
+
## License
|
121
|
+
|
122
|
+
Castaway is distributed under the MIT license. (See the `MIT-LICENSE` file for
|
123
|
+
details).
|
data/bin/castaway
ADDED
data/lib/castaway.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'castaway/production'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'castaway/interpolation'
|
2
|
+
|
3
|
+
module Castaway
|
4
|
+
|
5
|
+
class Animation
|
6
|
+
attr_reader :from, :to, :duration
|
7
|
+
|
8
|
+
def self.from_options(options)
|
9
|
+
factory = Castaway::Interpolation.lookup(options)
|
10
|
+
initial = options[:initial] || 0.0
|
11
|
+
final = options[:final] || (initial + 1.0)
|
12
|
+
interpolator = factory.new(initial, final)
|
13
|
+
|
14
|
+
from = options[:from] ||
|
15
|
+
raise(ArgumentError, 'animations require `from` time')
|
16
|
+
to = options[:to] ||
|
17
|
+
(options[:length] ? from + options[:length] : nil) ||
|
18
|
+
raise(ArgumentError, 'animations require `to` or `length`')
|
19
|
+
|
20
|
+
new(interpolator, from, to)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(interpolator, from, to)
|
24
|
+
@interpolator = interpolator
|
25
|
+
@from = from.to_f
|
26
|
+
@to = to.to_f
|
27
|
+
@duration = @to - @from
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](t)
|
31
|
+
# adjust t to 0..1
|
32
|
+
adjusted_t = duration.zero? ? 1.0 : (t - from) / duration.to_f
|
33
|
+
@interpolator[adjusted_t]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/castaway/box.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'castaway/point'
|
2
|
+
|
3
|
+
module Castaway
|
4
|
+
class Box
|
5
|
+
def self.from_size(size)
|
6
|
+
new(
|
7
|
+
Point.new(0, 0),
|
8
|
+
Point.new(size.width - 1, 0),
|
9
|
+
Point.new(size.width - 1, size.height - 1),
|
10
|
+
Point.new(0, size.height - 1))
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(a, b, c, d, other = {})
|
14
|
+
@a = a
|
15
|
+
@b = b
|
16
|
+
@c = c
|
17
|
+
@d = d
|
18
|
+
@other_points = other.dup
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(id, point_of_interest)
|
22
|
+
@other_points[id] = point_of_interest
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](id)
|
27
|
+
@other_points[id]
|
28
|
+
end
|
29
|
+
|
30
|
+
def scale(factor)
|
31
|
+
a = @a * factor
|
32
|
+
b = @b * factor
|
33
|
+
c = @c * factor
|
34
|
+
d = @d * factor
|
35
|
+
|
36
|
+
others = @other_points.each_with_object({}) do |(k, v), h|
|
37
|
+
h[k] = v * factor
|
38
|
+
end
|
39
|
+
|
40
|
+
Box.new(a, b, c, d, others)
|
41
|
+
end
|
42
|
+
|
43
|
+
def rotate(degrees)
|
44
|
+
rads = degrees * Math::PI / 180.0
|
45
|
+
|
46
|
+
a = @a.rotate(rads)
|
47
|
+
b = @b.rotate(rads)
|
48
|
+
c = @c.rotate(rads)
|
49
|
+
d = @d.rotate(rads)
|
50
|
+
|
51
|
+
others = @other_points.each_with_object({}) do |(k, v), h|
|
52
|
+
h[k] = v.rotate(rads)
|
53
|
+
end
|
54
|
+
|
55
|
+
Box.new(a, b, c, d, others)
|
56
|
+
end
|
57
|
+
|
58
|
+
def bounds
|
59
|
+
x1, x2 = [ @a.x, @b.x, @c.x, @d.x ].minmax
|
60
|
+
y1, y2 = [ @a.y, @b.y, @c.y, @d.y ].minmax
|
61
|
+
|
62
|
+
# translate to origin
|
63
|
+
x2 -= x1
|
64
|
+
y2 -= y1
|
65
|
+
others = @other_points.each_with_object({}) do |(k, v), h|
|
66
|
+
h[k] = v.translate(-x1, -y1)
|
67
|
+
end
|
68
|
+
|
69
|
+
Box.new(Point.new(0, 0), Point.new(x2, 0),
|
70
|
+
Point.new(x2, y2), Point.new(0, y2), others)
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s
|
74
|
+
"#{@a}-#{@b}-#{@c}-#{@d}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'castaway/production'
|
2
|
+
require 'castaway/range'
|
3
|
+
|
4
|
+
module Castaway
|
5
|
+
module CLI
|
6
|
+
class Build
|
7
|
+
extend GLI::App
|
8
|
+
|
9
|
+
def self.description
|
10
|
+
'Builds the given castaway production'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.define(command)
|
14
|
+
command.desc 'The resolution at which to generate the frames'
|
15
|
+
command.flag %i(r resolution), default_value: '540p'
|
16
|
+
|
17
|
+
command.desc 'How many frames per second to generate'
|
18
|
+
command.flag %i(f fps), default_value: 29.97, type: Float
|
19
|
+
|
20
|
+
command.desc 'The frame from which to start producing frames'
|
21
|
+
command.flag %i(start-frame), default_value: 0, type: Integer
|
22
|
+
|
23
|
+
command.desc 'The frame after which to stop producing frames'
|
24
|
+
command.flag %i(end-frame), type: Integer
|
25
|
+
|
26
|
+
command.desc 'The scene from which to start producing frames'
|
27
|
+
command.flag %i(start-scene)
|
28
|
+
|
29
|
+
command.desc 'The scene after which to stop producing frames'
|
30
|
+
command.flag %i(end-scene)
|
31
|
+
|
32
|
+
command.desc 'The time from which to start producing frames'
|
33
|
+
command.flag %i(start-time)
|
34
|
+
|
35
|
+
command.desc 'The time after which to stop producing frames'
|
36
|
+
command.flag %i(end-time)
|
37
|
+
|
38
|
+
command.desc 'What to call the resulting movie'
|
39
|
+
command.flag %i(o output)
|
40
|
+
|
41
|
+
command.action do |_globals, options, args|
|
42
|
+
exit_now!('you have to supply a castaway program') if args.empty?
|
43
|
+
|
44
|
+
definition = Castaway::Production.from_script(args.first)
|
45
|
+
new(definition, args.first, options)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(definition, name, options)
|
50
|
+
deliverable = File.basename(name, File.extname(name)) + '.mp4'
|
51
|
+
|
52
|
+
production = definition.new(
|
53
|
+
resolution: options[:resolution],
|
54
|
+
fps: options[:fps],
|
55
|
+
deliverable: options[:output] || deliverable)
|
56
|
+
|
57
|
+
range = Castaway::Range.new(production)
|
58
|
+
|
59
|
+
if options['start-time']
|
60
|
+
range.start_time = options['start-time']
|
61
|
+
elsif options['start-scene']
|
62
|
+
range.start_scene = options['start-scene']
|
63
|
+
elsif options['start-frame']
|
64
|
+
range.start_frame = options['start-frame']
|
65
|
+
end
|
66
|
+
|
67
|
+
if options['end-time']
|
68
|
+
range.end_time = options['end-time']
|
69
|
+
elsif options['end-scene']
|
70
|
+
range.end_scene = options['end-scene']
|
71
|
+
elsif options['end-frame']
|
72
|
+
range.end_frame = options['end-frame']
|
73
|
+
end
|
74
|
+
|
75
|
+
production.produce(range)
|
76
|
+
rescue Exception => e
|
77
|
+
puts "#{e.class} (#{e.message})"
|
78
|
+
puts e.backtrace
|
79
|
+
abort
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'gli'
|
2
|
+
require 'castaway'
|
3
|
+
require 'castaway/cli/build'
|
4
|
+
require 'castaway/cli/script'
|
5
|
+
require 'castaway/cli/version'
|
6
|
+
|
7
|
+
module Castaway
|
8
|
+
module CLI
|
9
|
+
class Main
|
10
|
+
extend GLI::App
|
11
|
+
|
12
|
+
program_desc 'Build screencasts from a script'
|
13
|
+
|
14
|
+
desc Castaway::CLI::Script.description
|
15
|
+
command(:build) { |c| Castaway::CLI::Build.define(c) }
|
16
|
+
|
17
|
+
desc Castaway::CLI::Script.description
|
18
|
+
command(:script) { |c| Castaway::CLI::Script.define(c) }
|
19
|
+
|
20
|
+
desc Castaway::CLI::Version.description
|
21
|
+
command(:version) { |c| Castaway::CLI::Version.define(c) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'castaway/production'
|
2
|
+
|
3
|
+
module Castaway
|
4
|
+
module CLI
|
5
|
+
class Script
|
6
|
+
extend GLI::App
|
7
|
+
|
8
|
+
def self.description
|
9
|
+
'Display the given program as a script'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.define(command)
|
13
|
+
command.action do |_globals, _options, args|
|
14
|
+
exit_now!('you have to supply a castaway program') if args.empty?
|
15
|
+
|
16
|
+
production = Castaway::Production.from_script(args.first)
|
17
|
+
|
18
|
+
production.new.scenes.each.with_index do |scene, idx|
|
19
|
+
mark = scene.start || "##{idx}"
|
20
|
+
puts "[#{mark}] #{scene.title}"
|
21
|
+
puts scene.script if scene.script
|
22
|
+
puts
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'castaway/version'
|
2
|
+
|
3
|
+
module Castaway
|
4
|
+
module CLI
|
5
|
+
class Version
|
6
|
+
def self.description
|
7
|
+
"Report the current version (#{Castaway::VERSION})"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.define(command)
|
11
|
+
command.action do |_globals, _options, _args|
|
12
|
+
puts "v#{Castaway::VERSION}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Castaway
|
2
|
+
module Effect
|
3
|
+
|
4
|
+
@effects = {}
|
5
|
+
def self.register(name, &implementation)
|
6
|
+
@effects[name] = implementation
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.invoke(name, element, options)
|
10
|
+
@effects.fetch(name).call(element, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
register :dissolve_in do |element, options|
|
14
|
+
element.animate(:alpha,
|
15
|
+
from: 0.0, to: options[:speed],
|
16
|
+
initial: 0.0, final: 1.0)
|
17
|
+
end
|
18
|
+
|
19
|
+
register :dissolve_out do |element, options|
|
20
|
+
element.animate(:alpha,
|
21
|
+
from: element.tail(options[:speed]), to: element.tail,
|
22
|
+
initial: 1.0, final: 0.0)
|
23
|
+
end
|
24
|
+
|
25
|
+
register :pan do |element, options|
|
26
|
+
dx = case options[:horizontal]
|
27
|
+
when nil then 0
|
28
|
+
when true, 1, :right then
|
29
|
+
element.production.resolution.width - element.size.width
|
30
|
+
when false, -1, :left then
|
31
|
+
element.size.width - element.production.resolution.width
|
32
|
+
else
|
33
|
+
raise ArgumentError,
|
34
|
+
"unsupported horizontal: #{options[:horizontal].inspect}"
|
35
|
+
end
|
36
|
+
|
37
|
+
dy = case options[:vertical]
|
38
|
+
when nil then 0
|
39
|
+
when true, 1, :down then
|
40
|
+
element.production.resolution.height - element.size.height
|
41
|
+
when false, -1, :up then
|
42
|
+
element.size.height - element.production.resolution.height
|
43
|
+
else
|
44
|
+
raise ArgumentError,
|
45
|
+
"unsupported vertical: #{options[:vertical].inspect}"
|
46
|
+
end
|
47
|
+
|
48
|
+
type = options[:type] || :linear
|
49
|
+
from = options[:from] || 0.0
|
50
|
+
to = options[:to] || element.duration
|
51
|
+
|
52
|
+
p0 = Castaway::Point.new(0, 0)
|
53
|
+
p1 = Castaway::Point.new(dx, dy)
|
54
|
+
|
55
|
+
element.animate(:position, type: type, from: from, to: to,
|
56
|
+
initial: p0, final: p1)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|