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