1 /*  mode_fix.c - fix mode module
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 "mode.h"
20 
21 CVSID("$Id: mode_fix.c,v 1.115 2009/03/16 04:46:03 jason Exp $")
22 
23 static bool fix_main(int,char **);
24 static void fix_help(void);
25 
26 mode_module mode_fix = {
27   "fix",
28   "shnfix",
29   "Fixes sector-boundary problems with CD-quality PCM WAVE data",
30   CVSIDSTR,
31   TRUE,
32   fix_main,
33   fix_help
34 };
35 
36 #define FIX_POSTFIX "-fixed"
37 
38 typedef enum {
39   SHIFT_UNKNOWN,
40   SHIFT_BACKWARD,
41   SHIFT_FORWARD,
42   SHIFT_ROUND
43 } fix_shifts;
44 
45 static bool pad = TRUE;
46 static bool skip = TRUE;
47 static bool check_only = FALSE;
48 static int pad_bytes = 0;
49 static int desired_shift = SHIFT_UNKNOWN;
50 static int numfiles;
51 
52 static wave_info **files;
53 
fix_help()54 static void fix_help()
55 {
56   st_info("Usage: %s [OPTIONS] [file1 ...]\n",st_progname());
57   st_info("\n");
58   st_info("Mode-specific options:\n");
59   st_info("\n");
60   st_info("  -b      shift track breaks backward to previous sector boundary (default)\n");
61   st_info("  -c      check whether fixing is needed\n");
62   st_info("  -f      shift track breaks forward to next sector boundary\n");
63   st_info("  -h      show this help screen\n");
64   st_info("  -k      don't skip initial unchanged files\n");
65   st_info("  -n      don't pad the last file with silence\n");
66   st_info("  -u      round track breaks to the nearest sector boundary\n");
67   st_info("\n");
68 }
69 
parse(int argc,char ** argv,int * first_arg)70 static void parse(int argc,char **argv,int *first_arg)
71 {
72   int c;
73 
74   st_ops.output_directory = CURRENT_DIR;
75   st_ops.output_postfix = FIX_POSTFIX;
76   desired_shift = SHIFT_BACKWARD;
77 
78   while ((c = st_getopt(argc,argv,"bcfknu")) != -1) {
79     switch (c) {
80       case 'b':
81         desired_shift = SHIFT_BACKWARD;
82         break;
83       case 'c':
84         check_only = TRUE;
85         break;
86       case 'f':
87         desired_shift = SHIFT_FORWARD;
88         break;
89       case 'k':
90         skip = FALSE;
91         break;
92       case 'n':
93         pad = FALSE;
94         break;
95       case 'u':
96         desired_shift = SHIFT_ROUND;
97         break;
98     }
99   }
100 
101   *first_arg = optind;
102 }
103 
calculate_breaks_backward()104 static void calculate_breaks_backward()
105 {
106   int i,remainder = 0;
107   wlong tmp,begin_before = 0,begin_after = 0;
108 
109   for (i=0;i<numfiles-1;i++) {
110     files[i]->beginning_byte = begin_before;
111     files[i]->new_beginning_byte = begin_after;
112 
113     tmp = files[i]->data_size + remainder;
114     remainder = tmp % CD_BLOCK_SIZE;
115     files[i]->new_data_size = tmp - remainder;
116 
117     begin_before += files[i]->data_size;
118     begin_after += files[i]->new_data_size;
119   }
120   files[numfiles-1]->new_data_size = files[numfiles-1]->data_size + remainder;
121   files[numfiles-1]->beginning_byte = begin_before;
122   files[numfiles-1]->new_beginning_byte = begin_after;
123 }
124 
calculate_breaks_forward()125 static void calculate_breaks_forward()
126 {
127   int i,used = 0,remainder;
128   wlong tmp,begin_before = 0,begin_after = 0;
129 
130   for (i=0;i<numfiles-1;i++) {
131     files[i]->beginning_byte = begin_before;
132     files[i]->new_beginning_byte = begin_after;
133 
134     tmp = files[i]->data_size - used;
135     remainder = tmp % CD_BLOCK_SIZE;
136     if (remainder) {
137       used = CD_BLOCK_SIZE - remainder;
138       files[i]->new_data_size = tmp + used;
139     }
140     else {
141       used = 0;
142       files[i]->new_data_size = tmp;
143     }
144 
145     begin_before += files[i]->data_size;
146     begin_after += files[i]->new_data_size;
147   }
148   files[numfiles-1]->new_data_size = files[numfiles-1]->data_size - used;
149   files[numfiles-1]->beginning_byte = begin_before;
150   files[numfiles-1]->new_beginning_byte = begin_after;
151 }
152 
calculate_breaks_round()153 static void calculate_breaks_round()
154 {
155   int i,give_or_take = 0;
156   wlong tmp,how_much,begin_before = 0,begin_after = 0;
157 
158   for (i=0;i<numfiles-1;i++) {
159     files[i]->beginning_byte = begin_before;
160     files[i]->new_beginning_byte = begin_after;
161 
162     tmp = files[i]->data_size + give_or_take;
163     how_much = (((tmp + CD_BLOCK_SIZE/2) / CD_BLOCK_SIZE) * CD_BLOCK_SIZE);
164     give_or_take = tmp - how_much;
165     files[i]->new_data_size = how_much;
166 
167     begin_before += files[i]->data_size;
168     begin_after += files[i]->new_data_size;
169   }
170   files[numfiles-1]->new_data_size = files[numfiles-1]->data_size + give_or_take;
171   files[numfiles-1]->beginning_byte = begin_before;
172   files[numfiles-1]->new_beginning_byte = begin_after;
173 }
174 
calculation_sanity_check()175 static void calculation_sanity_check()
176 {
177   int i;
178   wlong old_total = 0,new_total = 0;
179 
180   for (i=0;i<numfiles;i++) {
181     old_total += files[i]->data_size;
182     new_total += files[i]->new_data_size;
183   }
184 
185   if (old_total != new_total) {
186     st_warning("total WAVE data size differs from newly calculated total --\n"
187                "please file a bug report with the following data:");
188 
189     st_info("\n");
190     st_info("Shift type: %s\n",(SHIFT_BACKWARD == desired_shift)?"backward":
191                                       ((SHIFT_FORWARD == desired_shift)?"forward":
192                                       ((SHIFT_ROUND == desired_shift)?"round":"unknown")));
193 
194     st_info("\n");
195     for (i=0;i<numfiles;i++)
196       st_info("file %2d:  data size = %10lu, new data size = %10lu\n",i+1,files[i]->data_size,files[i]->new_data_size);
197 
198     st_info("\n");
199     st_info("totals :  data size = %10lu, new data size = %10lu\n",old_total,new_total);
200 
201     exit(ST_EXIT_ERROR);
202   }
203 }
204 
reopen_input_file(int i,progress_info * proginfo)205 static bool reopen_input_file(int i,progress_info *proginfo)
206 {
207   unsigned char *header;
208 
209   if (!open_input_stream(files[i])) {
210     prog_error(proginfo);
211     st_warning("could not reopen input file: [%s]",files[i]->filename);
212     return FALSE;
213   }
214 
215   if (NULL == (header = malloc(files[i]->header_size * sizeof(unsigned char)))) {
216     prog_error(proginfo);
217     st_warning("could not allocate %d-byte WAVE header",files[i]->header_size);
218     return FALSE;
219   }
220 
221   if (read_n_bytes(files[i]->input,header,files[i]->header_size,NULL) != files[i]->header_size) {
222     prog_error(proginfo);
223     st_warning("error while reading %d-byte WAVE header",files[i]->header_size);
224     st_free(header);
225     return FALSE;
226   }
227 
228   st_free(header);
229 
230   return TRUE;
231 }
232 
open_this_file(int i,char * outfilename,progress_info * proginfo)233 static bool open_this_file(int i,char *outfilename,progress_info *proginfo)
234 {
235   unsigned char header[CANONICAL_HEADER_SIZE];
236 
237   create_output_filename(files[i]->filename,files[i]->input_format->extension,outfilename);
238 
239   proginfo->filename1 = files[i]->filename;
240   proginfo->filedesc1 = files[i]->m_ss;
241   proginfo->filename2 = outfilename;
242 
243   if (NULL == (files[i]->output = open_output_stream(outfilename,&files[i]->output_proc))) {
244     prog_error(proginfo);
245     st_error("could not open output file: [%s]",outfilename);
246   }
247 
248   make_canonical_header(header,files[i]);
249 
250   if ((numfiles - 1 == i) && pad)
251     put_data_size(header,CANONICAL_HEADER_SIZE,files[i]->new_data_size+pad_bytes);
252   else {
253     put_data_size(header,CANONICAL_HEADER_SIZE,files[i]->new_data_size);
254     if (files[i]->new_data_size & 1)
255       put_chunk_size(header,(files[i]->new_data_size + 1) + CANONICAL_HEADER_SIZE - 8);
256   }
257 
258   if (write_n_bytes(files[i]->output,header,CANONICAL_HEADER_SIZE,proginfo) != CANONICAL_HEADER_SIZE) {
259     prog_error(proginfo);
260     st_warning("error while writing %d-byte WAVE header",CANONICAL_HEADER_SIZE);
261     return FALSE;
262   }
263 
264   proginfo->filename2 = outfilename;
265   proginfo->bytes_total = files[i]->new_data_size + CANONICAL_HEADER_SIZE;
266 
267   return TRUE;
268 }
269 
write_fixed_files()270 static bool write_fixed_files()
271 {
272   int cur_input,cur_output;
273   unsigned long bytes_have,bytes_needed,bytes_to_xfer;
274   char outfilename[FILENAME_SIZE];
275   bool success;
276   progress_info proginfo;
277 
278   success = FALSE;
279 
280   cur_input = cur_output = 0;
281   bytes_have = (unsigned long)files[cur_input]->data_size;
282   bytes_needed = (unsigned long)files[cur_output]->new_data_size;
283 
284   proginfo.initialized = FALSE;
285   proginfo.prefix = "Fixing";
286   proginfo.clause = "-->";
287   proginfo.filename1 = files[0]->filename;
288   proginfo.filedesc1 = files[0]->m_ss;
289   proginfo.filename2 = NULL;
290   proginfo.filedesc2 = NULL;
291   proginfo.bytes_total = 1;
292 
293   if (!open_this_file(cur_output,outfilename,&proginfo))
294     goto cleanup;
295 
296   if (!reopen_input_file(cur_input,&proginfo))
297     goto cleanup;
298 
299   while (cur_input < numfiles && cur_output < numfiles) {
300     bytes_to_xfer = min(bytes_have,bytes_needed);
301 
302     if (transfer_n_bytes(files[cur_input]->input,files[cur_output]->output,bytes_to_xfer,&proginfo) != bytes_to_xfer) {
303       prog_error(&proginfo);
304       st_warning("error while transferring %lu bytes of data",bytes_to_xfer);
305       goto cleanup;
306     }
307 
308     bytes_have -= bytes_to_xfer;
309     bytes_needed -= bytes_to_xfer;
310 
311     if (0 == bytes_have) {
312       close_input_stream(files[cur_input]);
313       files[cur_input]->input = NULL;
314       cur_input++;
315       if (cur_input < numfiles) {
316         bytes_have = (unsigned long)files[cur_input]->data_size;
317 
318         if (!reopen_input_file(cur_input,&proginfo))
319           goto cleanup;
320       }
321     }
322 
323     if (0 == bytes_needed) {
324       prog_success(&proginfo);
325 
326       if (numfiles - 1 == cur_output) {
327         if (pad) {
328           if (pad_bytes) {
329             if (pad_bytes != write_padding(files[cur_output]->output,pad_bytes,NULL)) {
330               prog_error(&proginfo);
331               st_warning("error while padding with %d zero-bytes",pad_bytes);
332               goto cleanup;
333             }
334             st_info("Padded last file with %d zero-bytes.\n",pad_bytes);
335           }
336           else
337             st_info("No padding needed.\n");
338         }
339         else {
340           st_info("Last file was not padded, ");
341           if (pad_bytes)
342             st_info("though it needs %d bytes of padding.\n",pad_bytes);
343           else
344             st_info("nor was it needed.\n");
345 
346           if ((files[cur_output]->new_data_size & 1) && (1 != write_padding(files[cur_output]->output,1,NULL))) {
347             prog_error(&proginfo);
348             st_warning("error while NULL-padding odd-sized data chunk");
349             goto cleanup;
350           }
351         }
352       }
353 
354       close_output_stream(files[cur_output]);
355       files[cur_output]->output = NULL;
356       cur_output++;
357       if (cur_output < numfiles) {
358         bytes_needed = (unsigned long)files[cur_output]->new_data_size;
359 
360         if (!open_this_file(cur_output,outfilename,&proginfo))
361           goto cleanup;
362       }
363     }
364   }
365 
366   success = TRUE;
367 
368 cleanup:
369   if (!success) {
370     close_output_stream(files[cur_output]);
371     remove_file(outfilename);
372     st_error("failed to fix files");
373   }
374 
375   return success;
376 }
377 
process(int argc,char ** argv,int start)378 static bool process(int argc,char **argv,int start)
379 {
380   int i,j,remainder;
381   bool needs_fixing = FALSE,found_errors = FALSE,success;
382   char *filename;
383 
384   input_init(start,argc,argv);
385   input_read_all_files();
386   numfiles = input_get_file_count();
387 
388   if (numfiles < 1)
389     st_help("need one or more files to process");
390 
391   if (NULL == (files = malloc((numfiles + 1) * sizeof(wave_info *))))
392     st_error("could not allocate memory for file info array");
393 
394   for (i=0;i<numfiles;i++) {
395     filename = input_get_filename();
396     if (NULL == (files[i] = new_wave_info(filename))) {
397       st_error("could not open file: [%s]",filename);
398     }
399   }
400 
401   files[numfiles] = NULL;
402 
403   /* validate files */
404   for (i=0;i<numfiles;i++) {
405     if (PROB_NOT_CD(files[i])) {
406       st_warning("file is not CD-quality: [%s]",files[i]->filename);
407       found_errors = TRUE;
408     }
409     if (PROB_HDR_INCONSISTENT(files[i])) {
410       st_warning("file has an inconsistent header: [%s]",files[i]->filename);
411       found_errors = TRUE;
412     }
413     if (PROB_TRUNCATED(files[i])) {
414       st_warning("file seems to be truncated: [%s]",files[i]->filename);
415       found_errors = TRUE;
416     }
417     if (PROB_BAD_BOUND(files[i]))
418       needs_fixing = TRUE;
419   }
420 
421   if (found_errors)
422     st_error("could not fix files due to errors, see above");
423 
424   if (check_only)
425     exit(needs_fixing ? ST_EXIT_SUCCESS : ST_EXIT_ERROR);
426 
427   if (!needs_fixing)
428     st_error("everything seems fine, no need for fixing");
429 
430   reorder_files(files,numfiles);
431 
432   i = 0;
433   while (!PROB_BAD_BOUND(files[i]))
434     i++;
435 
436   if (skip) {
437     if (i != 0) {
438       st_warning("skipping first %d file%s because %s would not be changed",i,(1==i)?"":"s",(1==i)?"it":"they");
439       for (j=0;j<i;j++)
440         st_free(files[j]);
441       for (j=i;j<numfiles;j++) {
442         files[j-i] = files[j];
443         files[j] = NULL;
444       }
445       numfiles -= i;
446     }
447   }
448 
449   if (numfiles < 1)
450     st_error("need one or more files to process");
451 
452   switch (desired_shift) {
453     case SHIFT_BACKWARD:
454       calculate_breaks_backward();
455       break;
456     case SHIFT_FORWARD:
457       calculate_breaks_forward();
458       break;
459     case SHIFT_ROUND:
460       calculate_breaks_round();
461       break;
462   }
463 
464   calculation_sanity_check();
465 
466   remainder = files[numfiles-1]->new_data_size % CD_BLOCK_SIZE;
467   if (remainder)
468     pad_bytes = CD_BLOCK_SIZE - remainder;
469 
470   success = write_fixed_files();
471 
472   for (i=0;i<numfiles;i++)
473     st_free(files[i]);
474 
475   st_free(files);
476 
477   return success;
478 }
479 
fix_main(int argc,char ** argv)480 static bool fix_main(int argc,char **argv)
481 {
482   int first_arg;
483 
484   parse(argc,argv,&first_arg);
485 
486   return process(argc,argv,first_arg);
487 }
488