1 /*
2
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 */
19 // ==============================================================
20 // Add a sound track to a QuickTime movie. Remember to use MoviePlayer to flatten
21 // the resulting movie.
22 // ==============================================================
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "quiktime.h"
29
30 #include <ImageCompression.h>
31 #include <Movies.h>
32 #include <QuickTimeComponents.h>
33 #include <Sound.h>
34
35 // Prototypes
36 void main(void);
37 void CheckError(OSErr error, Str255 displayString);
38 void SetInputSpecs(void);
39
40 void CreateMySoundTrack(Movie theMovie);
41 void CreateSoundDescription(Handle sndHandle, SoundDescriptionHandle sndDesc, long *sndDataOffset, long *numSamples,
42 long *sndDataSize);
43 long GetSndHdrOffset(Handle sndHandle);
44
45 #ifdef ADD_TEXT_TRACK
46 void MyCreateTextTrack(Movie theMovie);
47 #endif
48
49 // ---------------------------------------------------------------
50 // MAIN PROGRAM
51 // ---------------------------------------------------------------
52
main(void)53 void main(void) {
54 Ptr p;
55 long stupid;
56 OSErr err, result;
57
58 short sndResNum;
59
60 Point dlgPos = {120, 120};
61 StandardFileReply reply;
62 SFTypeList typeList;
63 FSSpec outSpec;
64 Str255 name = "QT Movie";
65 Rect r;
66 short resRefNum;
67 Movie gMovie = 0; // Our movie, track and media
68
69 printf("\n");
70
71 //---------------------
72 // Setup Quicktime stuff.
73 //---------------------
74 if (EnterMovies() != noErr) // Start up the movie tools
75 {
76 ParamText("Can't startup QuickTime.", "", "", "");
77 StopAlert(1000, nil);
78 return;
79 }
80
81 //----------------------
82 // Open the QuickTime movie.
83 //----------------------
84 printf("Open the QuickTime movie...\n\n");
85 typeList[0] = 'MooV';
86 StandardGetFilePreview(nil, 1, typeList, &reply);
87 if (!reply.sfGood) {
88 ExitMovies();
89 return;
90 }
91 err = OpenMovieFile(&reply.sfFile, &resRefNum, fsRdPerm);
92 if (err == noErr) {
93 short movieResID = 0; // get first movie
94 Str255 movieName;
95 Boolean wasChanged;
96
97 err = NewMovieFromFile(&gMovie, resRefNum, &movieResID, movieName, newMovieActive, &wasChanged);
98 CloseMovieFile(resRefNum);
99 } else
100 CheckError(1, "Can't open the movie file!!");
101
102 // Setup an FSSpec for the output file.
103 outSpec = reply.sfFile;
104 BlockMove("Output Movie", outSpec.name, 20);
105
106 //----------------------
107 // Open the input Sound file.
108 //----------------------
109 printf("Open the Sound file...\n\n");
110 typeList[0] = 'sfil';
111 StandardGetFilePreview(nil, 1, typeList, &reply);
112 if (!reply.sfGood) {
113 ExitMovies();
114 return;
115 }
116 sndResNum = FSpOpenResFile(&reply.sfFile, fsRdPerm);
117 if (sndResNum == -1)
118 CheckError(1, "Can't open the sound file!!");
119
120 ClearMoviesStickyError();
121
122 // Add the sound track here.
123 CreateMySoundTrack(gMovie);
124
125 // Save the new movie.
126 FlattenMovie(gMovie, 0, &outSpec, 'TVOD', smCurrentScript, createMovieFileDeleteCurFile, NULL, NULL);
127 CheckError(GetMoviesError(), "Couldn't save output movie.");
128
129 // Cleanup.
130 if (gMovie)
131 DisposeMovie(gMovie);
132 CloseResFile(sndResNum);
133 ExitMovies();
134 }
135
136 //------------------------------------------------------------------------
137 // Exit in case of an error.
138 //------------------------------------------------------------------------
CheckError(OSErr error,Str255 displayString)139 void CheckError(OSErr error, Str255 displayString) {
140 if (error == noErr)
141 return;
142 ParamText(displayString, "", "", "");
143 StopAlert(1000, nil);
144 ExitMovies();
145 ExitToShell();
146 }
147
148 //================================================================
149 // QuickTime Sound Track routines.
150 //================================================================
151
152 #define kSoundSampleDuration 1
153 #define kSyncSample 0
154 #define kTrackStart 0
155 #define kMediaStart 0
156 #define kFix1 0x00010000
157
158 //----------------------------------------------------------------
CreateMySoundTrack(Movie theMovie)159 void CreateMySoundTrack(Movie theMovie) {
160 Track theTrack;
161 Media theMedia;
162 Handle sndHandle = nil;
163 SoundDescriptionHandle sndDesc = nil;
164 long sndDataOffset;
165 long sndDataSize;
166 long numSamples;
167 OSErr err = noErr;
168
169 sndHandle = GetIndResource('snd ', 1);
170 CheckError(ResError(), "GetResource 'snd '");
171 if (sndHandle == nil)
172 return;
173
174 sndDesc = (SoundDescriptionHandle)NewHandle(4);
175 CheckError(MemError(), "NewHandle for SoundDesc");
176
177 CreateSoundDescription(sndHandle, sndDesc, &sndDataOffset, &numSamples, &sndDataSize);
178
179 theTrack = NewMovieTrack(theMovie, 0, 0, kFullVolume);
180 CheckError(GetMoviesError(), "New Sound Track");
181
182 theMedia = NewTrackMedia(theTrack, SoundMediaType, FixRound((**sndDesc).sampleRate), nil, 0);
183 CheckError(GetMoviesError(), "New Media snd.");
184
185 err = BeginMediaEdits(theMedia);
186 CheckError(err, "BeginMediaEdits snd.");
187
188 err = AddMediaSample(theMedia, sndHandle, sndDataOffset, sndDataSize, 1, (SampleDescriptionHandle)sndDesc, numSamples,
189 0, nil);
190 CheckError(err, "AddMediaSample snd.");
191
192 err = EndMediaEdits(theMedia);
193 CheckError(err, "EndMediaEdits snd.");
194
195 err = InsertMediaIntoTrack(theTrack, 0, 0, GetMediaDuration(theMedia), kFix1);
196 CheckError(err, "InsertMediaIntoTrack snd.");
197
198 if (sndDesc != nil)
199 DisposeHandle((Handle)sndDesc);
200 }
201
202 //----------------------------------------------------------------
CreateSoundDescription(Handle sndHandle,SoundDescriptionHandle sndDesc,long * sndDataOffset,long * numSamples,long * sndDataSize)203 void CreateSoundDescription(Handle sndHandle, SoundDescriptionHandle sndDesc, long *sndDataOffset, long *numSamples,
204 long *sndDataSize) {
205 long sndHdrOffset = 0;
206 long sampleDataOffset;
207 SoundHeaderPtr sndHdrPtr = nil;
208 long numFrames;
209 long samplesPerFrame;
210 long bytesPerFrame;
211 SignedByte sndHState;
212 SoundDescriptionPtr sndDescPtr;
213
214 *sndDataOffset = 0;
215 *numSamples = 0;
216 *sndDataSize = 0;
217
218 SetHandleSize((Handle)sndDesc, sizeof(SoundDescription));
219 CheckError(MemError(), "SetHandleSize for sndDesc.");
220
221 sndHdrOffset = GetSndHdrOffset(sndHandle);
222 if (sndHdrOffset == 0)
223 CheckError(-1, "GetSndHdrOffset ");
224
225 // we can use pointers since we don't move memory
226 sndHdrPtr = (SoundHeaderPtr)(*sndHandle + sndHdrOffset);
227 sndDescPtr = *sndDesc;
228 sndDescPtr->descSize = sizeof(SoundDescription); // total size of sound desc structure
229 sndDescPtr->resvd1 = 0;
230 sndDescPtr->resvd2 = 0;
231 sndDescPtr->dataRefIndex = 1;
232 sndDescPtr->compressionID = 0;
233 sndDescPtr->packetSize = 0;
234 sndDescPtr->version = 0;
235 sndDescPtr->revlevel = 0;
236 sndDescPtr->vendor = 0;
237
238 switch (sndHdrPtr->encode) {
239 case stdSH:
240 sndDescPtr->dataFormat = 'raw '; // uncompressed offset-binary data
241 sndDescPtr->numChannels = 1; // number of channels of sound
242 sndDescPtr->sampleSize = 8; // number of bits per sample
243 sndDescPtr->sampleRate = sndHdrPtr->sampleRate; // sample rate
244 *numSamples = sndHdrPtr->length;
245 *sndDataSize = *numSamples;
246 bytesPerFrame = 1;
247 samplesPerFrame = 1;
248 sampleDataOffset = (Ptr)&sndHdrPtr->sampleArea - (Ptr)sndHdrPtr;
249 break;
250
251 case extSH: {
252 ExtSoundHeaderPtr extSndHdrP = (ExtSoundHeaderPtr)sndHdrPtr;
253
254 sndDescPtr->dataFormat = 'raw '; // uncompressed offset-binary data
255 sndDescPtr->numChannels = extSndHdrP->numChannels; // number of channels of sound
256 sndDescPtr->sampleSize = extSndHdrP->sampleSize; // number of bits per sample
257 sndDescPtr->sampleRate = extSndHdrP->sampleRate; // sample rate
258 numFrames = extSndHdrP->numFrames;
259 *numSamples = numFrames;
260 bytesPerFrame = extSndHdrP->numChannels * (extSndHdrP->sampleSize / 8);
261 samplesPerFrame = 1;
262 *sndDataSize = numFrames * bytesPerFrame;
263 sampleDataOffset = (Ptr)(&extSndHdrP->sampleArea) - (Ptr)extSndHdrP;
264 } break;
265
266 default:
267 CheckError(-1, "Corrupt sound data or unsupported format.");
268 break;
269 }
270 *sndDataOffset = sndHdrOffset + sampleDataOffset;
271 }
272
273 //----------------------------------------------------------------
274 typedef SndCommand *SndCmdPtr;
275
276 typedef struct {
277 short format;
278 short numSynths;
279 } Snd1Header, *Snd1HdrPtr, **Snd1HdrHndl;
280
281 typedef struct {
282 short format;
283 short refCount;
284 } Snd2Header, *Snd2HdrPtr, **Snd2HdrHndl;
285 typedef struct {
286 short synthID;
287 long initOption;
288 } SynthInfo, *SynthInfoPtr;
289
GetSndHdrOffset(Handle sndHandle)290 long GetSndHdrOffset(Handle sndHandle) {
291 short howManyCmds;
292 long sndOffset = 0;
293 Ptr sndPtr;
294
295 if (sndHandle == nil)
296 return 0;
297 sndPtr = *sndHandle;
298 if (sndPtr == nil)
299 return 0;
300
301 if ((*(Snd1HdrPtr)sndPtr).format == firstSoundFormat) {
302 short synths = ((Snd1HdrPtr)sndPtr)->numSynths;
303 sndPtr += sizeof(Snd1Header) + (sizeof(SynthInfo) * synths);
304 } else {
305 sndPtr += sizeof(Snd2Header);
306 }
307
308 howManyCmds = *(short *)sndPtr;
309
310 sndPtr += sizeof(howManyCmds);
311
312 // sndPtr is now at the first sound command--cruise all
313 // commands and find the first soundCmd or bufferCmd
314
315 while (howManyCmds > 0) {
316 switch (((SndCmdPtr)sndPtr)->cmd) {
317 case (soundCmd + dataOffsetFlag):
318 case (bufferCmd + dataOffsetFlag):
319 sndOffset = ((SndCmdPtr)sndPtr)->param2;
320 howManyCmds = 0; // done, get out of loop
321 break;
322 default: // catch any other type of commands
323 sndPtr += sizeof(SndCommand);
324 howManyCmds--;
325 break;
326 }
327 }
328
329 return sndOffset;
330 }
331