rmov 0.1.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.
- data/CHANGELOG +3 -0
- data/LICENSE +20 -0
- data/Manifest +26 -0
- data/README +73 -0
- data/Rakefile +16 -0
- data/TODO +19 -0
- data/ext/exporter.c +200 -0
- data/ext/extconf.rb +4 -0
- data/ext/movie.c +435 -0
- data/ext/rmov_ext.c +14 -0
- data/ext/rmov_ext.h +41 -0
- data/ext/track.c +174 -0
- data/lib/quicktime/exporter.rb +10 -0
- data/lib/quicktime/movie.rb +57 -0
- data/lib/quicktime/track.rb +25 -0
- data/lib/rmov.rb +12 -0
- data/rmov.gemspec +101 -0
- data/spec/fixtures/settings.st +0 -0
- data/spec/output/example.pct +0 -0
- data/spec/output/saved_settings.st +0 -0
- data/spec/quicktime/exporter_spec.rb +29 -0
- data/spec/quicktime/movie_spec.rb +147 -0
- data/spec/quicktime/track_spec.rb +40 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/setup.rake +7 -0
- data/tasks/spec.rake +9 -0
- metadata +100 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Ryan Bates
|
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/Manifest
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
ext/exporter.c
|
3
|
+
ext/extconf.rb
|
4
|
+
ext/movie.c
|
5
|
+
ext/rmov_ext.c
|
6
|
+
ext/rmov_ext.h
|
7
|
+
ext/track.c
|
8
|
+
lib/quicktime/exporter.rb
|
9
|
+
lib/quicktime/movie.rb
|
10
|
+
lib/quicktime/track.rb
|
11
|
+
lib/rmov.rb
|
12
|
+
LICENSE
|
13
|
+
Rakefile
|
14
|
+
README
|
15
|
+
spec/fixtures/settings.st
|
16
|
+
spec/output/example.pct
|
17
|
+
spec/output/saved_settings.st
|
18
|
+
spec/quicktime/exporter_spec.rb
|
19
|
+
spec/quicktime/movie_spec.rb
|
20
|
+
spec/quicktime/track_spec.rb
|
21
|
+
spec/spec.opts
|
22
|
+
spec/spec_helper.rb
|
23
|
+
tasks/setup.rake
|
24
|
+
tasks/spec.rake
|
25
|
+
TODO
|
26
|
+
Manifest
|
data/README
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
= RMov
|
2
|
+
|
3
|
+
Open, edit, and export QuickTime movies all within Ruby! This is an
|
4
|
+
unofficial wrapper around Apple's QuickTime C API. Mac OS X required.
|
5
|
+
|
6
|
+
|
7
|
+
== Install
|
8
|
+
|
9
|
+
Install the gem:
|
10
|
+
|
11
|
+
gem install rmov
|
12
|
+
|
13
|
+
And then load it in your project:
|
14
|
+
|
15
|
+
require 'rmov'
|
16
|
+
|
17
|
+
|
18
|
+
== Usage
|
19
|
+
|
20
|
+
Use this gem to open QuickTime movies and edit them to your liking.
|
21
|
+
|
22
|
+
movie1 = QuickTime::Movie.open("path/to/movie.mov")
|
23
|
+
movie2 = QuickTime::Movie.open("path/to/another_movie.mov")
|
24
|
+
|
25
|
+
# add movie2 to the end of movie1
|
26
|
+
movie1.append_movie(movie2)
|
27
|
+
|
28
|
+
# make a new movie out of a section of movie 1
|
29
|
+
# this will delete 5 seconds out of the movie at 2 seconds in
|
30
|
+
movie3 = movie1.clip_section(movie1, 2, 5)
|
31
|
+
|
32
|
+
# You can insert that part back into the movie at 8 seconds in
|
33
|
+
movie1.insert_movie(movie3, 8)
|
34
|
+
|
35
|
+
Now you can export the movie. Usually this is done through a user
|
36
|
+
interface the first time around. The settings can then be saved to
|
37
|
+
a file. After that you can load these settings without interfering
|
38
|
+
the user with the dialog again.
|
39
|
+
|
40
|
+
exporter = movie1.exporter
|
41
|
+
|
42
|
+
# if we already have saved the settings, load those
|
43
|
+
if File.exist? "settings.st"
|
44
|
+
exporter.load_settings("settings.st")
|
45
|
+
else
|
46
|
+
# otherwise open the QuickTime GUI settings dialog
|
47
|
+
exporter.open_settings_dialog
|
48
|
+
|
49
|
+
# save settings to a file so we don't have to bother user next time
|
50
|
+
exporter.save_settings("settings.st")
|
51
|
+
end
|
52
|
+
|
53
|
+
# export the movie to a file and report the progress along the way
|
54
|
+
exporter.export("movie.mov") do |progress|
|
55
|
+
percent = (progress*100).round
|
56
|
+
puts "#{percent}% complete"
|
57
|
+
end
|
58
|
+
|
59
|
+
See Quicktime::Movie in the RDoc for more information.
|
60
|
+
|
61
|
+
http://rmov.rubyforge.org
|
62
|
+
|
63
|
+
|
64
|
+
== Development
|
65
|
+
|
66
|
+
This project can be found on github at the following URL.
|
67
|
+
|
68
|
+
http://github.com/ryanb/rmov
|
69
|
+
|
70
|
+
If you find a bug, please send me a message on GitHub.
|
71
|
+
|
72
|
+
If you would like to contribute to this project, please fork the
|
73
|
+
repository and send me a pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('rmov', '0.1.0') do |p|
|
6
|
+
p.summary = "Ruby wrapper for the QuickTime C API."
|
7
|
+
p.description = "Ruby wrapper for the QuickTime C API."
|
8
|
+
p.url = "http://github.com/ryanb/rmov"
|
9
|
+
p.author = 'Ryan Bates'
|
10
|
+
p.email = "ryan (at) railscasts (dot) com"
|
11
|
+
p.ignore_pattern = ["script/*", "tmp/*", "output/*", "**/*.o", "**/*.bundle", "**/*.mov"]
|
12
|
+
p.extensions = ["ext/extconf.rb"]
|
13
|
+
p.development_dependencies = []
|
14
|
+
end
|
15
|
+
|
16
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/TODO
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Fixes
|
2
|
+
- check if movie/track really exists each time we call it
|
3
|
+
- add rdocs
|
4
|
+
- fill README
|
5
|
+
|
6
|
+
Features
|
7
|
+
- export a single frame as pict
|
8
|
+
- resize movie
|
9
|
+
- export image sequence
|
10
|
+
- import image sequence
|
11
|
+
- import other file formats (mpeg, aiff, mp3, etc.)
|
12
|
+
- get movie metadata (album, author, artist, etc.)
|
13
|
+
- set movie metadata
|
14
|
+
- programatically adjust export settings (framerate, codec, etc.)
|
15
|
+
|
16
|
+
Possible
|
17
|
+
- add text track to movie
|
18
|
+
- add chapters to movie
|
19
|
+
- time remapping
|
data/ext/exporter.c
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
#include "rmov_ext.h"
|
2
|
+
|
3
|
+
VALUE cExporter;
|
4
|
+
|
5
|
+
static void exporter_free(struct RExporter *rExporter)
|
6
|
+
{
|
7
|
+
if (rExporter->settings) {
|
8
|
+
QTDisposeAtomContainer(rExporter->settings);
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
static void exporter_mark(struct RExporter *rExporter)
|
13
|
+
{
|
14
|
+
}
|
15
|
+
|
16
|
+
/*
|
17
|
+
Creates a new exporter instance. Usually this is done through movie.exporter.
|
18
|
+
|
19
|
+
call-seq:
|
20
|
+
new(movie) -> exporter
|
21
|
+
*/
|
22
|
+
static VALUE exporter_new(VALUE klass)
|
23
|
+
{
|
24
|
+
struct RExporter *rExporter;
|
25
|
+
return Data_Make_Struct(klass, struct RExporter, exporter_mark, exporter_free, rExporter);
|
26
|
+
}
|
27
|
+
|
28
|
+
static ComponentInstance exporter_component(VALUE obj)
|
29
|
+
{
|
30
|
+
ComponentInstance component = OpenDefaultComponent('spit', 'MooV');
|
31
|
+
if (REXPORTER(obj)->settings) {
|
32
|
+
MovieExportSetSettingsFromAtomContainer(component, REXPORTER(obj)->settings);
|
33
|
+
}
|
34
|
+
return component;
|
35
|
+
}
|
36
|
+
|
37
|
+
/*
|
38
|
+
Exports a movie to the given filepath. This will use either the
|
39
|
+
settings you set beforehand, or QuickTime's defaults.
|
40
|
+
|
41
|
+
You can track the progress of this operation by passing a block to this
|
42
|
+
method. It will be called regularly during the process and pass the
|
43
|
+
percentage complete (0.0 to 1.0) as an argument to the block.
|
44
|
+
|
45
|
+
call-seq:
|
46
|
+
export_to_file(filepath)
|
47
|
+
*/
|
48
|
+
static VALUE exporter_export_to_file(VALUE obj, VALUE filepath)
|
49
|
+
{
|
50
|
+
OSErr err;
|
51
|
+
FSSpec fs;
|
52
|
+
Movie movie = MOVIE(rb_iv_get(obj, "@movie"));
|
53
|
+
ComponentInstance component = exporter_component(obj);
|
54
|
+
|
55
|
+
if (rb_block_given_p())
|
56
|
+
SetMovieProgressProc(movie, (MovieProgressUPP)movie_progress_proc, rb_block_proc());
|
57
|
+
|
58
|
+
// Activate so QuickTime doesn't export a white frame
|
59
|
+
SetMovieActive(movie, TRUE);
|
60
|
+
|
61
|
+
err = NativePathNameToFSSpec(RSTRING(filepath)->ptr, &fs, 0);
|
62
|
+
if (err != fnfErr)
|
63
|
+
rb_raise(eQuicktime, "Error %d occurred while opening file for export at %s.", err, RSTRING(filepath)->ptr);
|
64
|
+
|
65
|
+
// TODO use exporter settings when converting movie
|
66
|
+
err = ConvertMovieToFile(movie, 0, &fs, 'MooV', 'TVOD', 0, 0, 0, component);
|
67
|
+
if (err != noErr)
|
68
|
+
rb_raise(eQuicktime, "Error %d occurred while attempting to export movie to file %s.", err, RSTRING(filepath)->ptr);
|
69
|
+
|
70
|
+
if (rb_block_given_p())
|
71
|
+
SetMovieProgressProc(movie, 0, 0);
|
72
|
+
|
73
|
+
CloseComponent(component);
|
74
|
+
|
75
|
+
return Qnil;
|
76
|
+
}
|
77
|
+
|
78
|
+
/*
|
79
|
+
Opens the offical QuickTime GUI settings dialog. The process will be
|
80
|
+
suspended until the user closes the dialogue. If the user clicks Okay
|
81
|
+
the settings will be applied to this Exporter. You can then use
|
82
|
+
save_settings to save them to a file, and load_settings to load them
|
83
|
+
back again.
|
84
|
+
|
85
|
+
call-seq:
|
86
|
+
open_settings_dialog()
|
87
|
+
*/
|
88
|
+
static VALUE exporter_open_settings_dialog(VALUE obj)
|
89
|
+
{
|
90
|
+
Boolean canceled;
|
91
|
+
OSErr err;
|
92
|
+
ProcessSerialNumber current_process = {0, kCurrentProcess};
|
93
|
+
Movie movie = MOVIE(rb_iv_get(obj, "@movie"));
|
94
|
+
ComponentInstance component = exporter_component(obj);
|
95
|
+
|
96
|
+
// Bring this process to the front
|
97
|
+
if (!IsProcessVisible(¤t_process)) {
|
98
|
+
err = TransformProcessType(¤t_process, kProcessTransformToForegroundApplication);
|
99
|
+
if (err != noErr) {
|
100
|
+
rb_raise(eQuicktime, "Error %d occurred while brining this application to the forground.", err);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
SetFrontProcess(¤t_process);
|
104
|
+
|
105
|
+
// Show export dialog and save settings
|
106
|
+
err = MovieExportDoUserDialog(component, movie, 0, 0, GetMovieDuration(movie), &canceled);
|
107
|
+
if (err != noErr) {
|
108
|
+
rb_raise(eQuicktime, "Error %d occurred while opening export dialog.", err);
|
109
|
+
}
|
110
|
+
|
111
|
+
if (!canceled) {
|
112
|
+
// Clear existing settings if there are any
|
113
|
+
if (REXPORTER(obj)->settings) {
|
114
|
+
QTDisposeAtomContainer(REXPORTER(obj)->settings);
|
115
|
+
}
|
116
|
+
MovieExportGetSettingsAsAtomContainer(component, &REXPORTER(obj)->settings);
|
117
|
+
}
|
118
|
+
|
119
|
+
CloseComponent(component);
|
120
|
+
|
121
|
+
if (canceled) {
|
122
|
+
return Qfalse;
|
123
|
+
} else {
|
124
|
+
return Qtrue;
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
/*
|
129
|
+
Loads the settings at the given filepath. See save_settings.
|
130
|
+
|
131
|
+
call-seq:
|
132
|
+
load_settings(filepath)
|
133
|
+
*/
|
134
|
+
static VALUE exporter_load_settings(VALUE obj, VALUE filepath)
|
135
|
+
{
|
136
|
+
FILE *file;
|
137
|
+
long length, read_length;
|
138
|
+
|
139
|
+
file = fopen(RSTRING(filepath)->ptr, "r+b");
|
140
|
+
if (!file) {
|
141
|
+
rb_raise(eQuicktime, "Unable to open file for loading at %s.", RSTRING(filepath)->ptr);
|
142
|
+
}
|
143
|
+
|
144
|
+
// obtain file size:
|
145
|
+
fseek(file , 0, SEEK_END);
|
146
|
+
length = ftell(file);
|
147
|
+
rewind(file);
|
148
|
+
|
149
|
+
// clear existing settings if there are any
|
150
|
+
if (REXPORTER(obj)->settings) {
|
151
|
+
QTDisposeAtomContainer(REXPORTER(obj)->settings);
|
152
|
+
}
|
153
|
+
|
154
|
+
// load the file into settings
|
155
|
+
REXPORTER(obj)->settings = (QTAtomContainer)NewHandleClear(length);
|
156
|
+
read_length = fread(*(Handle)REXPORTER(obj)->settings, 1, length, file);
|
157
|
+
if (read_length != length) {
|
158
|
+
rb_raise(eQuicktime, "Unable to read entire file at %s.", RSTRING(filepath)->ptr);
|
159
|
+
}
|
160
|
+
|
161
|
+
fclose(file);
|
162
|
+
|
163
|
+
return Qnil;
|
164
|
+
}
|
165
|
+
|
166
|
+
/*
|
167
|
+
Saves the settings to the given filepath (usually with .st extension).
|
168
|
+
See open_settings_dialog and load_settings.
|
169
|
+
|
170
|
+
call-seq:
|
171
|
+
save_settings(filepath)
|
172
|
+
*/
|
173
|
+
static VALUE exporter_save_settings(VALUE obj, VALUE filepath)
|
174
|
+
{
|
175
|
+
FILE *file;
|
176
|
+
QTAtomContainer settings = REXPORTER(obj)->settings;
|
177
|
+
|
178
|
+
if (!settings) {
|
179
|
+
rb_raise(eQuicktime, "Unable to save settings because no settings are specified.");
|
180
|
+
}
|
181
|
+
|
182
|
+
file = fopen(RSTRING(filepath)->ptr, "wb");
|
183
|
+
if (!file) {
|
184
|
+
rb_raise(eQuicktime, "Unable to open file for saving at %s.", RSTRING(filepath)->ptr);
|
185
|
+
}
|
186
|
+
fwrite(&settings, GetHandleSize((Handle)settings), 1, file);
|
187
|
+
fclose(file);
|
188
|
+
|
189
|
+
return Qnil;
|
190
|
+
}
|
191
|
+
|
192
|
+
void Init_quicktime_exporter()
|
193
|
+
{
|
194
|
+
cExporter = rb_define_class_under(mQuicktime, "Exporter", rb_cObject);
|
195
|
+
rb_define_alloc_func(cExporter, exporter_new);
|
196
|
+
rb_define_method(cExporter, "export", exporter_export_to_file, 1);
|
197
|
+
rb_define_method(cExporter, "open_settings_dialog", exporter_open_settings_dialog, 0);
|
198
|
+
rb_define_method(cExporter, "load_settings", exporter_load_settings, 1);
|
199
|
+
rb_define_method(cExporter, "save_settings", exporter_save_settings, 1);
|
200
|
+
}
|
data/ext/extconf.rb
ADDED
data/ext/movie.c
ADDED
@@ -0,0 +1,435 @@
|
|
1
|
+
#include "rmov_ext.h"
|
2
|
+
|
3
|
+
VALUE cMovie;
|
4
|
+
|
5
|
+
OSErr movie_progress_proc(Movie movie, short message, short operation, Fixed percent, VALUE proc)
|
6
|
+
{
|
7
|
+
rb_funcall(proc, rb_intern("call"), 1, rb_float_new(FixedToFloat(percent)));
|
8
|
+
return 0;
|
9
|
+
}
|
10
|
+
|
11
|
+
static void movie_free(struct RMovie *rMovie)
|
12
|
+
{
|
13
|
+
if (rMovie->movie) {
|
14
|
+
DisposeMovie(rMovie->movie);
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
static void movie_mark(struct RMovie *rMovie)
|
19
|
+
{
|
20
|
+
}
|
21
|
+
|
22
|
+
/*
|
23
|
+
Creates a new movie instance. Generally you want to go through
|
24
|
+
Movie.open or Movie.empty to load or create a new movie respectively.
|
25
|
+
If you do no then you will need to load the movie with load_empty or
|
26
|
+
load_from_file before you can accomplish anything.
|
27
|
+
|
28
|
+
call-seq:
|
29
|
+
new() -> movie
|
30
|
+
*/
|
31
|
+
static VALUE movie_new(VALUE klass)
|
32
|
+
{
|
33
|
+
struct RMovie *rMovie;
|
34
|
+
return Data_Make_Struct(klass, struct RMovie, movie_mark, movie_free, rMovie);
|
35
|
+
}
|
36
|
+
|
37
|
+
/*
|
38
|
+
Dispose of the loaded QuickTime movie. This will automatically be done
|
39
|
+
when this movie instance is garbage collected. However if you are
|
40
|
+
iterating through many movies it is often helpful to dispose of it
|
41
|
+
as soon as you're done with it.
|
42
|
+
|
43
|
+
call-seq:
|
44
|
+
dispose()
|
45
|
+
*/
|
46
|
+
static VALUE movie_dispose(VALUE obj)
|
47
|
+
{
|
48
|
+
if (MOVIE(obj)) {
|
49
|
+
DisposeMovie(MOVIE(obj));
|
50
|
+
RMOVIE(obj)->movie = NULL;
|
51
|
+
}
|
52
|
+
return obj;
|
53
|
+
}
|
54
|
+
|
55
|
+
/*
|
56
|
+
Loads a new, empty QuickTime movie at given filepath. Should only be
|
57
|
+
called if no movie has been loaded (or it has been disposed). Usually
|
58
|
+
you go through Movie.open.
|
59
|
+
|
60
|
+
call-seq:
|
61
|
+
load_from_file(filepath)
|
62
|
+
*/
|
63
|
+
static VALUE movie_load_from_file(VALUE obj, VALUE filepath)
|
64
|
+
{
|
65
|
+
if (MOVIE(obj)) {
|
66
|
+
rb_raise(eQuicktime, "Movie has already been loaded.");
|
67
|
+
} else {
|
68
|
+
OSErr err;
|
69
|
+
FSSpec fs;
|
70
|
+
short frefnum = -1;
|
71
|
+
short movie_resid = 0;
|
72
|
+
Movie *movie = ALLOC(Movie);
|
73
|
+
|
74
|
+
err = NativePathNameToFSSpec(RSTRING(filepath)->ptr, &fs, 0);
|
75
|
+
if (err != 0)
|
76
|
+
rb_raise(eQuicktime, "Error %d occurred while reading file at %s", err, RSTRING(filepath)->ptr);
|
77
|
+
|
78
|
+
err = OpenMovieFile(&fs, &frefnum, fsRdPerm);
|
79
|
+
if (err != 0)
|
80
|
+
rb_raise(eQuicktime, "Error %d occurred while opening movie at %s", err, RSTRING(filepath)->ptr);
|
81
|
+
|
82
|
+
err = NewMovieFromFile(movie, frefnum, &movie_resid, 0, newMovieActive, 0);
|
83
|
+
if (err != 0)
|
84
|
+
rb_raise(eQuicktime, "Error %d occurred while loading movie at %s", err, RSTRING(filepath)->ptr);
|
85
|
+
|
86
|
+
err = CloseMovieFile(frefnum);
|
87
|
+
if (err != 0)
|
88
|
+
rb_raise(eQuicktime, "Error %d occurred while closing movie file at %s", err, RSTRING(filepath)->ptr);
|
89
|
+
|
90
|
+
RMOVIE(obj)->movie = *movie;
|
91
|
+
|
92
|
+
return obj;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
/*
|
97
|
+
Loads a new, empty QuickTime movie. Should only be called if no movie
|
98
|
+
has been loaded (or it has been disposed). Usually you go through
|
99
|
+
Movie.empty.
|
100
|
+
|
101
|
+
call-seq:
|
102
|
+
load_empty()
|
103
|
+
*/
|
104
|
+
static VALUE movie_load_empty(VALUE obj)
|
105
|
+
{
|
106
|
+
if (MOVIE(obj)) {
|
107
|
+
rb_raise(eQuicktime, "Movie has already been loaded.");
|
108
|
+
} else {
|
109
|
+
RMOVIE(obj)->movie = NewMovie(0);
|
110
|
+
return obj;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
/*
|
115
|
+
Returns the raw duration of the movie. Combine this with time_scale to
|
116
|
+
reach the duration in seconds.
|
117
|
+
|
118
|
+
call-seq:
|
119
|
+
raw_duration() -> duration_int
|
120
|
+
*/
|
121
|
+
static VALUE movie_raw_duration(VALUE obj)
|
122
|
+
{
|
123
|
+
return INT2NUM(GetMovieDuration(MOVIE(obj)));
|
124
|
+
}
|
125
|
+
|
126
|
+
/*
|
127
|
+
Returns the time scale of the movie. Usually only needed when working
|
128
|
+
with raw_duration.
|
129
|
+
|
130
|
+
call-seq:
|
131
|
+
time_scale() -> scale_int
|
132
|
+
*/
|
133
|
+
static VALUE movie_time_scale(VALUE obj)
|
134
|
+
{
|
135
|
+
return INT2NUM(GetMovieTimeScale(MOVIE(obj)));
|
136
|
+
}
|
137
|
+
|
138
|
+
/*
|
139
|
+
Returns the number of tracks in the movie.
|
140
|
+
|
141
|
+
call-seq:
|
142
|
+
track_count() -> count
|
143
|
+
*/
|
144
|
+
static VALUE movie_bounds(VALUE obj)
|
145
|
+
{
|
146
|
+
VALUE bounds_hash = rb_hash_new();
|
147
|
+
Rect bounds;
|
148
|
+
GetMovieBox(MOVIE(obj), &bounds);
|
149
|
+
rb_hash_aset(bounds_hash, ID2SYM(rb_intern("left")), INT2NUM(bounds.left));
|
150
|
+
rb_hash_aset(bounds_hash, ID2SYM(rb_intern("top")), INT2NUM(bounds.top));
|
151
|
+
rb_hash_aset(bounds_hash, ID2SYM(rb_intern("right")), INT2NUM(bounds.right));
|
152
|
+
rb_hash_aset(bounds_hash, ID2SYM(rb_intern("bottom")), INT2NUM(bounds.bottom));
|
153
|
+
return bounds_hash;
|
154
|
+
}
|
155
|
+
|
156
|
+
/*
|
157
|
+
Returns the number of tracks in the movie.
|
158
|
+
|
159
|
+
call-seq:
|
160
|
+
track_count -> count
|
161
|
+
*/
|
162
|
+
static VALUE movie_track_count(VALUE obj)
|
163
|
+
{
|
164
|
+
return INT2NUM(GetMovieTrackCount(MOVIE(obj)));
|
165
|
+
}
|
166
|
+
|
167
|
+
/*
|
168
|
+
Adds the tracks of given movie into called movie at given position (in seconds).
|
169
|
+
|
170
|
+
You can track the progress of this operation by passing a block to this
|
171
|
+
method. It will be called regularly during the process and pass the
|
172
|
+
percentage complete (0.0 to 1.0) as an argument to the block.
|
173
|
+
|
174
|
+
call-seq:
|
175
|
+
composite_movie(movie, position)
|
176
|
+
*/
|
177
|
+
static VALUE movie_composite_movie(VALUE obj, VALUE src, VALUE position)
|
178
|
+
{
|
179
|
+
if (rb_block_given_p())
|
180
|
+
SetMovieProgressProc(MOVIE(obj), (MovieProgressUPP)movie_progress_proc, rb_block_proc());
|
181
|
+
|
182
|
+
SetMovieSelection(MOVIE(obj), MOVIE_TIME(obj, position), 0);
|
183
|
+
AddMovieSelection(MOVIE(obj), MOVIE(src));
|
184
|
+
|
185
|
+
if (rb_block_given_p())
|
186
|
+
SetMovieProgressProc(MOVIE(obj), 0, 0);
|
187
|
+
|
188
|
+
return obj;
|
189
|
+
}
|
190
|
+
|
191
|
+
/*
|
192
|
+
Inserts given movie into called movie at given position (in seconds).
|
193
|
+
|
194
|
+
You can track the progress of this operation by passing a block to this
|
195
|
+
method. It will be called regularly during the process and pass the
|
196
|
+
percentage complete (0.0 to 1.0) as an argument to the block.
|
197
|
+
|
198
|
+
call-seq:
|
199
|
+
append_movie(movie, position)
|
200
|
+
*/
|
201
|
+
static VALUE movie_insert_movie(VALUE obj, VALUE src, VALUE position)
|
202
|
+
{
|
203
|
+
if (rb_block_given_p())
|
204
|
+
SetMovieProgressProc(MOVIE(obj), (MovieProgressUPP)movie_progress_proc, rb_block_proc());
|
205
|
+
|
206
|
+
SetMovieSelection(MOVIE(obj), MOVIE_TIME(obj, position), 0);
|
207
|
+
PasteMovieSelection(MOVIE(obj), MOVIE(src));
|
208
|
+
|
209
|
+
if (rb_block_given_p())
|
210
|
+
SetMovieProgressProc(MOVIE(obj), 0, 0);
|
211
|
+
|
212
|
+
return obj;
|
213
|
+
}
|
214
|
+
|
215
|
+
/*
|
216
|
+
Adds given movie to the end of movie which this method is called on.
|
217
|
+
|
218
|
+
You can track the progress of this operation by passing a block to this
|
219
|
+
method. It will be called regularly during the process and pass the
|
220
|
+
percentage complete (0.0 to 1.0) as an argument to the block.
|
221
|
+
|
222
|
+
call-seq:
|
223
|
+
append_movie(movie)
|
224
|
+
*/
|
225
|
+
static VALUE movie_append_movie(VALUE obj, VALUE src)
|
226
|
+
{
|
227
|
+
if (rb_block_given_p())
|
228
|
+
SetMovieProgressProc(MOVIE(obj), (MovieProgressUPP)movie_progress_proc, rb_block_proc());
|
229
|
+
|
230
|
+
SetMovieSelection(MOVIE(obj), GetMovieDuration(MOVIE(obj)), 0);
|
231
|
+
PasteMovieSelection(MOVIE(obj), MOVIE(src));
|
232
|
+
|
233
|
+
if (rb_block_given_p())
|
234
|
+
SetMovieProgressProc(MOVIE(obj), 0, 0);
|
235
|
+
|
236
|
+
return obj;
|
237
|
+
}
|
238
|
+
|
239
|
+
/*
|
240
|
+
Deletes given section from movie. Both start_time and duration
|
241
|
+
should be floats representing seconds.
|
242
|
+
|
243
|
+
call-seq:
|
244
|
+
delete_section(start_time, duration)
|
245
|
+
*/
|
246
|
+
static VALUE movie_delete_section(VALUE obj, VALUE start, VALUE duration)
|
247
|
+
{
|
248
|
+
SetMovieSelection(MOVIE(obj), MOVIE_TIME(obj, start), MOVIE_TIME(obj, duration));
|
249
|
+
ClearMovieSelection(MOVIE(obj));
|
250
|
+
return obj;
|
251
|
+
}
|
252
|
+
|
253
|
+
/*
|
254
|
+
Returns a new movie in the given section. Does not modify original
|
255
|
+
movie. Both start_time and duration should be floats representing
|
256
|
+
seconds.
|
257
|
+
|
258
|
+
You can track the progress of this operation by passing a block to this
|
259
|
+
method. It will be called regularly during the process and pass the
|
260
|
+
percentage complete (0.0 to 1.0) as an argument to the block.
|
261
|
+
|
262
|
+
call-seq:
|
263
|
+
clone_section(start_time, duration) -> movie
|
264
|
+
*/
|
265
|
+
static VALUE movie_clone_section(VALUE obj, VALUE start, VALUE duration)
|
266
|
+
{
|
267
|
+
VALUE new_movie_obj = rb_obj_alloc(cMovie);
|
268
|
+
|
269
|
+
if (rb_block_given_p())
|
270
|
+
SetMovieProgressProc(MOVIE(obj), (MovieProgressUPP)movie_progress_proc, rb_block_proc());
|
271
|
+
|
272
|
+
SetMovieSelection(MOVIE(obj), MOVIE_TIME(obj, start), MOVIE_TIME(obj, duration));
|
273
|
+
RMOVIE(new_movie_obj)->movie = CopyMovieSelection(MOVIE(obj));
|
274
|
+
|
275
|
+
if (rb_block_given_p())
|
276
|
+
SetMovieProgressProc(MOVIE(obj), 0, 0);
|
277
|
+
|
278
|
+
return new_movie_obj;
|
279
|
+
}
|
280
|
+
|
281
|
+
/*
|
282
|
+
Deletes given section on movie and returns a new movie with that
|
283
|
+
section. Both start_time and duration should be floats representing
|
284
|
+
seconds.
|
285
|
+
|
286
|
+
You can track the progress of this operation by passing a block to this
|
287
|
+
method. It will be called regularly during the process and pass the
|
288
|
+
percentage complete (0.0 to 1.0) as an argument to the block.
|
289
|
+
|
290
|
+
call-seq:
|
291
|
+
clip_section(start_time, duration) -> movie
|
292
|
+
*/
|
293
|
+
static VALUE movie_clip_section(VALUE obj, VALUE start, VALUE duration)
|
294
|
+
{
|
295
|
+
VALUE new_movie_obj = rb_obj_alloc(cMovie);
|
296
|
+
|
297
|
+
if (rb_block_given_p())
|
298
|
+
SetMovieProgressProc(MOVIE(obj), (MovieProgressUPP)movie_progress_proc, rb_block_proc());
|
299
|
+
|
300
|
+
SetMovieSelection(MOVIE(obj), MOVIE_TIME(obj, start), MOVIE_TIME(obj, duration));
|
301
|
+
RMOVIE(new_movie_obj)->movie = CutMovieSelection(MOVIE(obj));
|
302
|
+
|
303
|
+
if (rb_block_given_p())
|
304
|
+
SetMovieProgressProc(MOVIE(obj), 0, 0);
|
305
|
+
|
306
|
+
return new_movie_obj;
|
307
|
+
}
|
308
|
+
|
309
|
+
/*
|
310
|
+
Determine if a movie has changed since opening. Returns true/false.
|
311
|
+
See reset_changed_status to reset this value.
|
312
|
+
|
313
|
+
call-seq:
|
314
|
+
changed?() -> bool
|
315
|
+
*/
|
316
|
+
static VALUE movie_changed(VALUE obj)
|
317
|
+
{
|
318
|
+
if (HasMovieChanged(MOVIE(obj))) {
|
319
|
+
return Qtrue;
|
320
|
+
} else {
|
321
|
+
return Qfalse;
|
322
|
+
}
|
323
|
+
}
|
324
|
+
|
325
|
+
/*
|
326
|
+
Resets the "changed?" status. Does not revert the movie itself.
|
327
|
+
|
328
|
+
call-seq:
|
329
|
+
clear_changed_status()
|
330
|
+
*/
|
331
|
+
static VALUE movie_clear_changed_status(VALUE obj)
|
332
|
+
{
|
333
|
+
ClearMovieChanged(MOVIE(obj));
|
334
|
+
return Qnil;
|
335
|
+
}
|
336
|
+
|
337
|
+
|
338
|
+
/*
|
339
|
+
Saves the movie to the given filepath by flattening it.
|
340
|
+
|
341
|
+
call-seq:
|
342
|
+
flatten(filepath)
|
343
|
+
*/
|
344
|
+
|
345
|
+
static VALUE movie_flatten(VALUE obj, VALUE filepath)
|
346
|
+
{
|
347
|
+
OSErr err;
|
348
|
+
FSSpec fs;
|
349
|
+
VALUE new_movie_obj = rb_obj_alloc(cMovie);
|
350
|
+
|
351
|
+
err = NativePathNameToFSSpec(RSTRING(filepath)->ptr, &fs, 0);
|
352
|
+
if (err != fnfErr)
|
353
|
+
rb_raise(eQuicktime, "Error %d occurred while opening file for export at %s", err, RSTRING(filepath)->ptr);
|
354
|
+
|
355
|
+
// TODO make these flags settable through an options hash
|
356
|
+
RMOVIE(new_movie_obj)->movie = FlattenMovieData(MOVIE(obj),
|
357
|
+
flattenDontInterleaveFlatten
|
358
|
+
| flattenCompressMovieResource
|
359
|
+
| flattenAddMovieToDataFork
|
360
|
+
| flattenForceMovieResourceBeforeMovieData,
|
361
|
+
&fs, 'TVOD', smSystemScript, createMovieFileDontCreateResFile);
|
362
|
+
return new_movie_obj;
|
363
|
+
}
|
364
|
+
|
365
|
+
/*
|
366
|
+
Exports a PICT file to given filepath (should end in .pct) at the given
|
367
|
+
time. Time should be a floating point in seconds.
|
368
|
+
|
369
|
+
call-seq:
|
370
|
+
export_pict(filepath, time)
|
371
|
+
|
372
|
+
*/
|
373
|
+
|
374
|
+
static VALUE movie_export_pict(VALUE obj, VALUE filepath, VALUE frame_time)
|
375
|
+
{
|
376
|
+
GraphicsImportComponent component;
|
377
|
+
PicHandle picture;
|
378
|
+
Handle handle;
|
379
|
+
FSSpec fs;
|
380
|
+
OSErr err;
|
381
|
+
|
382
|
+
picture = GetMoviePict(MOVIE(obj), MOVIE_TIME(obj, frame_time));
|
383
|
+
|
384
|
+
err = NativePathNameToFSSpec(RSTRING(filepath)->ptr, &fs, 0);
|
385
|
+
if (err != fnfErr)
|
386
|
+
rb_raise(eQuicktime, "Error %d occurred while opening file for export at %s.", err, RSTRING(filepath)->ptr);
|
387
|
+
|
388
|
+
// Convert the picture handle into a PICT file (still in a handle)
|
389
|
+
// by adding a 512-byte header to the start.
|
390
|
+
handle = NewHandleClear(512);
|
391
|
+
err = HandAndHand((Handle)picture, handle);
|
392
|
+
if (err != noErr)
|
393
|
+
rb_raise(eQuicktime, "Error %d occurred while converting handle for pict export %s.", err, RSTRING(filepath)->ptr);
|
394
|
+
|
395
|
+
err = OpenADefaultComponent(GraphicsImporterComponentType, kQTFileTypePicture, &component);
|
396
|
+
if (err != noErr)
|
397
|
+
rb_raise(eQuicktime, "Error %d occurred while opening picture component for %s.", err, RSTRING(filepath)->ptr);
|
398
|
+
|
399
|
+
err = GraphicsImportSetDataHandle(component, handle);
|
400
|
+
if (err != noErr)
|
401
|
+
rb_raise(eQuicktime, "Error %d occurred while setting graphics importer data handle for %s.", err, RSTRING(filepath)->ptr);
|
402
|
+
|
403
|
+
err = GraphicsImportExportImageFile(component, 0, 0, &fs, smSystemScript);
|
404
|
+
if (err != noErr)
|
405
|
+
rb_raise(eQuicktime, "Error %d occurred while exporting pict to file %s.", err, RSTRING(filepath)->ptr);
|
406
|
+
|
407
|
+
CloseComponent(component);
|
408
|
+
DisposeHandle(handle);
|
409
|
+
DisposeHandle((Handle)picture);
|
410
|
+
|
411
|
+
return Qnil;
|
412
|
+
}
|
413
|
+
|
414
|
+
void Init_quicktime_movie()
|
415
|
+
{
|
416
|
+
cMovie = rb_define_class_under(mQuicktime, "Movie", rb_cObject);
|
417
|
+
rb_define_alloc_func(cMovie, movie_new);
|
418
|
+
rb_define_method(cMovie, "load_from_file", movie_load_from_file, 1);
|
419
|
+
rb_define_method(cMovie, "load_empty", movie_load_empty, 0);
|
420
|
+
rb_define_method(cMovie, "raw_duration", movie_raw_duration, 0);
|
421
|
+
rb_define_method(cMovie, "time_scale", movie_time_scale, 0);
|
422
|
+
rb_define_method(cMovie, "bounds", movie_bounds, 0);
|
423
|
+
rb_define_method(cMovie, "track_count", movie_track_count, 0);
|
424
|
+
rb_define_method(cMovie, "composite_movie", movie_composite_movie, 2);
|
425
|
+
rb_define_method(cMovie, "insert_movie", movie_insert_movie, 2);
|
426
|
+
rb_define_method(cMovie, "append_movie", movie_append_movie, 1);
|
427
|
+
rb_define_method(cMovie, "delete_section", movie_delete_section, 2);
|
428
|
+
rb_define_method(cMovie, "clone_section", movie_clone_section, 2);
|
429
|
+
rb_define_method(cMovie, "clip_section", movie_clip_section, 2);
|
430
|
+
rb_define_method(cMovie, "changed?", movie_changed, 0);
|
431
|
+
rb_define_method(cMovie, "clear_changed_status", movie_clear_changed_status, 0);
|
432
|
+
rb_define_method(cMovie, "flatten", movie_flatten, 1);
|
433
|
+
rb_define_method(cMovie, "export_pict", movie_export_pict, 2);
|
434
|
+
rb_define_method(cMovie, "dispose", movie_dispose, 0);
|
435
|
+
}
|