1 /* core_mode.c - public functions for mode modules
2 * Copyright (C) 2000-2009 Jason Jordan <shnutils@freeshell.org>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include <string.h>
20 #include <unistd.h>
21 #include <ctype.h>
22 #include <stdarg.h>
23 #include <sys/types.h>
24 #include <signal.h>
25 #ifndef WIN32
26 #include <sys/wait.h>
27 #endif
28 #include <sys/stat.h>
29 #include "shntool.h"
30
31 CVSID("$Id: core_mode.c,v 1.88 2009/03/30 05:55:33 jason Exp $")
32
33 global_opts st_ops;
34
35 /* private functions */
36
37 #define parse_input_args_cmd(a,b) parse_args(a,b,FALSE,ARGSRC_CMDLINE)
38 #define parse_output_args_cmd(a,b) parse_args(a,b,TRUE,ARGSRC_CMDLINE)
39 #define parse_input_args_env(a,b) parse_args(a,b,FALSE,ARGSRC_ENV)
40 #define parse_output_args_env(a,b) parse_args(a,b,TRUE,ARGSRC_ENV)
41 #define parse_input_args_fmt(a,b) parse_args(a,b,FALSE,ARGSRC_FORMAT)
42 #define parse_output_args_fmt(a,b) parse_args(a,b,TRUE,ARGSRC_FORMAT)
43
parse_args(format_module * fm,char * argstr,bool is_output,int argsrc)44 static void parse_args(format_module *fm,char *argstr,bool is_output,int argsrc)
45 {
46 char *argdup,*token,*seps = " \t",*extstr = "ext=",*argsrcstr;
47 child_args *ca;
48
49 if (NULL == argstr)
50 return;
51
52 argdup = strdup(argstr);
53 if (NULL == argdup)
54 st_error("argument string duplication failed");
55
56 switch (argsrc) {
57 case ARGSRC_ENV:
58 argsrcstr = "environment";
59 break;
60 case ARGSRC_CMDLINE:
61 argsrcstr = "command line";
62 break;
63 case ARGSRC_FORMAT:
64 default:
65 argsrcstr = PACKAGE;
66 break;
67 }
68
69 st_debug1("parsing [%s] %s argument string originating from %s: [%s]",fm->name,(is_output)?"encoder":"decoder",argsrcstr,argstr);
70
71 ca = (is_output) ? &fm->output_args_template : &fm->input_args_template;
72
73 /* read first token */
74 token = strtok(argdup,seps);
75
76 /* skip certain arguments based on the source of the string we're parsing.
77 format default = "arg1 arg2 ... argN"
78 environment = "prog arg1 arg2 ... argN"
79 command line = "format prog arg1 arg2 ... argN"
80 */
81 switch (argsrc) {
82 case ARGSRC_CMDLINE:
83 /* discard format name */
84 if (NULL == token)
85 return;
86
87 token = strtok(NULL,seps);
88
89 /* fall through */
90
91 case ARGSRC_ENV:
92 /* check for program name ("prog" or "ext=abc prog") */
93 if (NULL == token)
94 return;
95
96 if (is_output) {
97 /* output format: check for "ext=abc" */
98 if (strstr(token,extstr) == token) {
99 fm->extension = token + strlen(extstr);
100 st_debug1("changing %s output extension to: [%s]",fm->name,fm->extension);
101 token = strtok(NULL,seps);
102 }
103
104 if (NULL == token)
105 return;
106
107 /* output format: this is the program name */
108 fm->encoder = token;
109 }
110 else {
111 /* input format: this is the program name */
112 fm->decoder = token;
113 }
114
115 arg_replace(ca,0,token);
116
117 token = strtok(NULL,seps);
118
119 /* fall through */
120
121 case ARGSRC_FORMAT:
122 default:
123 break;
124 }
125
126 /* check if there are any program arguments specified */
127 if (NULL == token)
128 return;
129
130 /* if more arguments exist, they must be encoder/decoder args, so start modifying the existing arg list */
131 arg_reset(ca);
132 arg_add(ca,(is_output)?fm->encoder:fm->decoder);
133
134 while (token) {
135 arg_add(ca,token);
136 token = strtok(NULL,seps);
137 }
138 }
139
rename_by_extension(child_args * outargs,char * filename,char * outfilename,char * input_extension,char * output_extension)140 static void rename_by_extension(child_args *outargs,char *filename,char *outfilename,char *input_extension,char *output_extension)
141 /* function to properly name an output file based on the input filename, prefix/postfixes, and extensions */
142 {
143 char dirname[FILENAME_SIZE],
144 newbasename[FILENAME_SIZE],
145 outdirname[FILENAME_SIZE],
146 custprefix[FILENAME_SIZE],
147 custpostfix[FILENAME_SIZE],
148 tmp[FILENAME_SIZE],
149 *base,*ext,*outdir,*p,*extension;
150 int i;
151
152 /* get user-specified prefix/postfix in %f filename, if any */
153
154 strcpy(custprefix,"");
155 strcpy(custpostfix,"");
156
157 if (outargs) {
158 p = NULL;
159 for (i=0;i<outargs->num_args;i++) {
160 if (NULL == outargs->args[i])
161 continue;
162 if ((p = strstr(outargs->args[i],FILENAME_PLACEHOLDER)))
163 break;
164 }
165
166 if (p) {
167 /* get postfix (everything after %f) */
168 strcpy(custpostfix,p+strlen(FILENAME_PLACEHOLDER));
169
170 /* get prefix (everything before %f) */
171 strcat(custprefix,outargs->args[i]);
172 *(strstr(custprefix,FILENAME_PLACEHOLDER)) = 0;
173 }
174 }
175
176 /* next, copy directory name, if any */
177 strcpy(dirname,filename);
178 base = basename(dirname);
179 if (base)
180 *base = 0;
181
182 /* now get base filename as well as its extension */
183 base = basename(filename);
184 strcpy(newbasename,base);
185 ext = extname(newbasename);
186
187 /* if input filename's extension matches the input format's default extension, then swap it with the
188 output format's extension. otherwise, append the output format's extension to whatever's there */
189 if (ext && input_extension && !strcmp(ext,input_extension))
190 *(ext-1) = 0;
191
192 /* set output directory */
193 outdir = dirname;
194 if (strcmp(st_ops.output_directory,""))
195 outdir = st_ops.output_directory;
196
197 strcpy(outdirname,outdir);
198
199 #if 0
200 /* leave this out for now... we may be getting too fancy. let the underlying OS handle it.
201 * the repeated '/' case below is more straightforward, and harder to screw up, so that stays in for now.
202 */
203
204 /* remove all occurrences of '/./' - it's redundant */
205 p = outdirname;
206 while (*p) {
207 if (0 == *(p+1))
208 break;
209 while ((*(p+2)) && (PATHSEPCHAR == *p) && ('.' == *(p+1)) && (PATHSEPCHAR == *(p+2))) {
210 strcpy(p,p+2);
211 }
212 p++;
213 }
214 #endif
215
216 /* remove duplicate '/' (e.g. "/path///to/output///dir//" becomes "/path/to/output/dir/") */
217 p = outdirname;
218 while (*p) {
219 while ((*(p+1)) && (PATHSEPCHAR == *p) && (PATHSEPCHAR == *(p+1))) {
220 strcpy(p+1,p+2);
221 }
222 p++;
223 }
224
225 st_snprintf(tmp,FILENAME_SIZE,".%c",PATHSEPCHAR);
226
227 if (!strcmp(outdirname,".") || !strcmp(outdirname,tmp))
228 strcpy(outdirname,"");
229
230 st_snprintf(tmp,FILENAME_SIZE,"%c",PATHSEPCHAR);
231
232 if (strcmp(outdirname,"") && PATHSEPCHAR != outdirname[strlen(outdirname)-1])
233 strcat(outdirname,tmp);
234
235 extension = (output_extension) ? output_extension : "";
236
237 /* now build full output filename */
238 st_snprintf(outfilename,FILENAME_SIZE,"%s%s%s%s%s%s.%s",outdirname,custprefix,st_ops.output_prefix,newbasename,st_ops.output_postfix,custpostfix,extension);
239 }
240
default_output_format()241 static format_module *default_output_format()
242 {
243 int i;
244
245 for (i=0;st_formats[i];i++) {
246 if (st_formats[i]->supports_output) {
247 return st_formats[i];
248 }
249 }
250
251 st_error("no output formats found");
252
253 return NULL;
254 }
255
is_numeric(unsigned char * buf)256 static int is_numeric(unsigned char *buf)
257 {
258 unsigned char *p = buf;
259 wlong bytes;
260
261 if (0 == strlen((const char *)buf))
262 return -1;
263
264 while (*p) {
265 if (!isdigit(*p))
266 return -1;
267 p++;
268 }
269
270 bytes =
271 #ifdef HAVE_ATOL
272 (wlong)atol((const char *)buf);
273 #else
274 (wlong)atoi((const char *)buf);
275 #endif
276
277 return bytes;
278 }
279
is_m_ss(unsigned char * buf,wave_info * info)280 static wlong is_m_ss(unsigned char *buf,wave_info *info)
281 {
282 unsigned char *colon,*p;
283 int len,min,sec;
284 wlong bytes;
285
286 len = strlen((const char *)buf);
287
288 if (len < 4)
289 return -1;
290
291 colon = buf + len - 3;
292
293 if (':' != *colon)
294 return -1;
295
296 p = buf;
297 while (*p) {
298 if (p != colon) {
299 if (!isdigit(*p))
300 return -1;
301 }
302 p++;
303 }
304
305 *colon = 0;
306
307 min = atoi((const char *)buf);
308 sec = atoi((const char *)(colon+1));
309
310 if (sec >= 60)
311 st_error("invalid value for seconds: [%d]",sec);
312
313 bytes = (wlong)(min * info->rate * 60) +
314 (wlong)(sec * info->rate);
315
316 return bytes;
317 }
318
is_m_ss_ff(unsigned char * buf,wave_info * info)319 static wlong is_m_ss_ff(unsigned char *buf,wave_info *info)
320 {
321 unsigned char *colon,*dot,*p;
322 int len,min,sec,frames;
323 wlong bytes;
324
325 len = strlen((const char *)buf);
326
327 if (len < 7)
328 return -1;
329
330 colon = buf + len - 6;
331 dot = buf + len - 3;
332
333 if (':' != *colon || '.' != *dot)
334 return -1;
335
336 p = buf;
337 while (*p) {
338 if (p != colon && p != dot) {
339 if (!isdigit(*p))
340 return -1;
341 }
342 p++;
343 }
344
345 *colon = 0;
346 *dot = 0;
347
348 if (PROB_NOT_CD(info))
349 st_error("m:ss.ff format can only be used with CD-quality files");
350
351 min = atoi((const char *)buf);
352 sec = atoi((const char *)(colon+1));
353 frames = atoi((const char *)(dot+1));
354
355 if (sec >= 60)
356 st_error("invalid value for seconds: [%d]",sec);
357
358 if (frames >= 75)
359 st_error("invalid value for frames: [%d]",frames);
360
361 bytes = (wlong)(min * CD_RATE * 60) +
362 (wlong)(sec * CD_RATE) +
363 (wlong)(frames * CD_BLOCK_SIZE);
364
365 return bytes;
366 }
367
is_m_ss_nnn(unsigned char * buf,wave_info * info)368 static wlong is_m_ss_nnn(unsigned char *buf,wave_info *info)
369 {
370 unsigned char *colon,*dot,*p;
371 int len,min,sec,ms,nearest_byte,nearest_frame;
372 wlong bytes;
373
374 len = strlen((const char *)buf);
375
376 if (len < 8)
377 return -1;
378
379 colon = buf + len - 7;
380 dot = buf + len - 4;
381
382 if (':' != *colon || '.' != *dot)
383 return -1;
384
385 p = buf;
386 while (*p) {
387 if (p != colon && p != dot) {
388 if (!isdigit(*p))
389 return -1;
390 }
391 p++;
392 }
393
394 *colon = 0;
395 *dot = 0;
396
397 min = atoi((const char *)buf);
398 sec = atoi((const char *)(colon+1));
399 ms = atoi((const char *)(dot+1));
400
401 if (sec >= 60)
402 st_error("invalid value for seconds: [%d]",sec);
403
404 nearest_byte = (int)((((double)ms * (double)info->rate) / 1000.0) + 0.5);
405
406 bytes = (wlong)(min * info->rate * 60) +
407 (wlong)(sec * info->rate);
408
409 if (PROB_NOT_CD(info)) {
410 bytes += nearest_byte;
411 }
412 else {
413 nearest_frame = (int)((double)ms * .075 + 0.5) * CD_BLOCK_SIZE;
414 if (0 == nearest_frame && 0 == bytes) {
415 st_warning("closest sector boundary to %d:%02d.%03d is the beginning of the file -- rounding up to first sector boundary",min,sec,ms);
416 nearest_frame = CD_BLOCK_SIZE;
417 }
418 if (nearest_frame != nearest_byte) {
419 st_warning("rounding %d:%02d.%03d (offset: %lu) to nearest sector boundary (offset: %lu)",
420 min,sec,ms,bytes+(wlong)nearest_byte,bytes+(wlong)nearest_frame);
421 bytes += (wlong)nearest_frame;
422 }
423 }
424
425 return bytes;
426 }
427
428 /* Compare strings while treating digits characters numerically.
429 Copyright (C) 1997, 2000 Free Software Foundation, Inc.
430 This file is part of the GNU C Library.
431 Contributed by Jean-Fran�ois Bignolles <bignolle@ecoledoc.ibp.fr>, 1997.
432
433 The GNU C Library is free software; you can redistribute it and/or
434 modify it under the terms of the GNU Library General Public License as
435 published by the Free Software Foundation; either version 2 of the
436 License, or (at your option) any later version.
437
438 The GNU C Library is distributed in the hope that it will be useful,
439 but WITHOUT ANY WARRANTY; without even the implied warranty of
440 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
441 Library General Public License for more details.
442
443 You should have received a copy of the GNU Library General Public
444 License along with the GNU C Library; see the file COPYING.LIB. If not,
445 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
446 Boston, MA 02111-1307, USA. */
447
448 /* states: S_N: normal, S_I: comparing integral part, S_F: comparing
449 fractional parts, S_Z: idem but with leading Zeroes only */
450 #define S_N 0x0
451 #define S_I 0x4
452 #define S_F 0x8
453 #define S_Z 0xC
454
455 /* result_type: CMP: return diff; LEN: compare using len_diff/diff */
456 #define CMP 2
457 #define LEN 3
458
459
460 /* ISDIGIT differs from isdigit, as follows:
461 - Its arg may be any int or unsigned int; it need not be an unsigned char.
462 - It's guaranteed to evaluate its argument exactly once.
463 - It's typically faster.
464 Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
465 only '0' through '9' are digits. Prefer ISDIGIT to isdigit unless
466 it's important to use the locale's definition of `digit' even when the
467 host does not conform to Posix. */
468 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
469
470 #undef __strverscmp
471 #undef strverscmp
472
473 #ifndef weak_alias
474 # define __strverscmp strverscmp
475 #endif
476
477 /* Compare S1 and S2 as strings holding indices/version numbers,
478 returning less than, equal to or greater than zero if S1 is less than,
479 equal to or greater than S2 (for more info, see the texinfo doc).
480 */
481
482 static int
__strverscmp(const char * s1,const char * s2)483 __strverscmp (const char *s1, const char *s2)
484 {
485 const unsigned char *p1 = (const unsigned char *) s1;
486 const unsigned char *p2 = (const unsigned char *) s2;
487 unsigned char c1, c2;
488 int state;
489 int diff;
490
491 /* Symbol(s) 0 [1-9] others (padding)
492 Transition (10) 0 (01) d (00) x (11) - */
493 static const unsigned int next_state[] =
494 {
495 /* state x d 0 - */
496 /* S_N */ S_N, S_I, S_Z, S_N,
497 /* S_I */ S_N, S_I, S_I, S_I,
498 /* S_F */ S_N, S_F, S_F, S_F,
499 /* S_Z */ S_N, S_F, S_Z, S_Z
500 };
501
502 static const int result_type[] =
503 {
504 /* state x/x x/d x/0 x/- d/x d/d d/0 d/-
505 0/x 0/d 0/0 0/- -/x -/d -/0 -/- */
506
507 /* S_N */ CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP,
508 CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,
509 /* S_I */ CMP, -1, -1, CMP, 1, LEN, LEN, CMP,
510 1, LEN, LEN, CMP, CMP, CMP, CMP, CMP,
511 /* S_F */ CMP, CMP, CMP, CMP, CMP, LEN, CMP, CMP,
512 CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP,
513 /* S_Z */ CMP, 1, 1, CMP, -1, CMP, CMP, CMP,
514 -1, CMP, CMP, CMP
515 };
516
517 if (p1 == p2)
518 return 0;
519
520 c1 = *p1++;
521 c2 = *p2++;
522 /* Hint: '0' is a digit too. */
523 state = S_N | ((c1 == '0') + (ISDIGIT (c1) != 0));
524
525 while ((diff = c1 - c2) == 0 && c1 != '\0')
526 {
527 state = next_state[state];
528 c1 = *p1++;
529 c2 = *p2++;
530 state |= (c1 == '0') + (ISDIGIT (c1) != 0);
531 }
532
533 state = result_type[state << 2 | ((c2 == '0') + (ISDIGIT (c2) != 0))];
534
535 switch (state)
536 {
537 case CMP:
538 return diff;
539
540 case LEN:
541 while (ISDIGIT (*p1++))
542 if (!ISDIGIT (*p2++))
543 return 1;
544
545 return ISDIGIT (*p2) ? -1 : diff;
546
547 default:
548 return state;
549 }
550 }
551 #ifdef weak_alias
weak_alias(__strverscmp,strverscmp)552 static weak_alias (__strverscmp, strverscmp)
553 #endif
554
555 static int compare_version(const wave_info **w1,const wave_info **w2)
556 {
557 return strverscmp(w1[0]->filename,w2[0]->filename);
558 }
559
compare_ascii(const wave_info ** w1,const wave_info ** w2)560 static int compare_ascii(const wave_info **w1,const wave_info **w2)
561 {
562 return strcmp(w1[0]->filename,w2[0]->filename);
563 }
564
ascii_sort_files(wave_info ** filenames,int numfiles)565 static void ascii_sort_files(wave_info **filenames, int numfiles)
566 {
567 int (*cmpfunc) ();
568
569 cmpfunc = compare_ascii;
570 qsort(filenames,numfiles,sizeof(wave_info *),cmpfunc);
571 }
572
version_sort_files(wave_info ** filenames,int numfiles)573 static void version_sort_files(wave_info **filenames,int numfiles)
574 {
575 int (*cmpfunc) ();
576
577 cmpfunc = compare_version;
578 qsort(filenames,numfiles,sizeof(wave_info *),cmpfunc);
579 }
580
581 /* public functions */
582
open_input_stream_fmt(format_module * fmt,char * infile,proc_info * pinfo)583 FILE *open_input_stream_fmt(format_module *fmt,char *infile,proc_info *pinfo)
584 {
585 if (fmt && fmt->supports_input) {
586 /* if this format defines its own input function, run it */
587 if (fmt->input_func)
588 return fmt->input_func(infile,pinfo);
589
590 return launch_input(fmt,infile,pinfo);
591 }
592
593 return NULL;
594 }
595
open_input_stream(wave_info * info)596 bool open_input_stream(wave_info *info)
597 /* opens an input stream, and if it contains an ID3v2 tag, skips past it */
598 {
599 unsigned long bytes_to_read,tag_size;
600 unsigned char tmp[BUF_SIZE];
601
602 if (info->file_has_id3v2_tag) {
603 if (info->input_format->decoder)
604 st_debug1("decoder [%s] might fail to process ID3v2 tag detected in file: [%s]",info->input_format->decoder,info->filename);
605 else
606 st_debug1("discarding ID3v2 tag detected in file: [%s]",info->filename);
607 }
608
609 if (NULL == (info->input = open_input_stream_fmt(info->input_format,info->filename,&info->input_proc))) {
610 st_warning("could not open file for streaming input: [%s]",info->filename);
611 return FALSE;
612 }
613
614 /* check for ID3v2 tag on input stream */
615 if ((tag_size = check_for_id3v2_tag(info->input))) {
616 if (!info->stream_has_id3v2_tag) {
617 if (info->input_format->decoder)
618 st_debug1("discarding ID3v2 tag detected in input stream generated by decoder [%s] from file: [%s]",info->input_format->decoder,info->filename);
619 else
620 st_debug1("discarding ID3v2 tag detected in input stream from file: [%s]",info->filename);
621 info->stream_has_id3v2_tag = TRUE;
622 }
623
624 if (0 == info->id3v2_tag_size)
625 info->id3v2_tag_size = (wlong)(tag_size + sizeof(id3v2_header));
626
627 st_debug1("discarding %lu-byte ID3v2 tag in input stream generated by decoder [%s] from file: [%s]",
628 info->id3v2_tag_size,info->filename,info->input_format->decoder);
629
630 while (tag_size > 0) {
631 bytes_to_read = min(tag_size,BUF_SIZE);
632
633 if (bytes_to_read != read_n_bytes(info->input,tmp,bytes_to_read,NULL)) {
634 if (info->input_format->decoder)
635 st_warning("error while discarding ID3v2 tag in input stream generated by decoder [%s] from file: [%s]",info->input_format->decoder,info->filename);
636 else
637 st_warning("error while discarding ID3v2 tag in input stream from file: [%s]",info->filename);
638 close_input_stream(info);
639 return FALSE;
640 }
641
642 tag_size -= bytes_to_read;
643 }
644 }
645 else {
646 close_input_stream(info);
647
648 if (NULL == (info->input = open_input_stream_fmt(info->input_format,info->filename,&info->input_proc))) {
649 st_warning("could not reopen file for streaming input: [%s]",info->filename);
650 return FALSE;
651 }
652 }
653
654 return TRUE;
655 }
656
remove_file(char * filename)657 void remove_file(char *filename)
658 {
659 struct stat sz;
660 int retval;
661
662 if (st_ops.output_format && !st_ops.output_format->remove_output_file) {
663 return;
664 }
665
666 if (stat(filename,&sz)) {
667 st_debug1("tried to remove nonexistent file: [%s]",filename);
668 return;
669 }
670
671 if ((retval = unlink(filename))) {
672 st_warning("error while trying to remove partially-written output file: [%s]",filename);
673 return;
674 }
675
676 st_debug1("successfully removed file: [%s]",filename);
677 }
678
files_are_identical(char * file1,char * file2)679 bool files_are_identical(char *file1,char *file2)
680 {
681 struct stat sz1,sz2;
682
683 if (stat(file1,&sz1) || stat(file2,&sz2))
684 return FALSE;
685
686 if (!S_ISREG(sz1.st_mode) || !S_ISREG(sz2.st_mode))
687 return FALSE;
688
689 if (sz1.st_dev != sz2.st_dev)
690 return FALSE;
691
692 if (sz1.st_ino != sz2.st_ino)
693 return FALSE;
694
695 /* more checks, because windows inodes are always zero, which creates false positives */
696 if (sz1.st_size != sz2.st_size)
697 return FALSE;
698
699 if (sz1.st_mtime != sz2.st_mtime)
700 return FALSE;
701
702 return TRUE;
703 }
704
odd_sized_data_chunk_is_null_padded(wave_info * info)705 bool odd_sized_data_chunk_is_null_padded(wave_info *info)
706 /* function to determine whether odd-sized data chunks are NULL-padded to an even length */
707 {
708 FILE *devnull;
709 unsigned char nullpad[BUF_SIZE];
710
711 if (!PROB_ODD_SIZED_DATA(info))
712 return TRUE;
713
714 /* with no extra RIFF chunks, we can tell from the extra RIFF size whether it's padded */
715 if (-1 == info->extra_riff_size)
716 return FALSE;
717
718 if (0 == info->extra_riff_size)
719 return TRUE;
720
721 /* it's odd-sized, and has extra RIFF chunks, so we'll have to make a pass through it to know for sure.
722 * most modes don't need to do this, but ones that update chunk sizes on existing files need to know whether
723 * it's padded in order to calculate the correct chunk size (currently this includes pad and strip modes).
724 */
725
726 if (NULL == (devnull = open_output(NULLDEVICE))) {
727 return FALSE;
728 }
729
730 if (!open_input_stream(info)) {
731 fclose(devnull);
732 return FALSE;
733 }
734
735 st_debug1("scanning WAVE contents to determine whether odd-sized data chunk is padded with a NULL byte per RIFF specs");
736
737 if (info->header_size + info->data_size != transfer_n_bytes(info->input,devnull,info->header_size + info->data_size,NULL)) {
738 fclose(devnull);
739 close_input_stream(info);
740 return FALSE;
741 }
742
743 nullpad[0] = 1;
744
745 if (0 == read_n_bytes(info->input,nullpad,1,NULL)) {
746 fclose(devnull);
747 close_input_stream(info);
748 return FALSE;
749 }
750
751 fclose(devnull);
752 close_input_stream(info);
753
754 st_debug1("odd-sized data chunk is%s padded with a NULL byte in file: [%s]",(0==nullpad[0])?"":" not",info->filename);
755
756 return (0 == nullpad[0]) ? TRUE : FALSE;
757 }
758
st_snprintf(char * dest,int maxlen,char * formatstr,...)759 void st_snprintf(char *dest,int maxlen,char *formatstr, ...)
760 /* acts like snprintf, but makes 100% sure the string is NULL-terminated */
761 {
762 va_list args;
763
764 va_start(args,formatstr);
765
766 st_vsnprintf(dest,maxlen,formatstr,args);
767
768 dest[maxlen-1] = 0;
769
770 va_end(args);
771 }
772
length_to_str(wave_info * info)773 void length_to_str(wave_info *info)
774 /* converts length of file to a string in m:ss or m:ss.ff format */
775 {
776 wlong newlength,rem1,rem2,frames,ms,h,m,s;
777 double tmp;
778 char ffnnn[8];
779
780 /* get total seconds plus trailing portion of seconds */
781 if (PROB_NOT_CD(info)) {
782 newlength = (wlong)info->exact_length;
783
784 tmp = info->exact_length - (double)((wlong)info->exact_length);
785 ms = (wlong)((tmp * 1000.0) + 0.5);
786
787 if (1000 == ms) {
788 ms = 0;
789 newlength++;
790 }
791
792 st_snprintf(ffnnn,8,"%03lu",ms);
793 }
794 else {
795 newlength = info->length;
796
797 rem1 = info->data_size % CD_RATE;
798 rem2 = rem1 % CD_BLOCK_SIZE;
799
800 frames = rem1 / CD_BLOCK_SIZE;
801 if (rem2 >= (CD_BLOCK_SIZE / 2))
802 frames++;
803
804 if (frames == CD_BLOCKS_PER_SEC) {
805 frames = 0;
806 newlength++;
807 }
808
809 st_snprintf(ffnnn,8,"%02lu",frames);
810 }
811
812 /* calculate h:m:s */
813 h = newlength / 3600;
814 newlength -= h * 3600;
815 m = newlength / 60;
816 newlength -= m * 60;
817 s = newlength;
818
819 if (!st_priv.show_hmmss)
820 m += h * 60;
821
822 /* now build m_ss string, with h if necessary */
823 if (st_priv.show_hmmss && h > 0)
824 st_snprintf(info->m_ss,16,"%lu:%02lu:%02lu.%s",h,m,s,ffnnn);
825 else
826 st_snprintf(info->m_ss,16,"%lu:%02lu.%s",m,s,ffnnn);
827 }
828
close_and_wait(FILE * fd,proc_info * pinfo,int child_type,format_module * fm)829 int close_and_wait(FILE *fd,proc_info *pinfo,int child_type,format_module *fm)
830 {
831 int retval;
832 #ifdef WIN32
833 DWORD exitcode;
834 #else
835 int gotpid,status;
836 char tmp[BUF_SIZE],debuginfo[BUF_SIZE];
837 #endif
838
839 retval = CLOSE_SUCCESS;
840
841 /* never close stdin/stdout/stderr */
842 if ((fd == stdin) || (fd == stdout) || (fd == stderr))
843 return retval;
844
845 if (fd) {
846 fclose(fd);
847 fd = NULL;
848 }
849
850 if (NO_CHILD_PID == pinfo->pid)
851 return retval;
852
853 /* the following seems to work fine under linux, for any decoder. but we really only need it for certain ones. */
854 if (CHILD_INPUT == child_type) {
855 /*
856 * kill input processes outright, since we're done with them. this prevents programs from stalling us too long.
857 * for example, 'la' doesn't exit when we close its stdout, but instead keeps decoding the whole file. so each
858 * time shntool opens an 'la' file, it will be decoded entirely at least four times before all is said and done,
859 * even for simple things such as checking sizes in len mode. a preemptive 'kill' here prevents this from
860 * happening. note that we can't do this for output child types, since we may unwittingly corrupt the output
861 * file in the process -- for example, we would most certainly cause problems with .shn files by killing
862 * 'shorten' while it is busy creating a seek table.
863 *
864 * this is now done on a per-format basis, with reasons given below:
865 *
866 * la : 'la' decodes entire file no matter what, every time the input file is opened (see above).
867 * aiff : 'sox' takes progressively longer as the input file becomes larger.
868 * bonk : 'bonk' decodes entire file no matter what, every time the input file is opened (see above).
869 * tta : 'ttaenc' decodes entire file no matter what, every time the input file is opened (see above).
870 */
871
872 if (fm) {
873 if (fm->kill_when_input_done) {
874 st_debug2("preemptively killing [%s] input process %d to prevent possible delays",fm->decoder,pinfo->pid);
875 #ifdef WIN32
876 TerminateProcess(pinfo->hProcess,0);
877 #else
878 kill(pinfo->pid,SIGHUP);
879 #endif
880 }
881 else {
882 st_debug2("waiting for [%s] input process %d to exit",fm->decoder,pinfo->pid);
883 }
884 }
885 else {
886 st_debug2("waiting for input process %d to exit",pinfo->pid);
887 }
888 }
889 else {
890 if (st_ops.output_format) {
891 st_debug2("waiting for [%s] output process %d to exit",st_ops.output_format->encoder,pinfo->pid);
892 }
893 else {
894 st_debug2("waiting for output process %d to exit",pinfo->pid);
895 }
896 }
897
898 #ifdef WIN32
899 if ((exitcode = WaitForSingleObject(pinfo->hProcess,INFINITE)))
900 st_debug2("WaitForSingleObject() return value: %d",exitcode);
901
902 if (GetExitCodeProcess(pinfo->hProcess,&exitcode)) {
903 st_debug2("process %d exit status: [%d]",pinfo->pid,exitcode);
904 }
905 else {
906 st_debug2("process %d exit status could not be determined",pinfo->pid);
907 }
908 #else
909 gotpid = (int)waitpid((pid_t)pinfo->pid,&status,0);
910
911 st_snprintf(debuginfo,BUF_SIZE,"process %d exit status: [%d",gotpid,WIFEXITED(status));
912 if (WIFEXITED(status)) {
913 st_snprintf(tmp,BUF_SIZE,"/%d",WEXITSTATUS(status));
914 strcat(debuginfo,tmp);
915 }
916 st_snprintf(tmp,BUF_SIZE,"] [%d",WIFSIGNALED(status));
917 strcat(debuginfo,tmp);
918 if (WIFSIGNALED(status)) {
919 st_snprintf(tmp,BUF_SIZE,"/%d",WTERMSIG(status));
920 strcat(debuginfo,tmp);
921 }
922 st_snprintf(tmp,BUF_SIZE,"] [%d",WIFSTOPPED(status));
923 strcat(debuginfo,tmp);
924 if (WIFSTOPPED(status)) {
925 st_snprintf(tmp,BUF_SIZE,"/%d",WSTOPSIG(status));
926 strcat(debuginfo,tmp);
927 }
928 strcat(debuginfo,"]");
929
930 st_debug2(debuginfo);
931 #endif
932
933 #ifdef WIN32
934 if (0 != exitcode) {
935 #else
936 if (WIFEXITED(status) && WEXITSTATUS(status)) {
937 #endif
938 if (CHILD_OUTPUT == child_type) {
939 st_warning("child encoder process %d had non-zero exit status %d",pinfo->pid,
940 #ifdef WIN32
941 exitcode);
942 #else
943 WEXITSTATUS(status));
944 #endif
945 retval = CLOSE_CHILD_ERROR_OUTPUT;
946 }
947 else if (CHILD_INPUT == child_type) {
948 retval = CLOSE_CHILD_ERROR_INPUT;
949 }
950 }
951
952 return retval;
953 }
954
955 char *st_progname()
956 {
957 return st_priv.fullprogname;
958 }
959
960 void alter_file_order(wave_info **filenames,int numfiles)
961 /* a simple menu system to change the order of the files as given on the command line */
962 {
963 int i,current,a,b;
964 char response[BUF_SIZE],
965 *p,
966 *args[3];
967 wave_info *tmp;
968 bool finished = FALSE;
969
970 /* some modes read files/split points/etc. from stdin, so we can't ask. force user to use -O option */
971 if (!isatty(fileno(stdin)))
972 st_error("standard input is not a terminal -- cannot invoke interactive file order editor");
973
974 st_info("\nEntering file order editor.\n");
975
976 while (!finished) {
977 st_info("\n");
978 st_info("File list:\n");
979 st_info("\n");
980 for (i=0;i<numfiles;i++)
981 st_info("%2d: %s\n",i+1,filenames[i]->filename);
982 st_info("\n");
983 st_info("Commands:\n");
984 st_info("\n");
985 st_info(" [s]wap a b (swaps positions a and b)\n");
986 st_info(" [m]ove a b (moves position a to position b, shifting everything in between)\n");
987 st_info(" [b]egin a (moves position a to the beginning of the list)\n");
988 st_info(" [e]nd a (moves position a to the end of the list)\n");
989 st_info(" [n]atural (sorts files using a natural sorting algorithm -- t1, t2, ... t10)\n");
990 st_info(" [a]scii (sorts files based on their ascii characters -- t1, t10, t2, ...)\n");
991 st_info(" [d]one (quits this editor, and continues with processing)\n");
992 st_info(" [q]uit (quits %s [you can also use Ctrl-C])\n",st_priv.progname);
993 st_info("\n? ");
994 fgets(response,BUF_SIZE-1,stdin);
995 if (feof(stdin))
996 strcpy(response,"done");
997 trim(response);
998 p = response;
999 current = 0;
1000 args[0] = NULL;
1001 args[1] = NULL;
1002 args[2] = NULL;
1003 while (*p) {
1004 while (' ' == *p || '\t' == *p || '\n' == *p) {
1005 *p = 0;
1006 p++;
1007 }
1008 if (0 == *p)
1009 break;
1010 if (3 > current)
1011 args[current++] = p;
1012 while (*p && ' ' != *p && '\t' != *p && '\n' != *p)
1013 p++;
1014 }
1015 if (NULL == args[0])
1016 args[0] = "";
1017
1018 st_info("\n");
1019
1020 switch (args[0][0]) {
1021 case 's':
1022 case 'S':
1023 if (NULL == args[2])
1024 st_info("Not enough positions supplied to the 'swap' command\n");
1025 else {
1026 a = atoi(args[1]);
1027 b = atoi(args[2]);
1028 if ((a<1 || a>numfiles) || (b<1 || b>numfiles))
1029 st_info("One or more of your entries are out of range. Try again.\n");
1030 else {
1031 st_info("Swapping positions %d and %d...\n",a,b);
1032 tmp = filenames[a-1];
1033 filenames[a-1] = filenames[b-1];
1034 filenames[b-1] = tmp;
1035 }
1036 }
1037 break;
1038 case 'm':
1039 case 'M':
1040 if (NULL == args[2])
1041 st_info("Not enough positions supplied to the 'move' command\n");
1042 else {
1043 a = atoi(args[1]);
1044 b = atoi(args[2]);
1045 if ((a<1 || a>numfiles) || (b<1 || b>numfiles))
1046 st_info("One or more of your entries are out of range. Try again.\n");
1047 else {
1048 st_info("Moving position %d to position %d...\n",a,b);
1049 tmp = filenames[a-1];
1050 if (a < b) {
1051 for (i=a-1;i<b-1;i++)
1052 filenames[i] = filenames[i+1];
1053 }
1054 else if (a > b) {
1055 for (i=a-1;i>b-1;i--)
1056 filenames[i] = filenames[i-1];
1057 }
1058 filenames[b-1] = tmp;
1059 }
1060 }
1061 break;
1062 case 'b':
1063 case 'B':
1064 if (NULL == args[1])
1065 st_info("Missing a position for the 'begin' command.\n");
1066 else {
1067 a = atoi(args[1]);
1068 if ((a<1 || a>numfiles))
1069 st_info("Your entry is out of range. Try again.\n");
1070 else {
1071 st_info("Moving position %d to the beginning of the list\n",a);
1072 tmp = filenames[a-1];
1073 for (i=a-1;i>0;i--)
1074 filenames[i] = filenames[i-1];
1075 filenames[0] = tmp;
1076 }
1077 }
1078 break;
1079 case 'e':
1080 case 'E':
1081 if (NULL == args[1])
1082 st_info("Missing a position for the 'end' command.\n");
1083 else {
1084 a = atoi(args[1]);
1085 if ((a<1 || a>numfiles))
1086 st_info("Your entry is out of range. Try again.\n");
1087 else {
1088 st_info("Moving position %d to the end of the list...\n",a);
1089 tmp = filenames[a-1];
1090 for (i=a-1;i<numfiles-1;i++)
1091 filenames[i] = filenames[i+1];
1092 filenames[numfiles-1] = tmp;
1093 }
1094 }
1095 break;
1096 case 'n':
1097 case 'N':
1098 version_sort_files(filenames,numfiles);
1099 break;
1100 case 'a':
1101 case 'A':
1102 ascii_sort_files(filenames,numfiles);
1103 break;
1104 case 'd':
1105 case 'D':
1106 st_info("Exiting file order editor.\n");
1107 finished = TRUE;
1108 break;
1109 case 'q':
1110 case 'Q':
1111 st_info("Quitting %s.\n",st_priv.progname);
1112 exit(ST_EXIT_QUIT);
1113 break;
1114 default:
1115 st_info("Unrecognized command: %s\n",args[0]);
1116 break;
1117 }
1118 }
1119
1120 st_info("\n");
1121 }
1122
1123 wlong smrt_parse(unsigned char *data,wave_info *info)
1124 /* currently not so smart, so I gave it the Homer Simpson spelling :) */
1125 {
1126 wlong bytes;
1127 unsigned char tmp[BUF_SIZE];
1128
1129 strcpy((char *)tmp,(const char *)data);
1130
1131 /* check for all digits */
1132 if (-1 != (bytes = is_numeric(tmp)))
1133 return bytes;
1134
1135 /* check for m:ss */
1136 if (-1 != (bytes = is_m_ss(tmp,info)))
1137 return bytes;
1138
1139 /* check for m:ss.ff */
1140 if (-1 != (bytes = is_m_ss_ff(tmp,info)))
1141 return bytes;
1142
1143 /* check for m:ss.nnn */
1144 if (-1 != (bytes = is_m_ss_nnn(tmp,info)))
1145 return bytes;
1146
1147 /* it was not in any of these formats */
1148
1149 st_error("value not in bytes, m:ss, m:ss.ff, or m:ss.nnn format: [%s]",data);
1150
1151 return -1;
1152 }
1153
1154 int st_getopt(int argc,char **argv,char *mode_opts)
1155 {
1156 char ops[BUF_SIZE],*p,c[2],*global_opts;
1157 int opt;
1158 format_module *input_format = NULL;
1159
1160 global_opts = (st_priv.mode->creates_files) ? GLOBAL_OPTS GLOBAL_OPTS_OUTPUT : GLOBAL_OPTS;
1161
1162 /* make sure the calling mode isn't using an option reserved for shntool global use */
1163 c[1] = 0;
1164 for (p=global_opts;*p;p++) {
1165 if (':' == *p)
1166 continue;
1167 c[0] = *p;
1168 if (strstr(mode_opts,c))
1169 st_error("mode attempted to use global option: [-%s]",c);
1170 }
1171
1172 if (st_priv.mode->creates_files && (NULL == st_ops.output_format))
1173 st_ops.output_format = default_output_format();
1174
1175 st_snprintf(ops,BUF_SIZE,"%s%s",global_opts,mode_opts);
1176
1177 opt = getopt(argc,argv,ops);
1178
1179 if (-1 == opt)
1180 return opt;
1181
1182 /* handle global options before mode options */
1183 switch (opt) {
1184 case 'D':
1185 st_priv.debug_level++;
1186 break;
1187 case 'F':
1188 if (NULL == optarg)
1189 st_help("missing input file filename");
1190 st_input.type = INPUT_FILE;
1191 st_input.filename_source = optarg;
1192 break;
1193 case 'H':
1194 st_priv.show_hmmss = TRUE;
1195 break;
1196 case 'P':
1197 if (NULL == optarg)
1198 st_help("missing progress indicator type");
1199 if (!strcmp(optarg,"pct"))
1200 st_priv.progress_type = PROGRESS_PERCENT;
1201 else if (!strcmp(optarg,"dot"))
1202 st_priv.progress_type = PROGRESS_DOT;
1203 else if (!strcmp(optarg,"spin"))
1204 st_priv.progress_type = PROGRESS_SPIN;
1205 else if (!strcmp(optarg,"face"))
1206 st_priv.progress_type = PROGRESS_FACE;
1207 else if (!strcmp(optarg,"none"))
1208 st_priv.progress_type = PROGRESS_NONE;
1209 else
1210 st_help("invalid progress indicator type: [%s]",optarg);
1211 break;
1212 case '?':
1213 case 'h':
1214 if ('?' == opt)
1215 st_info("\n");
1216 if (st_priv.mode->run_help)
1217 st_priv.mode->run_help();
1218 st_global_usage();
1219 exit(('?' == opt) ? ST_EXIT_ERROR : ST_EXIT_SUCCESS);
1220 break;
1221 case 'i':
1222 if (NULL == optarg)
1223 st_help("missing input format");
1224 if (NULL == (input_format = find_format(optarg)))
1225 st_help("missing input format name");
1226 if (!input_format->supports_input)
1227 st_help("format does not support input: [%s]",input_format->name);
1228 parse_input_args_cmd(input_format,optarg);
1229 break;
1230 case 'q':
1231 st_priv.suppress_stderr = TRUE;
1232 break;
1233 case 'r':
1234 if (NULL == optarg)
1235 st_help("missing reorder type");
1236 if (!strcmp(optarg,"ask"))
1237 st_priv.reorder_type = ORDER_ASK;
1238 else if (!strcmp(optarg,"ascii"))
1239 st_priv.reorder_type = ORDER_ASCII;
1240 else if (!strcmp(optarg,"natural"))
1241 st_priv.reorder_type = ORDER_NATURAL;
1242 else if (!strcmp(optarg,"none"))
1243 st_priv.reorder_type = ORDER_AS_IS;
1244 else
1245 st_help("invalid reorder type: [%s]",optarg);
1246 break;
1247 case 'v':
1248 st_version();
1249 exit(ST_EXIT_SUCCESS);
1250 break;
1251 case 'w':
1252 st_priv.suppress_warnings = TRUE;
1253 break;
1254 }
1255
1256 if (st_priv.mode->creates_files) {
1257 switch (opt) {
1258 case 'O':
1259 if (NULL == optarg)
1260 st_help("missing overwrite action");
1261 if (!strcmp(optarg,"ask"))
1262 st_priv.clobber_action = CLOBBER_ACTION_ASK;
1263 else if (!strcmp(optarg,"always"))
1264 st_priv.clobber_action = CLOBBER_ACTION_ALWAYS;
1265 else if (!strcmp(optarg,"never"))
1266 st_priv.clobber_action = CLOBBER_ACTION_NEVER;
1267 else
1268 st_help("invalid overwrite action: [%s]",optarg);
1269 break;
1270 case 'a':
1271 if (NULL == optarg)
1272 st_help("missing filename prefix");
1273 st_ops.output_prefix = optarg;
1274 break;
1275 case 'd':
1276 if (NULL == optarg)
1277 st_help("missing output directory");
1278 st_ops.output_directory = optarg;
1279 break;
1280 case 'o':
1281 if (NULL == optarg)
1282 st_help("missing output format");
1283 if (NULL == (st_ops.output_format = find_format(optarg)))
1284 st_help("missing output format name");
1285 if (!st_ops.output_format->supports_output)
1286 st_help("format does not support output: [%s]",st_ops.output_format->name);
1287 parse_output_args_cmd(st_ops.output_format,optarg);
1288 break;
1289 case 'z':
1290 if (NULL == optarg)
1291 st_help("missing filename postfix");
1292 st_ops.output_postfix = optarg;
1293 break;
1294 }
1295 }
1296
1297 return opt;
1298 }
1299
1300 void discard_header(wave_info *info)
1301 {
1302 unsigned char *header;
1303
1304 if (NULL == (header = malloc(info->header_size * sizeof(unsigned char))))
1305 st_error("could not allocate %d bytes for WAVE header",info->header_size);
1306
1307 if (read_n_bytes(info->input,header,info->header_size,NULL) != info->header_size)
1308 st_error("error while discarding %d-byte header from file: [%s]",info->header_size,info->filename);
1309
1310 st_free(header);
1311 }
1312
1313 void create_output_filename(char *infile,char *inext,char *outfile)
1314 {
1315 strcpy(outfile,"");
1316
1317 if (st_ops.output_format) {
1318 if (st_ops.output_format->create_output_filename) {
1319 st_ops.output_format->create_output_filename(outfile);
1320 return;
1321 }
1322
1323 rename_by_extension(
1324 (st_ops.output_format->encoder) ? &st_ops.output_format->output_args_template : NULL,
1325 infile,outfile,inext,st_ops.output_format->extension);
1326 }
1327 }
1328
1329 FILE *open_output_stream(char *outfile,proc_info *pinfo)
1330 {
1331 pinfo->pid = NO_CHILD_PID;
1332
1333 if (st_ops.output_format && st_ops.output_format->supports_output) {
1334 /* if this format defines its own output function, run it */
1335 if (st_ops.output_format->output_func)
1336 return st_ops.output_format->output_func(outfile,pinfo);
1337
1338 return launch_output(st_ops.output_format,outfile,pinfo);
1339 }
1340
1341 return NULL;
1342 }
1343
1344 void st_global_usage()
1345 {
1346 st_info("Global options:\n");
1347 st_info("\n");
1348 st_info(" -D print debugging information (each one increases debugging level)\n");
1349 st_info(" -F file get input filenames from file, instead of command line or terminal\n");
1350 st_info(" -H print times in h:mm:ss.{ff,nnn} format, instead of m:ss.{ff,nnn}\n");
1351 if (st_priv.mode->creates_files) {
1352 st_info(" -O val overwrite existing files? val is: {[ask], always, never}\n");
1353 }
1354 st_info(" -P type progress indicator type. type is: {[pct], dot, spin, face, none}\n");
1355 if (st_priv.mode->creates_files) {
1356 st_info(" -a str prefix 'str' to base part of output filenames\n");
1357 st_info(" -d dir specify output directory\n");
1358 }
1359 st_info(" -i fmt specify input file format decoder and/or arguments.\n");
1360 st_info(" format is: \"fmt decoder [arg1 ... argN (%s = filename)]\"\n",FILENAME_PLACEHOLDER);
1361 if (st_priv.mode->creates_files) {
1362 st_info(" -o fmt specify output file format, extension, encoder and/or arguments.\n");
1363 st_info(" format is: \"fmt [ext=abc] [encoder [arg1 ... argN (%s = filename)]]\"\n",FILENAME_PLACEHOLDER);
1364 }
1365 st_info(" -q suppress non-critical output (quiet mode)\n");
1366 st_info(" -r val reorder input files? val is: {ask, ascii, [natural], none}\n");
1367 st_info(" -v show version information\n");
1368 st_info(" -w suppress warnings\n");
1369 if (st_priv.mode->creates_files) {
1370 st_info(" -z str postfix 'str' to base part of output filenames\n");
1371 }
1372 st_info(" -- indicates that everything following it is a filename\n");
1373 st_info("\n");
1374 }
1375
1376 void arg_init(format_module *fm)
1377 {
1378 char *p,envname[BUF_SIZE],upper[BUF_SIZE];
1379
1380 strcpy(upper,fm->name);
1381
1382 /* convert format name to upper case */
1383 for (p=upper;*p;p++) {
1384 if (*p >= 'a' && *p <= 'z')
1385 *p = *p - 32;
1386 }
1387
1388 if (fm->supports_input) {
1389 /* initialize with format defaults */
1390 parse_input_args_fmt(fm,fm->decoder_args);
1391
1392 /* check environment for user-specified decoder */
1393 st_snprintf(envname,BUF_SIZE,"ST_%s_DEC",upper);
1394 parse_input_args_env(fm,scan_env(envname));
1395 }
1396
1397 if (fm->supports_output) {
1398 /* initialize with format defaults */
1399 parse_output_args_fmt(fm,fm->encoder_args);
1400
1401 /* check environment for user-specified extension and/or encoder */
1402 st_snprintf(envname,BUF_SIZE,"ST_%s_ENC",upper);
1403 parse_output_args_env(fm,scan_env(envname));
1404 }
1405 }
1406
1407 void arg_add(child_args *child_args,char *arg)
1408 {
1409 if (child_args->num_args >= MAX_CHILD_ARGS)
1410 st_error("too many arguments specified -- limit is %d",MAX_CHILD_ARGS);
1411
1412 child_args->args[child_args->num_args] = arg;
1413 child_args->num_args++;
1414 }
1415
1416 void arg_reset(child_args *child_args)
1417 {
1418 int i;
1419
1420 child_args->num_args = 0;
1421
1422 for (i=0;i<MAX_CHILD_ARGS;i++)
1423 child_args->args[i] = NULL;
1424 }
1425
1426 void arg_replace(child_args *child_args,int pos,char *arg)
1427 {
1428 child_args->args[pos] = arg;
1429 }
1430
1431 void reorder_files(wave_info **files,int numfiles)
1432 {
1433 switch (st_priv.reorder_type) {
1434 case ORDER_ASK:
1435 alter_file_order(files,numfiles);
1436 break;
1437 case ORDER_NATURAL:
1438 version_sort_files(files,numfiles);
1439 break;
1440 case ORDER_ASCII:
1441 ascii_sort_files(files,numfiles);
1442 break;
1443 case ORDER_AS_IS:
1444 default:
1445 break;
1446 }
1447 }
1448
1449 static void prog_print_data(progress_info *proginfo)
1450 {
1451 if (proginfo->prefix) {
1452 st_info("%s ",proginfo->prefix);
1453 }
1454
1455 if (proginfo->filename1) {
1456 st_info("[%s] ",proginfo->filename1);
1457 if (proginfo->filedesc1) {
1458 st_info("(%s) ",proginfo->filedesc1);
1459 }
1460 }
1461
1462 if (proginfo->clause) {
1463 st_info("%s ",proginfo->clause);
1464 }
1465
1466 if (proginfo->filename2) {
1467 st_info("[%s] ",proginfo->filename2);
1468 if (proginfo->filedesc2) {
1469 st_info("(%s) ",proginfo->filedesc2);
1470 }
1471 }
1472
1473 st_info(": ");
1474 }
1475
1476 static void prog_init(progress_info *proginfo)
1477 {
1478 if (!proginfo->initialized) {
1479 proginfo->bytes_written = 0;
1480 proginfo->dot_step = -1;
1481 proginfo->last_percent = -1;
1482 proginfo->progress_shown = FALSE;
1483 prog_print_data(proginfo);
1484 proginfo->initialized = TRUE;
1485 st_priv.screen_dirty = FALSE;
1486 }
1487
1488 if (st_priv.screen_dirty) {
1489 proginfo->last_percent = -1;
1490 proginfo->progress_shown = FALSE;
1491 prog_print_data(proginfo);
1492 st_priv.screen_dirty = FALSE;
1493 }
1494 }
1495
1496 static void prog_uninit(progress_info *proginfo)
1497 {
1498 proginfo->initialized = FALSE;
1499 proginfo->progress_shown = FALSE;
1500 proginfo->bytes_written = 0;
1501 proginfo->bytes_total = 0;
1502 proginfo->dot_step = 0;
1503 proginfo->percent = 0;
1504 proginfo->last_percent = 0;
1505 }
1506
1507 static void prog_erase_chars(progress_info *proginfo,int erasecnt)
1508 {
1509 int i;
1510
1511 if (!proginfo->progress_shown) {
1512 proginfo->progress_shown = TRUE;
1513 return;
1514 }
1515
1516 for (i=0;i<erasecnt;i++)
1517 st_info("\b");
1518 }
1519
1520 static void prog_show_pct(progress_info *proginfo)
1521 {
1522 prog_erase_chars(proginfo,5);
1523
1524 st_info("%3d%% ",proginfo->percent);
1525 }
1526
1527 static void prog_show_dot(progress_info *proginfo)
1528 {
1529 if (-1 == proginfo->dot_step)
1530 proginfo->dot_step = 0;
1531
1532 while ((proginfo->dot_step + 10) <= proginfo->percent) {
1533 st_info(".");
1534 proginfo->dot_step += 10;
1535 }
1536 }
1537
1538 static void prog_show_spin(progress_info *proginfo)
1539 {
1540 char spin[4] = { '|', '/', '-', '\\' };
1541
1542 while ((proginfo->dot_step + 1) <= proginfo->percent) {
1543 proginfo->dot_step++;
1544 prog_erase_chars(proginfo,2);
1545 st_info("%c ",spin[proginfo->dot_step % 4]);
1546 }
1547 }
1548
1549 static void prog_show_face(progress_info *proginfo)
1550 {
1551 char *faces[6] = { ":-(", ":-/", ":-\\", ":-|", ":-)", ":-D" };
1552 bool show_face = FALSE;
1553
1554 if (-1 == proginfo->dot_step)
1555 proginfo->dot_step = -20;
1556
1557 while ((proginfo->dot_step + 20) <= proginfo->percent) {
1558 proginfo->dot_step += 20;
1559 show_face = TRUE;
1560 }
1561
1562 if (!show_face)
1563 return;
1564
1565 prog_erase_chars(proginfo,4);
1566
1567 st_info("%s ",faces[proginfo->dot_step/20]);
1568 }
1569
1570 static void prog_finish(char *status,progress_info *proginfo)
1571 {
1572 if (PROGRESS_DOT == st_priv.progress_type)
1573 st_info(" ");
1574
1575 st_info("%s\n",status);
1576
1577 prog_uninit(proginfo);
1578 }
1579
1580 void prog_update(progress_info *proginfo)
1581 {
1582 prog_init(proginfo);
1583
1584 if (proginfo->bytes_total <= 0)
1585 proginfo->percent = 0;
1586 else
1587 proginfo->percent = (int)(100.0 * ((double)proginfo->bytes_written)/((double)proginfo->bytes_total));
1588
1589 proginfo->percent = min(max(proginfo->percent,0),100);
1590
1591 if (proginfo->percent <= proginfo->last_percent)
1592 return;
1593
1594 switch (st_priv.progress_type) {
1595 case PROGRESS_PERCENT:
1596 prog_show_pct(proginfo);
1597 break;
1598 case PROGRESS_DOT:
1599 prog_show_dot(proginfo);
1600 break;
1601 case PROGRESS_SPIN:
1602 prog_show_spin(proginfo);
1603 break;
1604 case PROGRESS_FACE:
1605 prog_show_face(proginfo);
1606 break;
1607 case PROGRESS_NONE:
1608 default:
1609 break;
1610 }
1611
1612 proginfo->last_percent = proginfo->percent;
1613 }
1614
1615 void prog_success(progress_info *proginfo)
1616 {
1617 proginfo->bytes_written = proginfo->bytes_total;
1618
1619 prog_update(proginfo);
1620
1621 prog_finish("OK",proginfo);
1622 }
1623
1624 void prog_error(progress_info *proginfo)
1625 {
1626 prog_update(proginfo);
1627
1628 prog_finish("ERROR",proginfo);
1629 }
1630
1631 void input_init(int argn,int argc,char **argv)
1632 {
1633 if (INPUT_FILE != st_input.type && INPUT_INTERNAL != st_input.type) {
1634 if (argn >= argc) {
1635 st_input.type = INPUT_STDIN;
1636 }
1637 else {
1638 st_input.type = INPUT_CMDLINE;
1639 }
1640 }
1641
1642 switch (st_input.type) {
1643 case INPUT_CMDLINE:
1644 st_debug1("reading input filenames from command line");
1645 st_input.argn = argn;
1646 st_input.argc = argc;
1647 st_input.argv = argv;
1648 break;
1649
1650 case INPUT_STDIN:
1651 st_debug1("reading input filenames from stdin");
1652 if (isatty(fileno(stdin)))
1653 st_info("enter input filename(s):\n");
1654 st_input.fd = stdin;
1655 break;
1656
1657 case INPUT_FILE:
1658 st_debug1("reading input filenames from file: [%s]",st_input.filename_source);
1659 if (NULL == (st_input.fd = fopen(st_input.filename_source,"rb"))) {
1660 st_error("could not open input filename file: [%s]",st_input.filename_source);
1661 }
1662 break;
1663
1664 case INPUT_INTERNAL:
1665 st_input.filecur = 0;
1666 break;
1667
1668 default:
1669 break;
1670 }
1671 }
1672
1673 char *input_get_filename()
1674 {
1675 static char internal_filename[FILENAME_SIZE];
1676 char *filename = NULL;
1677
1678 switch (st_input.type) {
1679 case INPUT_CMDLINE:
1680 if (st_input.argn < st_input.argc) {
1681 filename = st_input.argv[st_input.argn];
1682 st_input.argn++;
1683 }
1684 break;
1685
1686 case INPUT_STDIN:
1687 case INPUT_FILE:
1688 fgets(internal_filename,FILENAME_SIZE-1,st_input.fd);
1689 if (!feof(st_input.fd)) {
1690 trim(internal_filename);
1691 filename = internal_filename;
1692 }
1693 else {
1694 if (INPUT_FILE == st_input.type) {
1695 fclose(st_input.fd);
1696 }
1697 }
1698 break;
1699
1700 case INPUT_INTERNAL:
1701 if (st_input.filecur < st_input.filemax) {
1702 st_debug1("returning file %d: [%s]",st_input.filecur,st_input.filenames[st_input.filecur]);
1703 filename = st_input.filenames[st_input.filecur];
1704 st_input.filecur++;
1705 }
1706 break;
1707
1708 default:
1709 break;
1710 }
1711
1712 return filename;
1713 }
1714
1715 void input_read_all_files()
1716 {
1717 char *filename;
1718
1719 st_input.filemax = 0;
1720
1721 while ((filename = input_get_filename())) {
1722 st_input.filenames[st_input.filemax] = strdup(filename);
1723 st_input.filemax++;
1724 if (st_input.filemax >= MAX_FILENAMES)
1725 st_error("exceeded maximum number of filenames: [%s]",MAX_FILENAMES);
1726 }
1727
1728 st_input.type = INPUT_INTERNAL;
1729 st_input.filecur = 0;
1730 }
1731
1732 int input_get_file_count()
1733 {
1734 return st_input.filemax;
1735 }
1736