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 //	 Convert a raw QT movie into a nice compressed movie.  Set the movie's color table.
21 //	==============================================================
22 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "2d.h"
27 #include "fix.h"
28 #include "lg.h"
29 #include "quiktime.h"
30 #include "rect.h"
31 
32 #include "InitMac.h"
33 #include "ShockBitmap.h"
34 extern Ptr gScreenAddress;
35 extern long gScreenRowbytes;
36 
37 #include <ImageCompression.h>
38 #include <Movies.h>
39 #include <QuickTimeComponents.h>
40 #include <Sound.h>
41 
42 //  Globals
43 ComponentInstance ci = nil;
44 WindowPtr gMainWindow;
45 extern short gMainVRef;
46 short gResNum;
47 ulong *gSampleTimes;
48 ulong *gChunkOffsets;
49 long gNumFrames;
50 long gFakeIndex;
51 QT_ChunkInfo chunkInfo[] = {QT_CLIP, FALSE, QT_CRGN, TRUE,  QT_DINF, FALSE, QT_DREF, TRUE,  QT_EDTS, FALSE,
52                             QT_ELST, TRUE,  QT_HDLR, TRUE,  QT_KMAT, TRUE,  QT_MATT, FALSE, QT_MDAT, TRUE,
53                             QT_MDIA, FALSE, QT_MDHD, TRUE,  QT_MINF, FALSE, QT_MOOV, FALSE, QT_MVHD, TRUE,
54                             QT_SMHD, TRUE,  QT_STBL, FALSE, QT_STCO, TRUE,  QT_STSC, TRUE,  QT_STSD, TRUE,
55                             QT_STSH, TRUE,  QT_STSS, TRUE,  QT_STSZ, TRUE,  QT_STTS, TRUE,  QT_TKHD, TRUE,
56                             QT_TRAK, FALSE, QT_UDTA, FALSE, QT_VMHD, TRUE,  0,       0};
57 TrackType currTrackType;
58 
59 //  Prototypes
60 void main(void);
61 void CheckError(OSErr error, Str255 displayString);
62 void SetInputSpecs(void);
63 void SetPalette(short palID);
64 
65 /*
66 uchar QuikReadChunkHdr(FILE *fp, QT_ChunkHdr *phdr);
67 QT_ChunkInfo *QuikFindChunkInfo(QT_ChunkHdr *phdr);
68 void QuikSkipChunk(FILE *fp, QT_ChunkHdr *phdr);
69 */
70 uchar QuikReadChunkHdr(Ptr &p, QT_ChunkHdr *phdr);
71 QT_ChunkInfo *QuikFindChunkInfo(QT_ChunkHdr *phdr);
72 void QuikSkipChunk(Ptr &p, QT_ChunkHdr *phdr);
73 
74 //	---------------------------------------------------------------
75 //		MAIN PROGRAM
76 //	---------------------------------------------------------------
77 
main(void)78 void main(void) {
79   grs_screen *screen;
80   OSErr err, result;
81 
82   StandardFileReply reply;
83   SFTypeList typeList;
84 
85   FILE *fp;
86   uchar *dbuff;
87   ulong dbuffLen;
88   QT_ChunkHdr chunkHdr;
89   QT_ChunkInfo *pinfo;
90 
91   CTabHandle ctab;
92 
93   Point dlgPos = {120, 120};
94   SFReply sfr;
95   FSSpec mySpec;
96   Str255 name = "QT Movie";
97   Rect movieRect, r;
98   ImageDescription **imageDescriptionH = 0L; // Contains info about the sample
99   short resRefNum;
100   Movie gMovie = 0; // Our movie, track and media
101   Track gTrack;
102   Media gMedia;
103 
104   Handle movieRsrc;
105 
106   //---------------------
107   //	Init graphics system
108   //---------------------
109   InitMac();
110   CheckConfig();
111 
112   SetupWindows(&gMainWindow); // setup everything
113   SetupOffscreenBitmaps();
114 
115   gr_init();
116   gr_set_mode(GRM_640x480x8, TRUE);
117   screen = gr_alloc_screen(grd_cap->w, grd_cap->h);
118   gr_set_screen(screen);
119   gr_clear(0xff);
120 
121   //---------------------
122   // Setup Quicktime stuff.
123   //---------------------
124   if (EnterMovies() != noErr) // Start up the movie tools
125   {
126     ParamText("Can't startup QuickTime.", "", "", "");
127     StopAlert(1000, nil);
128     CleanupAndExit();
129   }
130 
131   //-------------------------------------------------------------
132   //	Open the QuickTime movie as a QuickTime movie, and get information about the move.
133   //-------------------------------------------------------------
134   typeList[0] = 'MooV';
135   StandardGetFilePreview(nil, 1, typeList, &reply);
136   if (!reply.sfGood) {
137     ExitMovies();
138     CleanupAndExit();
139   }
140   err = OpenMovieFile(&reply.sfFile, &resRefNum, fsRdPerm);
141   if (err == noErr) {
142     movieRsrc = GetIndResource('moov', 1);
143     if (!movieRsrc)
144       CheckError(1, "Can't get movie resource!");
145     DetachResource(movieRsrc);
146 
147     short movieResID = 0; // get first movie
148     Str255 movieName;
149     Boolean wasChanged;
150 
151     err = NewMovieFromFile(&gMovie, resRefNum, &movieResID, movieName, newMovieActive, &wasChanged);
152     CloseMovieFile(resRefNum);
153   } else
154     CheckError(1, "Can't open the movie file!!");
155 
156   // Get movie rectangle
157   GetMovieBox(gMovie, &movieRect);
158   OffsetRect(&movieRect, -movieRect.left, -movieRect.top);
159 
160   // Set movie palette
161   GetMovieColorTable(gMovie, &ctab);
162   SetEntries(1, 253, &(**ctab).ctTable[1]);
163 
164   DisposeMovie(gMovie);
165 
166   //------------------------------------------------
167   //	Open the input QuickTime movie again, this time as a data fork file.
168   //------------------------------------------------
169   fp = fopen(p2cstr(reply.sfFile.name), "rb");
170   if (fp == NULL)
171     CheckError(1, "Can't open the input movie!!");
172 
173   dbuffLen = 64000;
174   dbuff = (uchar *)malloc(dbuffLen);
175 
176   //----------------------
177   //	Setup output file.
178   //----------------------
179   SFPutFile(dlgPos, "Save Movie as:", name, 0L, &sfr);
180   if (!sfr.good) {
181     ExitMovies();
182     CleanupAndExit();
183   }
184 
185   ClearMoviesStickyError();
186   FSMakeFSSpec(sfr.vRefNum, 0, sfr.fName, &mySpec);
187   err = CreateMovieFile(&mySpec, 'TVOD', 0, createMovieFileDeleteCurFile, &resRefNum, &gMovie);
188   CheckError(err, "Can't create output movie file.");
189 
190   SetMovieColorTable(gMovie, ctab);
191 
192   gTrack = NewMovieTrack(gMovie, movieRect.right << 16, movieRect.bottom << 16, 0);
193   CheckError(GetMoviesError(), "New video track.");
194 
195   gMedia = NewTrackMedia(gTrack, VideoMediaType, 600, 0L, 0L);
196   CheckError(GetMoviesError(), "New Media for video track.");
197 
198   BeginMediaEdits(gMedia); // We do this since we are adding samples to the media
199 
200   //-----------------------------------
201   //  Setup the standard compression component stuff.
202   //-----------------------------------
203   ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
204   if (!ci)
205     CheckError(-1, "Can't open the Standard Compression component.");
206 
207   //-----------------------------------
208   //	Get the Chunk Offset and Sample-to-Time tables.
209   //-----------------------------------
210   HLock(movieRsrc);
211   Ptr mp = (Ptr)*movieRsrc;
212   while (TRUE) {
213     // if (!QuikReadChunkHdr(fp, &chunkHdr))
214     if (!QuikReadChunkHdr(mp, &chunkHdr))
215       break;
216     if (chunkHdr.length == 0)
217       break;
218     pinfo = QuikFindChunkInfo(&chunkHdr);
219     if (pinfo->isleaf) {
220       if ((chunkHdr.ctype != QT_MDAT)) {
221         if (chunkHdr.length > dbuffLen) {
222           dbuffLen = chunkHdr.length;
223           dbuff = (uchar *)realloc(dbuff, dbuffLen);
224         }
225         // fread(dbuff, chunkHdr.length - sizeof(QT_ChunkHdr), 1, fp);
226         BlockMove(mp, dbuff, chunkHdr.length - sizeof(QT_ChunkHdr));
227         mp += chunkHdr.length - sizeof(QT_ChunkHdr);
228 
229         // For the sample-to-time table, create our own table giving a time
230         // for each frame.
231         if (chunkHdr.ctype == QT_STTS) {
232           QTS_STTS *p = (QTS_STTS *)dbuff;
233           short i, j, si;
234 
235           gSampleTimes = (ulong *)malloc(1200 * sizeof(ulong));
236           si = 0;
237           for (i = 0; i < p->numEntries; i++)
238             for (j = 0; j < p->time2samp[i].count; j++)
239               gSampleTimes[si++] = p->time2samp[i].duration;
240         }
241 
242         // For the chunk offsets table, read it in to a memory block.
243         if (chunkHdr.ctype == QT_STCO) {
244           QTS_STCO *p = (QTS_STCO *)dbuff;
245 
246           gNumFrames = p->numEntries;
247           gChunkOffsets = (ulong *)malloc(p->numEntries * sizeof(ulong));
248           BlockMove(p->offset, gChunkOffsets, p->numEntries * sizeof(ulong));
249         }
250       } else
251         // QuikSkipChunk(fp, &chunkHdr);
252         QuikSkipChunk(mp, &chunkHdr);
253     }
254   }
255   HUnlock(movieRsrc);
256 
257   HideCursor();
258 
259   //----------------------------
260   //	Show the movie, one frame at a time.
261   //----------------------------
262   {
263     long f, line;
264     Ptr frameBuff;
265     Ptr imgp, scrp;
266     RGBColor black = {0, 0, 0};
267     RGBColor white = {0xffff, 0xffff, 0xffff};
268     char buff[64];
269     ulong sampTime;
270     Boolean subColor;
271     Handle compHdl;
272     long compSize;
273     short notSyncFlag;
274     uchar *pp;
275 
276     frameBuff = malloc(movieRect.right * movieRect.bottom);
277     CheckError(MemError(), "Can't allocate a frame buffer for input movie.");
278 
279     SetRect(&r, 0, -17, 300, 0);
280     for (f = 0; f < gNumFrames; f++) {
281       if (f % 4 == 3) // Skip every fourth frame
282         continue;
283 
284       // Read the next frame from the input movie.
285       fseek(fp, gChunkOffsets[f], SEEK_SET);
286       fread(frameBuff, movieRect.right * movieRect.bottom, 1, fp);
287 
288       // See if there's an 0xFF anywhere in this screen.
289       subColor = FALSE;
290       pp = (uchar *)frameBuff;
291       for (int pix = 0; pix < movieRect.right * movieRect.bottom; pix++) {
292         if (*pp == 0x00) {
293           *pp = 0xFF;
294           subColor = TRUE;
295         }
296         pp++;
297       }
298 
299       imgp = frameBuff;
300       scrp = gScreenAddress;
301       for (line = 0; line < movieRect.bottom; line++) {
302         BlockMove(imgp, scrp, movieRect.right);
303         imgp += movieRect.right;
304         scrp += gScreenRowbytes;
305       }
306 
307       // The first time through the loop (after displaying the first frame), set the compression
308       // parameters for the output movie and begin a compression sequence.
309       if (f == 0) {
310         result = SCDefaultPixMapSettings(ci, ((CGrafPort *)(gMainWindow))->portPixMap, TRUE);
311         result = SCRequestSequenceSettings(ci);
312         if (result == scUserCancelled) {
313           CloseComponent(ci);
314           ExitMovies();
315           CleanupAndExit();
316         }
317         CheckError(result, "Error in sequence settings.");
318 
319         // Redraw the first frame on the screen.
320         gr_clear(0xFF);
321         imgp = frameBuff;
322         scrp = gScreenAddress;
323         for (line = 0; line < movieRect.bottom; line++) {
324           BlockMove(imgp, scrp, movieRect.right);
325           imgp += movieRect.right;
326           scrp += gScreenRowbytes;
327         }
328 
329         // Begin a compression sequence.
330         result = SCCompressSequenceBegin(ci, ((CGrafPort *)(gMainWindow))->portPixMap, &movieRect, &imageDescriptionH);
331         CheckError(result, "Can't start a sequence.");
332       }
333 
334       // Display the frame number.
335 
336       sprintf(buff, "Frame: %d", f);
337       RGBForeColor(&black);
338       PaintRect(&r);
339       MoveTo(1, -6);
340       RGBForeColor(&white);
341       DrawText(buff, 0, strlen(buff));
342       if (subColor) {
343         MoveTo(250, -6);
344         DrawString("Substitued color 0x00");
345       }
346 
347       // Add the frame to the QuickTime movie.
348 
349       result = SCCompressSequenceFrame(ci, ((CGrafPort *)(gMainWindow))->portPixMap, &movieRect, &compHdl, &compSize,
350                                        &notSyncFlag);
351       CheckError(result, "Can't compress a frame.");
352 
353       //			sampTime = gSampleTimes[f];
354       sampTime = gSampleTimes[f] * 1.3333;
355       result = AddMediaSample(gMedia, compHdl, 0L, compSize, sampTime, (SampleDescriptionHandle)imageDescriptionH, 1L,
356                               notSyncFlag, 0L);
357       CheckError(result, "Can't add the frame sample.");
358     }
359   }
360   ShowCursor();
361 
362   //	CDSequenceEnd(seq);
363   SCCompressSequenceEnd(ci);
364   EndMediaEdits(gMedia);
365 
366   result = InsertMediaIntoTrack(gTrack, 0L, 0L, GetMediaDuration(gMedia), 1L << 16);
367   CheckError(result, "Can't insert media into track.");
368 
369   // Finally, we're done with the movie.
370   result = AddMovieResource(gMovie, resRefNum, 0L, 0L);
371   CheckError(result, "Can't add the movie resource.");
372 
373   // Close the movie file.
374   CloseMovieFile(resRefNum);
375 
376   if (gMovie)
377     DisposeMovie(gMovie);
378 
379   fclose(fp);
380   CloseComponent(ci);
381   ExitMovies();
382 
383   gr_clear(0xFF);
384   CleanupAndExit();
385 }
386 
387 //------------------------------------------------------------------------
388 //  Exit in case of an error.
389 //------------------------------------------------------------------------
CheckError(OSErr error,Str255 displayString)390 void CheckError(OSErr error, Str255 displayString) {
391   if (error == noErr)
392     return;
393   ParamText(displayString, "", "", "");
394   StopAlert(1000, nil);
395   if (ci)
396     CloseComponent(ci);
397   ExitMovies();
398   CleanupAndExit();
399 }
400 
401 //	===============================================================
402 //	QuickTime file dumping routines.
403 //	===============================================================
404 
405 //	--------------------------------------------------------------
406 //
407 //	QuikReadChunkHdr() reads in the next chunk header, returns TRUE if ok.
408 
409 // uchar QuikReadChunkHdr(FILE *fp, QT_ChunkHdr *phdr)
QuikReadChunkHdr(Ptr & p,QT_ChunkHdr * phdr)410 uchar QuikReadChunkHdr(Ptr &p, QT_ChunkHdr *phdr) {
411   //	fread(phdr, sizeof(QT_ChunkHdr), 1, fp);
412   BlockMove(p, phdr, sizeof(QT_ChunkHdr));
413   p += sizeof(QT_ChunkHdr);
414 
415   switch (phdr->ctype) {
416   case QT_TRAK:
417     currTrackType = TRACK_OTHER;
418     break;
419 
420   case QT_VMHD:
421     currTrackType = TRACK_VIDEO;
422     break;
423 
424   case QT_SMHD:
425     currTrackType = TRACK_AUDIO;
426     break;
427   }
428 
429   //	return(feof(fp) == 0);
430   return (TRUE);
431 }
432 
433 //	--------------------------------------------------------------
434 //
435 //	QuikFindChunkInfo() finds info for a chunk.
436 
QuikFindChunkInfo(QT_ChunkHdr * phdr)437 QT_ChunkInfo *QuikFindChunkInfo(QT_ChunkHdr *phdr) {
438   static QT_Ctype lastType = 0;
439   static QT_ChunkInfo *lastInfoPtr = NULL;
440 
441   QT_ChunkInfo *pinfo;
442 
443   if (lastType == phdr->ctype)
444     return ((QT_ChunkInfo *)lastInfoPtr);
445 
446   pinfo = chunkInfo;
447   while (pinfo->ctype) {
448     if (pinfo->ctype == phdr->ctype) {
449       lastType = phdr->ctype;
450       lastInfoPtr = pinfo;
451       return ((QT_ChunkInfo *)pinfo);
452     }
453     ++pinfo;
454   }
455   return NULL;
456 }
457 
458 //	--------------------------------------------------------------
459 //
460 //	QuikSkipChunk() skips over the data in the current chunk.
461 
462 // void QuikSkipChunk(FILE *fp, QT_ChunkHdr *phdr)
QuikSkipChunk(Ptr & p,QT_ChunkHdr * phdr)463 void QuikSkipChunk(Ptr &p, QT_ChunkHdr *phdr) {
464   //	fseek(fp, phdr->length - sizeof(QT_ChunkHdr), SEEK_CUR);
465   p += phdr->length - sizeof(QT_ChunkHdr);
466 }
467