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 ¬SyncFlag);
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