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