rmov 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|