sprintf considered harmful...
[asdcplib.git] / src / wavesplit.cpp
1 /*
2 Copyright (c) 2005-2006, 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    wavesplit.cpp
28     \version $Id$
29     \brief   WAV file splitter
30 */
31
32 #include <AS_DCP.h>
33 #include <WavFileWriter.h>
34 #include <assert.h>
35
36 using namespace ASDCP;
37
38 //------------------------------------------------------------------------------------------
39 //
40 // command line option parser class
41
42 static const char* PACKAGE = "wavesplit";    // program name for messages
43
44 // Macros used to test command option data state.
45
46 // True if a major mode has already been selected.
47 #define TEST_MAJOR_MODE()     ( create_flag )
48
49 // Causes the caller to return if a major mode has already been selected,
50 // otherwise sets the given flag.
51 #define TEST_SET_MAJOR_MODE(f) if ( TEST_MAJOR_MODE() ) \
52                                  { \
53                                    fputs("Conflicting major mode, choose one of -(ic)).\n", stderr); \
54                                    return; \
55                                  } \
56                                  (f) = true;
57
58 // Increment the iterator, test for an additional non-option command line argument.
59 // Causes the caller to return if there are no remaining arguments or if the next
60 // argument begins with '-'.
61 #define TEST_EXTRA_ARG(i,c)    if ( ++i >= argc || argv[(i)][0] == '-' ) \
62                                  { \
63                                    fprintf(stderr, "Argument not found for option %c.\n", (c)); \
64                                    return; \
65                                  }
66 //
67 void
68 banner(FILE* stream = stderr)
69 {
70   fprintf(stream, "\n\
71 %s (asdcplib %s)\n\n\
72 Copyright (c) 2005-2006 John Hurst\n\n\
73 wavesplit is part of asdcplib.\n\
74 asdcplib may be copied only under the terms of the license found at\n\
75 the top of every file in the asdcplib distribution kit.\n\n\
76 Specify the -h (help) option for further information about %s\n\n",
77           PACKAGE, ASDCP::Version(), PACKAGE);
78 }
79
80 //
81 void
82 usage(FILE* stream = stderr)
83 {
84   fprintf(stream, "\
85 USAGE: %s [-i|-c <root-name> [-v]] <filename>\n\
86 \n\
87 Major modes:\n\
88   -c <root-name>  - Create a WAV file for each channel in the input file (default is two channel files)\n\
89   -V              - Show version\n\
90   -h              - Show help\n\
91 \n\
92 Read/Write Options:\n\
93   -f <frame-num>  - Starting frame number, default 0\n\
94   -d <duration>   - Number of frames to process, default all\n\
95   -v              - Print extra info while processing\n\
96 \n\
97   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
98          o All option arguments must be separated from the option by whitespace.\n\
99 \n", PACKAGE);
100 }
101
102 //
103 //
104 class CommandOptions
105 {
106   CommandOptions();
107
108 public:
109   bool   error_flag;     // true if the given options are in error or not complete
110   bool   create_flag;    // true if the file create mode was selected
111   bool   version_flag;   // true if the version display option was selected
112   bool   help_flag;      // true if the help display option was selected
113   bool   verbose_flag;   // true for extra info during procesing
114   ui32_t start_frame;    // frame number to begin processing
115   ui32_t duration;       // number of frames to be processed
116   const char* file_root; // filename prefix for files written by the extract mode
117   const char* filename;  // filename to be processed
118
119   CommandOptions(int argc, const char** argv) :
120     error_flag(true), create_flag(false),
121     version_flag(false), help_flag(false), start_frame(0),
122     duration(0xffffffff), file_root(0), filename(0)
123   {
124     for ( int i = 1; i < argc; i++ )
125       {
126         if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
127           {
128             switch ( argv[i][1] )
129               {
130               case 'V': version_flag = true; break;
131               case 'h': help_flag = true; break;
132               case 'c':
133                 TEST_SET_MAJOR_MODE(create_flag);
134                 TEST_EXTRA_ARG(i, 'c');
135                 file_root = argv[i];
136                 break;
137
138               case 'f':
139                 TEST_EXTRA_ARG(i, 'f');
140                 start_frame = atoi(argv[i]); // TODO: test for negative value, should use strtol()
141                 break;
142
143               case 'd':
144                 TEST_EXTRA_ARG(i, 'd');
145                 duration = atoi(argv[i]); // TODO: test for negative value, should use strtol()
146                 break;
147
148               default:
149                 fprintf(stderr, "Unrecognized option: %c\n", argv[i][1]);
150                 return;
151               }
152           }
153         else
154           {
155             if ( filename )
156               {
157                 fprintf(stderr, "Unexpected extra filename.\n");
158                 return;
159               }
160
161             filename = argv[i];
162           }
163       }
164
165     if ( TEST_MAJOR_MODE() )
166       {
167         if ( filename == 0 )
168           {
169             fputs("Input filename required.\n", stderr);
170             return;
171           }
172       }
173
174     if ( ! TEST_MAJOR_MODE() && ! help_flag && ! version_flag )
175       {
176         fputs("No operation selected (use one of -(ic) or -h for help).\n", stderr);
177         return;
178       }
179
180     error_flag = false;
181   }
182 };
183
184
185 //
186 //
187 void
188 split_buffer(ui32_t sample_size, PCM::FrameBuffer& FrameBuffer,
189              PCM::FrameBuffer& L_FrameBuffer, PCM::FrameBuffer& R_FrameBuffer)
190 {
191   assert((FrameBuffer.Size() % 2) == 0);
192   byte_t* p = FrameBuffer.Data();
193   byte_t* end_p = p + FrameBuffer.Size();
194   byte_t* lp = L_FrameBuffer.Data();
195   byte_t* rp = R_FrameBuffer.Data();
196
197   for ( ; p < end_p; )
198     {
199       memcpy(lp, p, sample_size);
200       lp += sample_size;
201       p += sample_size;
202       memcpy(rp, p, sample_size);
203       rp += sample_size;
204       p += sample_size;
205     }
206
207   L_FrameBuffer.Size(L_FrameBuffer.Capacity());
208   R_FrameBuffer.Size(R_FrameBuffer.Capacity());
209 }
210
211
212 // 
213 //
214 Result_t
215 split_wav_file(CommandOptions& Options)
216 {
217   PCM::FrameBuffer FrameBuffer;
218   PCM::FrameBuffer L_FrameBuffer;
219   PCM::FrameBuffer R_FrameBuffer;
220   PCM::AudioDescriptor ADesc;
221   Rational         PictureRate = EditRate_24;
222   PCM::WAVParser Parser;
223
224   // set up essence parser
225   Result_t result = Parser.OpenRead(Options.filename, PictureRate);
226
227   if ( ASDCP_SUCCESS(result) )
228     {
229       Parser.FillAudioDescriptor(ADesc);
230
231       ADesc.SampleRate = PictureRate;
232       ui32_t fb_size = PCM::CalcFrameBufferSize(ADesc);
233       assert((fb_size % 2) == 0);
234       FrameBuffer.Capacity(fb_size);
235       L_FrameBuffer.Capacity(fb_size/2);
236       R_FrameBuffer.Capacity(fb_size/2);
237
238       if ( Options.verbose_flag )
239         {
240           fprintf(stderr, "48Khz PCM Audio, %s fps (%u spf)\n", "24",
241                   PCM::CalcSamplesPerFrame(ADesc));
242           fputs("AudioDescriptor:\n", stderr);
243           PCM::AudioDescriptorDump(ADesc);
244         }
245
246       ADesc.ChannelCount = 1;
247     }
248
249   // set up output files
250   Kumu::FileWriter L_OutFile;
251   Kumu::FileWriter R_OutFile;
252
253   if ( ASDCP_SUCCESS(result) )
254     {
255       char filename[256];
256       snprintf(filename, 256, "%s_l.wav", Options.file_root);
257       result = L_OutFile.OpenWrite(filename);
258
259       if ( ASDCP_SUCCESS(result) )
260         {
261           snprintf(filename, 256, "%s_r.wav", Options.file_root);
262           result = R_OutFile.OpenWrite(filename);
263         }
264     }
265
266
267   if ( ASDCP_SUCCESS(result) )
268     {
269       Wav::SimpleWaveHeader WavHeader(ADesc);
270       result = WavHeader.WriteToFile(L_OutFile);
271
272       if ( ASDCP_SUCCESS(result) )
273         result = WavHeader.WriteToFile(R_OutFile);
274     }
275
276   if ( ASDCP_SUCCESS(result) )
277     {
278       ui32_t write_count = 0;
279       ui32_t duration = 0;
280
281       while ( ASDCP_SUCCESS(result) && (duration++ < Options.duration) )
282         {
283           result = Parser.ReadFrame(FrameBuffer);
284
285           if ( FrameBuffer.Size() != FrameBuffer.Capacity() )
286             {
287               fprintf(stderr, "WARNING: Last frame read was short, PCM input is possibly not frame aligned.\n");
288               fprintf(stderr, "Expecting %u bytes, got %u.\n", FrameBuffer.Capacity(), FrameBuffer.Size());
289               result = RESULT_ENDOFFILE;
290               continue;
291             }
292
293           if ( Options.verbose_flag )
294             FrameBuffer.Dump(stderr);
295
296           if ( ASDCP_SUCCESS(result) )
297             {
298               split_buffer(PCM::CalcSampleSize(ADesc), FrameBuffer, L_FrameBuffer, R_FrameBuffer);
299               result = L_OutFile.Write(L_FrameBuffer.Data(), L_FrameBuffer.Size(), &write_count);
300
301               if ( ASDCP_SUCCESS(result) )
302                 result = R_OutFile.Write(R_FrameBuffer.Data(), R_FrameBuffer.Size(), &write_count);
303             }
304         }
305
306       if ( result == RESULT_ENDOFFILE )
307         result = RESULT_OK;
308
309       if ( ASDCP_SUCCESS(result) )
310         {
311           ADesc.ContainerDuration = duration;
312           Wav::SimpleWaveHeader WavHeader(ADesc);
313           L_OutFile.Seek();
314
315           if ( ASDCP_SUCCESS(result) )
316             result = R_OutFile.Seek();
317
318           if ( ASDCP_SUCCESS(result) )
319             result = WavHeader.WriteToFile(L_OutFile);
320
321           if ( ASDCP_SUCCESS(result) )
322             result = WavHeader.WriteToFile(R_OutFile);
323         }
324     }
325
326   return RESULT_OK;
327 }
328
329
330 //
331 int
332 main(int argc, const char** argv)
333 {
334   Result_t result = RESULT_OK;
335   CommandOptions Options(argc, argv);
336
337   if ( Options.help_flag )
338     {
339       usage();
340       return 0;
341     }
342
343   if ( Options.error_flag )
344     return 3;
345
346   if ( Options.version_flag )
347     banner();
348
349   if ( Options.create_flag )
350     result = split_wav_file(Options);
351
352   if ( result != RESULT_OK )
353     {
354       fputs("Program stopped on error.\n", stderr);
355
356       if ( result != RESULT_FAIL )
357         {
358           fputs(result, stderr);
359           fputc('\n', stderr);
360         }
361
362       return 1;
363     }
364
365   return 0;
366 }
367
368
369 //