Simplify time representation; better in-tree DCP subtitle parser.
[libsub.git] / asdcplib / src / asdcp-info.cpp
1 /*
2 Copyright (c) 2003-2012, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    asdcp-info.cpp
28     \version $Id: asdcp-info.cpp,v 1.1 2012/02/03 19:49:56 jhurst Exp $       
29     \brief   AS-DCP file metadata utility
30
31   This program provides metadata information about an AS-DCP file.
32
33   For more information about asdcplib, please refer to the header file AS_DCP.h
34 */
35
36 #include <KM_fileio.h>
37 #include <AS_DCP.h>
38 #include <MXF.h>
39 #include <Metadata.h>
40 #include <openssl/sha.h>
41
42 using namespace Kumu;
43 using namespace ASDCP;
44
45 const ui32_t FRAME_BUFFER_SIZE = 4 * Kumu::Megabyte;
46
47 //------------------------------------------------------------------------------------------
48 //
49 // command line option parser class
50
51 static const char* PROGRAM_NAME = "asdcp-info";  // program name for messages
52
53
54 // Increment the iterator, test for an additional non-option command line argument.
55 // Causes the caller to return if there are no remaining arguments or if the next
56 // argument begins with '-'.
57 #define TEST_EXTRA_ARG(i,c)                                             \
58   if ( ++i >= argc || argv[(i)][0] == '-' ) {                           \
59     fprintf(stderr, "Argument not found for option -%c.\n", (c));       \
60     return;                                                             \
61   }
62
63 //
64 void
65 banner(FILE* stream = stdout)
66 {
67   fprintf(stream, "\n\
68 %s (asdcplib %s)\n\n\
69 Copyright (c) 2003-2012 John Hurst\n\n\
70 asdcplib may be copied only under the terms of the license found at\n\
71 the top of every file in the asdcplib distribution kit.\n\n\
72 Specify the -h (help) option for further information about %s\n\n",
73           PROGRAM_NAME, ASDCP::Version(), PROGRAM_NAME);
74 }
75
76 //
77 void
78 usage(FILE* stream = stdout)
79 {
80   fprintf(stream, "\
81 USAGE:%s [-h|-help] [-V]\n\
82 \n\
83        %s [-3] [-H] [-n] <input-file>+\n\
84 \n\
85 Options:\n\
86   -3                - Force stereoscopic interpretation of a JP2K file\n\
87   -h | -help        - Show help\n\
88   -H                - Show MXF header metadata\n\
89   -n                - Show index\n\
90   -V                - Show version information\n\
91 \n\
92   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
93          o All option arguments must be separated from the option by whitespace.\n\n",
94           PROGRAM_NAME, PROGRAM_NAME);
95
96 }
97
98 //
99 class CommandOptions
100 {
101   CommandOptions();
102
103 public:
104   bool   error_flag;     // true if the given options are in error or not complete
105   bool   version_flag;   // true if the version display option was selected
106   bool   help_flag;      // true if the help display option was selected
107   PathList_t filenames;  // list of filenames to be processed
108   bool   showindex_flag; // true if index is to be displayed
109   bool   showheader_flag; // true if MXF file header is to be displayed
110   bool   stereo_image_flag; // if true, expect stereoscopic JP2K input (left eye first)
111
112   //
113   CommandOptions(int argc, const char** argv) :
114     error_flag(true), version_flag(false), help_flag(false),
115     showindex_flag(), showheader_flag(), stereo_image_flag(false)
116   {
117     for ( int i = 1; i < argc; ++i )
118       {
119
120         if ( (strcmp( argv[i], "-help") == 0) )
121           {
122             help_flag = true;
123             continue;
124           }
125          
126         if ( argv[i][0] == '-'
127              && ( isalpha(argv[i][1]) || isdigit(argv[i][1]) )
128              && argv[i][2] == 0 )
129           {
130             switch ( argv[i][1] )
131               {
132               case '3': stereo_image_flag = true; break;
133               case 'H': showheader_flag = true; break;
134               case 'h': help_flag = true; break;
135               case 'n': showindex_flag = true; break;
136               case 'V': version_flag = true; break;
137
138               default:
139                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
140                 return;
141               }
142           }
143         else
144           {
145             if ( argv[i][0] != '-' )
146               {
147                 filenames.push_back(argv[i]);
148               }
149             else
150               {
151                 fprintf(stderr, "Unrecognized argument: %s\n", argv[i]);
152                 return;
153               }
154           }
155       }
156
157     if ( help_flag || version_flag )
158       return;
159     
160     if ( filenames.empty() )
161       {
162         fputs("Option requires at least one filename argument.\n", stderr);
163         return;
164       }
165
166     error_flag = false;
167   }
168 };
169
170 //------------------------------------------------------------------------------------------
171 //
172
173 //
174 // These classes wrap the irregular names in the asdcplib API
175 // so that I can use a template to simplify the implementation
176 // of show_file_info()
177
178 class MyVideoDescriptor : public MPEG2::VideoDescriptor
179 {
180  public:
181   void FillDescriptor(MPEG2::MXFReader& Reader) {
182     Reader.FillVideoDescriptor(*this);
183   }
184
185   void Dump(FILE* stream) {
186     MPEG2::VideoDescriptorDump(*this, stream);
187   }
188 };
189
190 class MyPictureDescriptor : public JP2K::PictureDescriptor
191 {
192  public:
193   void FillDescriptor(JP2K::MXFReader& Reader) {
194     Reader.FillPictureDescriptor(*this);
195   }
196
197   void Dump(FILE* stream) {
198     JP2K::PictureDescriptorDump(*this, stream);
199   }
200 };
201
202 class MyStereoPictureDescriptor : public JP2K::PictureDescriptor
203 {
204  public:
205   void FillDescriptor(JP2K::MXFSReader& Reader) {
206     Reader.FillPictureDescriptor(*this);
207   }
208
209   void Dump(FILE* stream) {
210     JP2K::PictureDescriptorDump(*this, stream);
211   }
212 };
213
214 class MyAudioDescriptor : public PCM::AudioDescriptor
215 {
216  public:
217   void FillDescriptor(PCM::MXFReader& Reader) {
218     Reader.FillAudioDescriptor(*this);
219   }
220
221   void Dump(FILE* stream) {
222     PCM::AudioDescriptorDump(*this, stream);
223   }
224 };
225
226 class MyTextDescriptor : public TimedText::TimedTextDescriptor
227 {
228  public:
229   void FillDescriptor(TimedText::MXFReader& Reader) {
230     Reader.FillTimedTextDescriptor(*this);
231   }
232
233   void Dump(FILE* stream) {
234     TimedText::DescriptorDump(*this, stream);
235   }
236 };
237
238 // MSVC didn't like the function template, so now it's a static class method
239 template<class ReaderT, class DescriptorT>
240 class FileInfoWrapper
241 {
242 public:
243   static Result_t
244   file_info(CommandOptions& Options, const char* type_string, FILE* stream = 0)
245   {
246     assert(type_string);
247     if ( stream == 0 )
248       stream = stdout;
249
250     Result_t result = RESULT_OK;
251     ReaderT     Reader;
252     result = Reader.OpenRead(Options.filenames.front().c_str());
253
254     if ( ASDCP_SUCCESS(result) )
255       {
256         fprintf(stdout, "File essence type is %s.\n", type_string);
257
258         if ( Options.showheader_flag )
259           Reader.DumpHeaderMetadata(stream);
260
261         WriterInfo WI;
262         Reader.FillWriterInfo(WI);
263         WriterInfoDump(WI, stream);
264
265         DescriptorT Desc;
266         Desc.FillDescriptor(Reader);
267         Desc.Dump(stream);
268
269         if ( Options.showindex_flag )
270           Reader.DumpIndex(stream);
271       }
272     else if ( result == RESULT_FORMAT && Options.showheader_flag )
273       {
274         Reader.DumpHeaderMetadata(stream);
275       }
276
277     return result;
278   }
279 };
280
281 // Read header metadata from an ASDCP file
282 //
283 Result_t
284 show_file_info(CommandOptions& Options)
285 {
286   EssenceType_t EssenceType;
287   Result_t result = ASDCP::EssenceType(Options.filenames.front().c_str(), EssenceType);
288
289   if ( ASDCP_FAILURE(result) )
290     return result;
291
292   if ( EssenceType == ESS_MPEG2_VES )
293     {
294       result = FileInfoWrapper<ASDCP::MPEG2::MXFReader, MyVideoDescriptor>::file_info(Options, "MPEG2 video");
295     }
296   else if ( EssenceType == ESS_PCM_24b_48k || EssenceType == ESS_PCM_24b_96k )
297     {
298       result = FileInfoWrapper<ASDCP::PCM::MXFReader, MyAudioDescriptor>::file_info(Options, "PCM audio");
299
300       if ( ASDCP_SUCCESS(result) )
301         {
302           const Dictionary* Dict = &DefaultCompositeDict();
303           PCM::MXFReader Reader;
304           MXF::OPAtomHeader OPAtomHeader(Dict);
305           MXF::WaveAudioDescriptor *descriptor = 0;
306
307           result = Reader.OpenRead(Options.filenames.front().c_str());
308
309           if ( ASDCP_SUCCESS(result) )
310             result = Reader.OPAtomHeader().GetMDObjectByType(Dict->ul(MDD_WaveAudioDescriptor), reinterpret_cast<MXF::InterchangeObject**>(&descriptor));
311
312           if ( ASDCP_SUCCESS(result) )
313             {
314               char buf[64];
315               fprintf(stdout, " ChannelAssignment: %s\n", descriptor->ChannelAssignment.EncodeString(buf, 64));
316             }
317         }
318     }
319   else if ( EssenceType == ESS_JPEG_2000 )
320     {
321       if ( Options.stereo_image_flag )
322         {
323           result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
324                                    MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
325         }
326       else
327         {
328           result = FileInfoWrapper<ASDCP::JP2K::MXFReader,
329                                    MyPictureDescriptor>::file_info(Options, "JPEG 2000 pictures");
330         }
331     }
332   else if ( EssenceType == ESS_JPEG_2000_S )
333     {
334       result = FileInfoWrapper<ASDCP::JP2K::MXFSReader,
335                                MyStereoPictureDescriptor>::file_info(Options, "JPEG 2000 stereoscopic pictures");
336     }
337   else if ( EssenceType == ESS_TIMED_TEXT )
338     {
339       result = FileInfoWrapper<ASDCP::TimedText::MXFReader, MyTextDescriptor>::file_info(Options, "Timed Text");
340     }
341   else
342     {
343       fprintf(stderr, "File is not AS-DCP: %s\n", Options.filenames.front().c_str());
344       Kumu::FileReader   Reader;
345       const Dictionary* Dict = &DefaultCompositeDict();
346       MXF::OPAtomHeader TestHeader(Dict);
347
348       result = Reader.OpenRead(Options.filenames.front().c_str());
349
350       if ( ASDCP_SUCCESS(result) )
351         result = TestHeader.InitFromFile(Reader); // test UL and OP
352
353       if ( ASDCP_SUCCESS(result) )
354         {
355           TestHeader.Partition::Dump(stdout);
356
357           if ( MXF::Identification* ID = TestHeader.GetIdentification() )
358             ID->Dump(stdout);
359           else
360             fputs("File contains no Identification object.\n", stdout);
361
362           if ( MXF::SourcePackage* SP = TestHeader.GetSourcePackage() )
363             SP->Dump(stdout);
364           else
365             fputs("File contains no SourcePackage object.\n", stdout);
366         }
367       else
368         {
369           fputs("File is not MXF.\n", stdout);
370         }
371     }
372
373   return result;
374 }
375
376 //
377 int
378 main(int argc, const char** argv)
379 {
380   Result_t result = RESULT_OK;
381   char     str_buf[64];
382   CommandOptions Options(argc, argv);
383
384   if ( Options.version_flag )
385     banner();
386
387   if ( Options.help_flag )
388     usage();
389
390   if ( Options.version_flag || Options.help_flag )
391     return 0;
392
393   if ( Options.error_flag )
394     {
395       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
396       return 3;
397     }
398
399   while ( ! Options.filenames.empty() && ASDCP_SUCCESS(result) )
400     {
401       result = show_file_info(Options);
402       Options.filenames.pop_front();
403     }
404
405   if ( ASDCP_FAILURE(result) )
406     {
407       fputs("Program stopped on error.\n", stderr);
408
409       if ( result == RESULT_SFORMAT )
410         {
411           fputs("Use option '-3' to force stereoscopic mode.\n", stderr);
412         }
413       else if ( result != RESULT_FAIL )
414         {
415           fputs(result, stderr);
416           fputc('\n', stderr);
417         }
418
419       return 1;
420     }
421
422   return 0;
423 }
424
425
426 //
427 // end asdcp-info.cpp
428 //