1647 lines
48 KiB
C
1647 lines
48 KiB
C
////////////////////////////////////////////////////////////////////////////
|
|
// **** WAVPACK **** //
|
|
// Hybrid Lossless Wavefile Compressor //
|
|
// Copyright (c) 1998 - 2005 Conifer Software. //
|
|
// All Rights Reserved. //
|
|
// Distributed under the BSD Software License (see license.txt) //
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// wavpack.c
|
|
|
|
// This is the main module for the WavPack command-line compressor.
|
|
|
|
#if defined(WIN32)
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#else
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <locale.h>
|
|
#include <iconv.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
|
|
#include "wavpack.h"
|
|
#include "md5.h"
|
|
|
|
#if defined (__GNUC__) && !defined(WIN32)
|
|
#include <unistd.h>
|
|
#include <glob.h>
|
|
#include <sys/time.h>
|
|
#else
|
|
#include <sys/timeb.h>
|
|
#endif
|
|
|
|
#ifdef DEBUG_ALLOC
|
|
#define malloc malloc_db
|
|
#define realloc realloc_db
|
|
#define free free_db
|
|
void *malloc_db (uint32_t size);
|
|
void *realloc_db (void *ptr, uint32_t size);
|
|
void free_db (void *ptr);
|
|
int32_t dump_alloc (void);
|
|
static char *strdup (const char *s)
|
|
{ char *d = malloc (strlen (s) + 1); return strcpy (d, s); }
|
|
#endif
|
|
|
|
///////////////////////////// local variable storage //////////////////////////
|
|
|
|
static const char *sign_on = "\n"
|
|
" WAVPACK Hybrid Lossless Audio Compressor %s Version %s %s\n"
|
|
" Copyright (c) 1998 - 2005 Conifer Software. All Rights Reserved.\n\n";
|
|
|
|
static const char *usage =
|
|
#if defined (WIN32)
|
|
" Usage: WAVPACK [-options] [@]infile[.wav]|- [[@]outfile[.wv]|outpath|-]\n"
|
|
#else
|
|
" Usage: WAVPACK [-options] [@]infile[.wav]|- [...] [-o [@]outfile[.wv]|outpath|-]\n"
|
|
#endif
|
|
" (default is lossless; infile may contain wildcards: ?,*)\n\n"
|
|
" Options: -a = Adobe Audition (CoolEdit) mode for 32-bit floats\n"
|
|
" -bn = enable hybrid compression, n = 2.0 to 23.9 bits/sample, or\n"
|
|
" n = 24-9600 kbits/second (kbps)\n"
|
|
" -c = create correction file (.wvc) for hybrid mode (=lossless)\n"
|
|
" -cc = maximum hybrid compression (hurts lossy quality & decode speed)\n"
|
|
" -d = delete source file if successful (use with caution!)\n"
|
|
#if defined (WIN32)
|
|
" -e = create self-extracting executable (needs wvselfx.exe)\n"
|
|
#endif
|
|
" -f = fast mode (fast, but some compromise in compression ratio)\n"
|
|
" -h = high quality (best compression in all modes, but slower)\n"
|
|
" -i = ignore length in wav header (no pipe output allowed)\n"
|
|
" -jn = joint-stereo override (0 = left/right, 1 = mid/side)\n"
|
|
#if defined (WIN32)
|
|
" -l = run at low priority (for smoother multitasking)\n"
|
|
#endif
|
|
" -m = compute & store MD5 signature of raw audio data\n"
|
|
" -n = calculate average and peak quantization noise (hybrid only)\n"
|
|
#if !defined (WIN32)
|
|
" -o FILENAME | PATH = specify output filename or path\n"
|
|
#endif
|
|
" -p = practical float storage (also 32-bit ints, not lossless)\n"
|
|
" -r = generate new RIFF wav header (removing extra chunk info)\n"
|
|
" -q = quiet (keep console output to a minimum)\n"
|
|
" -sn = noise shaping override (hybrid only, n = -1.0 to 1.0, 0 = off)\n"
|
|
" -t = copy input file's time stamp to output file(s)\n"
|
|
" -w \"Field=Value\" = write specified metadata to APEv2 tag\n"
|
|
" -x[n] = extra encode processing (optional n = 1-6 for less/more)\n"
|
|
" -y = yes to all warnings (use with caution!)\n\n"
|
|
" Web: Visit www.wavpack.com for latest version and info\n";
|
|
|
|
|
|
// this global is used to indicate the special "debug" mode where extra debug messages
|
|
// are displayed and all messages are logged to the file \wavpack.log
|
|
|
|
int debug_logging_mode;
|
|
|
|
static int overwrite_all, num_files, file_index;
|
|
|
|
#if defined (WIN32)
|
|
static char *wvselfx_image;
|
|
static uint32_t wvselfx_size;
|
|
#endif
|
|
|
|
/////////////////////////// local function declarations ///////////////////////
|
|
|
|
static FILE *wild_fopen (char *filename, const char *mode);
|
|
static int pack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config);
|
|
static int pack_audio (WavpackContext *wpc, FILE *infile);
|
|
static void display_progress (double file_progress);
|
|
|
|
#define NO_ERROR 0L
|
|
#define SOFT_ERROR 1
|
|
#define HARD_ERROR 2
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// The "main" function for the command-line WavPack compressor. //
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
int main (argc, argv) int argc; char **argv;
|
|
{
|
|
int delete_source = 0, error_count = 0, tag_next_arg = 0, output_spec = 0;
|
|
char *outfilename = NULL, *out2filename = NULL;
|
|
char **matches = NULL;
|
|
WavpackConfig config;
|
|
int result, i;
|
|
|
|
#if defined(WIN32)
|
|
struct _finddata_t _finddata_t;
|
|
char selfname [MAX_PATH];
|
|
|
|
if (GetModuleFileName (NULL, selfname, sizeof (selfname)) && filespec_name (selfname) &&
|
|
strupr (filespec_name (selfname)) && strstr (filespec_name (selfname), "DEBUG")) {
|
|
char **argv_t = argv;
|
|
int argc_t = argc;
|
|
|
|
debug_logging_mode = TRUE;
|
|
|
|
while (--argc_t)
|
|
error_line ("arg %d: %s", argc - argc_t, *++argv_t);
|
|
}
|
|
|
|
strcpy (selfname, *argv);
|
|
#else
|
|
if (filespec_name (*argv))
|
|
if (strstr (filespec_name (*argv), "ebug") || strstr (filespec_name (*argv), "DEBUG")) {
|
|
char **argv_t = argv;
|
|
int argc_t = argc;
|
|
|
|
debug_logging_mode = TRUE;
|
|
|
|
while (--argc_t)
|
|
error_line ("arg %d: %s", argc - argc_t, *++argv_t);
|
|
}
|
|
#endif
|
|
|
|
CLEAR (config);
|
|
|
|
// loop through command-line arguments
|
|
|
|
while (--argc)
|
|
#if defined (WIN32)
|
|
if ((**++argv == '-' || **argv == '/') && (*argv)[1])
|
|
#else
|
|
if ((**++argv == '-') && (*argv)[1])
|
|
#endif
|
|
while (*++*argv)
|
|
switch (**argv) {
|
|
|
|
case 'Y': case 'y':
|
|
overwrite_all = 1;
|
|
break;
|
|
|
|
case 'D': case 'd':
|
|
delete_source = 1;
|
|
break;
|
|
|
|
case 'C': case 'c':
|
|
if (config.flags & CONFIG_CREATE_WVC)
|
|
config.flags |= CONFIG_OPTIMIZE_WVC;
|
|
else
|
|
config.flags |= CONFIG_CREATE_WVC;
|
|
|
|
break;
|
|
|
|
case 'X': case 'x':
|
|
config.xmode = strtol (++*argv, argv, 10);
|
|
|
|
if (config.xmode < 0 || config.xmode > 6) {
|
|
error_line ("extra mode only goes from 1 to 6!");
|
|
++error_count;
|
|
}
|
|
else
|
|
config.flags |= CONFIG_EXTRA_MODE;
|
|
|
|
--*argv;
|
|
break;
|
|
|
|
case 'F': case 'f':
|
|
if (config.flags & CONFIG_FAST_FLAG)
|
|
config.flags |= CONFIG_VERY_FAST_FLAG;
|
|
else
|
|
config.flags |= CONFIG_FAST_FLAG;
|
|
|
|
break;
|
|
|
|
case 'H': case 'h':
|
|
if (config.flags & CONFIG_HIGH_FLAG)
|
|
config.flags |= CONFIG_VERY_HIGH_FLAG;
|
|
else
|
|
config.flags |= CONFIG_HIGH_FLAG;
|
|
|
|
break;
|
|
|
|
case 'N': case 'n':
|
|
config.flags |= CONFIG_CALC_NOISE;
|
|
break;
|
|
|
|
case 'A': case 'a':
|
|
config.flags |= CONFIG_ADOBE_MODE;
|
|
break;
|
|
#if defined (WIN32)
|
|
case 'E': case 'e':
|
|
config.flags |= CONFIG_CREATE_EXE;
|
|
break;
|
|
|
|
case 'L': case 'l':
|
|
SetPriorityClass (GetCurrentProcess(), IDLE_PRIORITY_CLASS);
|
|
break;
|
|
#else
|
|
case 'O': case 'o':
|
|
output_spec = 1;
|
|
break;
|
|
#endif
|
|
case 'T': case 't':
|
|
config.flags |= CONFIG_COPY_TIME;
|
|
break;
|
|
|
|
case 'P': case 'p':
|
|
config.flags |= CONFIG_SKIP_WVX;
|
|
break;
|
|
|
|
case 'Q': case 'q':
|
|
config.flags |= CONFIG_QUIET_MODE;
|
|
break;
|
|
|
|
case 'M': case 'm':
|
|
config.flags |= CONFIG_MD5_CHECKSUM;
|
|
break;
|
|
|
|
case 'I': case 'i':
|
|
config.flags |= CONFIG_IGNORE_LENGTH;
|
|
break;
|
|
|
|
case 'R': case 'r':
|
|
config.flags |= CONFIG_NEW_RIFF_HEADER;
|
|
break;
|
|
|
|
case 'K': case 'k':
|
|
config.block_samples = strtol (++*argv, argv, 10);
|
|
--*argv;
|
|
break;
|
|
|
|
case 'B': case 'b':
|
|
config.flags |= CONFIG_HYBRID_FLAG;
|
|
config.bitrate = strtod (++*argv, argv);
|
|
--*argv;
|
|
|
|
if (config.bitrate < 2.0 || config.bitrate > 9600.0) {
|
|
error_line ("hybrid spec must be 2.0 to 9600!");
|
|
++error_count;
|
|
}
|
|
|
|
if (config.bitrate >= 24.0)
|
|
config.flags |= CONFIG_BITRATE_KBPS;
|
|
|
|
break;
|
|
|
|
case 'J': case 'j':
|
|
switch (strtol (++*argv, argv, 10)) {
|
|
|
|
case 0:
|
|
config.flags |= CONFIG_JOINT_OVERRIDE;
|
|
config.flags &= ~CONFIG_JOINT_STEREO;
|
|
break;
|
|
|
|
case 1:
|
|
config.flags |= (CONFIG_JOINT_OVERRIDE | CONFIG_JOINT_STEREO);
|
|
break;
|
|
|
|
default:
|
|
error_line ("-j0 or -j1 only!");
|
|
++error_count;
|
|
}
|
|
|
|
--*argv;
|
|
break;
|
|
|
|
case 'S': case 's':
|
|
config.shaping_weight = strtod (++*argv, argv);
|
|
|
|
if (!config.shaping_weight) {
|
|
config.flags |= CONFIG_SHAPE_OVERRIDE;
|
|
config.flags &= ~CONFIG_HYBRID_SHAPE;
|
|
}
|
|
else if (config.shaping_weight >= -1.0 && config.shaping_weight <= 1.0)
|
|
config.flags |= (CONFIG_HYBRID_SHAPE | CONFIG_SHAPE_OVERRIDE);
|
|
else {
|
|
error_line ("-s-1.00 to -s1.00 only!");
|
|
++error_count;
|
|
}
|
|
|
|
--*argv;
|
|
break;
|
|
|
|
case 'W': case 'w':
|
|
tag_next_arg = 1;
|
|
break;
|
|
|
|
default:
|
|
error_line ("illegal option: %c !", **argv);
|
|
++error_count;
|
|
}
|
|
else if (tag_next_arg) {
|
|
tag_next_arg = 0;
|
|
config.tag_strings = realloc (config.tag_strings, ++config.num_tag_strings * sizeof (*config.tag_strings));
|
|
config.tag_strings [config.num_tag_strings - 1] = *argv;
|
|
}
|
|
#if defined (WIN32)
|
|
else if (!num_files) {
|
|
matches = realloc (matches, (num_files + 1) * sizeof (*matches));
|
|
matches [num_files] = malloc (strlen (*argv) + 10);
|
|
strcpy (matches [num_files], *argv);
|
|
|
|
if (*(matches [num_files]) != '-' && *(matches [num_files]) != '@' &&
|
|
!filespec_ext (matches [num_files]))
|
|
strcat (matches [num_files], ".wav");
|
|
|
|
num_files++;
|
|
}
|
|
else if (!outfilename) {
|
|
outfilename = malloc (strlen (*argv) + PATH_MAX);
|
|
strcpy (outfilename, *argv);
|
|
}
|
|
else if (!out2filename) {
|
|
out2filename = malloc (strlen (*argv) + PATH_MAX);
|
|
strcpy (out2filename, *argv);
|
|
}
|
|
else {
|
|
error_line ("extra unknown argument: %s !", *argv);
|
|
++error_count;
|
|
}
|
|
#else
|
|
else if (output_spec) {
|
|
outfilename = malloc (strlen (*argv) + PATH_MAX);
|
|
strcpy (outfilename, *argv);
|
|
output_spec = 0;
|
|
}
|
|
else {
|
|
matches = realloc (matches, (num_files + 1) * sizeof (*matches));
|
|
matches [num_files] = malloc (strlen (*argv) + 10);
|
|
strcpy (matches [num_files], *argv);
|
|
|
|
if (*(matches [num_files]) != '-' && *(matches [num_files]) != '@' &&
|
|
!filespec_ext (matches [num_files]))
|
|
strcat (matches [num_files], ".wav");
|
|
|
|
num_files++;
|
|
}
|
|
#endif
|
|
|
|
setup_break (); // set up console and detect ^C and ^Break
|
|
|
|
// check for various command-line argument problems
|
|
|
|
if (!(~config.flags & (CONFIG_HIGH_FLAG | CONFIG_FAST_FLAG))) {
|
|
error_line ("high and fast modes are mutually exclusive!");
|
|
++error_count;
|
|
}
|
|
|
|
if ((config.flags & CONFIG_IGNORE_LENGTH) && outfilename && *outfilename == '-') {
|
|
error_line ("can't ignore length in header when using stdout!");
|
|
++error_count;
|
|
}
|
|
|
|
if (config.flags & CONFIG_HYBRID_FLAG) {
|
|
if ((config.flags & CONFIG_CREATE_WVC) && outfilename && *outfilename == '-') {
|
|
error_line ("can't create correction file when using stdout!");
|
|
++error_count;
|
|
}
|
|
}
|
|
else {
|
|
if (config.flags & (CONFIG_CALC_NOISE | CONFIG_SHAPE_OVERRIDE | CONFIG_CREATE_WVC)) {
|
|
error_line ("-s, -n and -c options are for hybrid mode (-b) only!");
|
|
++error_count;
|
|
}
|
|
}
|
|
|
|
if (!(config.flags & CONFIG_QUIET_MODE) && !error_count)
|
|
fprintf (stderr, sign_on, VERSION_OS, VERSION_STR, DATE_STR);
|
|
|
|
if (!num_files) {
|
|
printf ("%s", usage);
|
|
return 1;
|
|
}
|
|
|
|
// loop through any tag specification strings and check for file access
|
|
|
|
for (i = 0; i < config.num_tag_strings; ++i) {
|
|
char *cp = strchr (config.tag_strings [i], '='), *string = NULL;
|
|
|
|
if (cp && cp > config.tag_strings [i]) {
|
|
int item_len = cp - config.tag_strings [i];
|
|
|
|
if (cp [1] == '@') {
|
|
FILE *file = wild_fopen (cp+2, "rb");
|
|
|
|
if (!file && filespec_name (matches [0]) && *matches [0] != '-') {
|
|
char *temp = malloc (strlen (matches [0]) + PATH_MAX);
|
|
|
|
strcpy (temp, matches [0]);
|
|
strcpy (filespec_name (temp), cp+2);
|
|
file = wild_fopen (temp, "rb");
|
|
free (temp);
|
|
}
|
|
|
|
if (!file && filespec_name (outfilename) && *outfilename != '-') {
|
|
char *temp = malloc (strlen (outfilename) + PATH_MAX);
|
|
|
|
strcpy (temp, outfilename);
|
|
strcpy (filespec_name (temp), cp+2);
|
|
file = wild_fopen (temp, "rb");
|
|
free (temp);
|
|
}
|
|
|
|
if (file) {
|
|
uint32_t bcount, file_len;
|
|
|
|
file_len = DoGetFileSize (file);
|
|
if (file_len < 1048576 && (string = malloc (item_len + file_len + 2)) != NULL) {
|
|
memcpy (string, config.tag_strings [i], item_len + 1);
|
|
|
|
if (!DoReadFile (file, string + item_len + 1, file_len, &bcount) || bcount != file_len) {
|
|
free (string);
|
|
string = NULL;
|
|
}
|
|
else
|
|
string [item_len + file_len + 1] = 0;
|
|
}
|
|
|
|
DoCloseHandle (file);
|
|
}
|
|
}
|
|
else
|
|
string = config.tag_strings [i];
|
|
}
|
|
|
|
if (!string) {
|
|
error_line ("error in tag spec: %s !", config.tag_strings [i]);
|
|
++error_count;
|
|
}
|
|
else {
|
|
config.tag_strings [i] = string;
|
|
// error_line ("final tag data: %s", string);
|
|
}
|
|
}
|
|
|
|
if (error_count)
|
|
return 1;
|
|
|
|
// If we are trying to create self-extracting .exe files, this is where
|
|
// we read the wvselfx.exe file into memory in preparation for pre-pending
|
|
// it to the WavPack files.
|
|
|
|
#if defined (WIN32)
|
|
if (config.flags & CONFIG_CREATE_EXE) {
|
|
FILE *wvselfx_file;
|
|
uint32_t bcount;
|
|
|
|
strcpy (filespec_name (selfname), "wvselfx.exe");
|
|
|
|
wvselfx_file = fopen (selfname, "rb");
|
|
|
|
if (!wvselfx_file) {
|
|
_searchenv ("wvselfx.exe", "PATH", selfname);
|
|
wvselfx_file = fopen (selfname, "rb");
|
|
}
|
|
|
|
if (wvselfx_file) {
|
|
wvselfx_size = DoGetFileSize (wvselfx_file);
|
|
|
|
if (wvselfx_size && wvselfx_size != 26624 && wvselfx_size < 49152) {
|
|
wvselfx_image = malloc (wvselfx_size);
|
|
|
|
if (!DoReadFile (wvselfx_file, wvselfx_image, wvselfx_size, &bcount) || bcount != wvselfx_size) {
|
|
free (wvselfx_image);
|
|
wvselfx_image = NULL;
|
|
}
|
|
}
|
|
|
|
DoCloseHandle (wvselfx_file);
|
|
}
|
|
|
|
if (!wvselfx_image) {
|
|
error_line ("wvselfx.exe file is not readable or is outdated!");
|
|
free (wvselfx_image);
|
|
exit (1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (file_index = 0; file_index < num_files; ++file_index) {
|
|
char *infilename = matches [file_index];
|
|
|
|
// If the single infile specification begins with a '@', then it
|
|
// actually points to a file that contains the names of the files
|
|
// to be converted. This was included for use by Wim Speekenbrink's
|
|
// frontends, but could be used for other purposes.
|
|
|
|
if (*infilename == '@') {
|
|
FILE *list = fopen (infilename+1, "rt");
|
|
int di, c;
|
|
|
|
for (di = file_index; di < num_files - 1; di++)
|
|
matches [di] = matches [di + 1];
|
|
|
|
file_index--;
|
|
num_files--;
|
|
|
|
if (list == NULL) {
|
|
error_line ("file %s not found!", infilename+1);
|
|
free (infilename);
|
|
return 1;
|
|
}
|
|
|
|
while ((c = getc (list)) != EOF) {
|
|
|
|
while (c == '\n')
|
|
c = getc (list);
|
|
|
|
if (c != EOF) {
|
|
char *fname = malloc (PATH_MAX);
|
|
int ci = 0;
|
|
|
|
do
|
|
fname [ci++] = c;
|
|
while ((c = getc (list)) != '\n' && c != EOF && ci < PATH_MAX);
|
|
|
|
fname [ci++] = '\0';
|
|
fname = realloc (fname, ci);
|
|
matches = realloc (matches, ++num_files * sizeof (*matches));
|
|
|
|
for (di = num_files - 1; di > file_index + 1; di--)
|
|
matches [di] = matches [di - 1];
|
|
|
|
matches [++file_index] = fname;
|
|
}
|
|
}
|
|
|
|
fclose (list);
|
|
free (infilename);
|
|
}
|
|
#if defined (WIN32)
|
|
else if (filespec_wild (infilename)) {
|
|
FILE *list = fopen (infilename+1, "rt");
|
|
int di;
|
|
|
|
for (di = file_index; di < num_files - 1; di++)
|
|
matches [di] = matches [di + 1];
|
|
|
|
file_index--;
|
|
num_files--;
|
|
|
|
if ((i = _findfirst (infilename, &_finddata_t)) != -1L) {
|
|
do {
|
|
if (!(_finddata_t.attrib & _A_SUBDIR)) {
|
|
matches = realloc (matches, ++num_files * sizeof (*matches));
|
|
|
|
for (di = num_files - 1; di > file_index + 1; di--)
|
|
matches [di] = matches [di - 1];
|
|
|
|
matches [++file_index] = malloc (strlen (infilename) + strlen (_finddata_t.name) + 10);
|
|
strcpy (matches [file_index], infilename);
|
|
*filespec_name (matches [file_index]) = '\0';
|
|
strcat (matches [file_index], _finddata_t.name);
|
|
}
|
|
} while (_findnext (i, &_finddata_t) == 0);
|
|
|
|
_findclose (i);
|
|
}
|
|
|
|
free (infilename);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// If the outfile specification begins with a '@', then it actually points
|
|
// to a file that contains the output specification. This was included for
|
|
// use by Wim Speekenbrink's frontends because certain filenames could not
|
|
// be passed on the command-line, but could be used for other purposes.
|
|
|
|
if (outfilename && outfilename [0] == '@') {
|
|
FILE *list = fopen (outfilename+1, "rt");
|
|
int c;
|
|
|
|
if (list == NULL) {
|
|
error_line ("file %s not found!", outfilename+1);
|
|
free(outfilename);
|
|
return 1;
|
|
}
|
|
|
|
while ((c = getc (list)) == '\n');
|
|
|
|
if (c != EOF) {
|
|
int ci = 0;
|
|
|
|
do
|
|
outfilename [ci++] = c;
|
|
while ((c = getc (list)) != '\n' && c != EOF && ci < PATH_MAX);
|
|
|
|
outfilename [ci] = '\0';
|
|
}
|
|
else {
|
|
error_line ("output spec file is empty!");
|
|
free(outfilename);
|
|
fclose (list);
|
|
return 1;
|
|
}
|
|
|
|
fclose (list);
|
|
}
|
|
|
|
if (out2filename && (num_files > 1 || !(config.flags & CONFIG_CREATE_WVC))) {
|
|
error_line ("extra unknown argument: %s !", out2filename);
|
|
return 1;
|
|
}
|
|
|
|
// if we found any files to process, this is where we start
|
|
|
|
if (num_files) {
|
|
char outpath, addext;
|
|
|
|
if (outfilename && *outfilename != '-') {
|
|
outpath = (filespec_path (outfilename) != NULL);
|
|
|
|
if (num_files > 1 && !outpath) {
|
|
error_line ("%s is not a valid output path", outfilename);
|
|
free(outfilename);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
outpath = 0;
|
|
|
|
addext = !outfilename || outpath || !filespec_ext (outfilename);
|
|
|
|
// loop through and process files in list
|
|
|
|
for (file_index = 0; file_index < num_files; ++file_index) {
|
|
if (check_break ())
|
|
break;
|
|
|
|
// generate output filename
|
|
|
|
if (outpath) {
|
|
strcat (outfilename, filespec_name (matches [file_index]));
|
|
|
|
if (filespec_ext (outfilename))
|
|
*filespec_ext (outfilename) = '\0';
|
|
}
|
|
else if (!outfilename) {
|
|
outfilename = malloc (strlen (matches [file_index]) + 10);
|
|
strcpy (outfilename, matches [file_index]);
|
|
|
|
if (filespec_ext (outfilename))
|
|
*filespec_ext (outfilename) = '\0';
|
|
}
|
|
|
|
if (addext && *outfilename != '-')
|
|
strcat (outfilename, (config.flags & CONFIG_CREATE_EXE) ? ".exe" : ".wv");
|
|
|
|
// if "correction" file is desired, generate name for that
|
|
|
|
if (config.flags & CONFIG_CREATE_WVC) {
|
|
if (!out2filename) {
|
|
out2filename = malloc (strlen (outfilename) + 10);
|
|
strcpy (out2filename, outfilename);
|
|
}
|
|
else {
|
|
char *temp = malloc (strlen (outfilename) + PATH_MAX);
|
|
|
|
strcpy (temp, outfilename);
|
|
strcpy (filespec_name (temp), filespec_name (out2filename));
|
|
strcpy (out2filename, temp);
|
|
free (temp);
|
|
}
|
|
|
|
if (filespec_ext (out2filename))
|
|
*filespec_ext (out2filename) = '\0';
|
|
|
|
strcat (out2filename, ".wvc");
|
|
}
|
|
else
|
|
out2filename = NULL;
|
|
|
|
if (num_files > 1)
|
|
fprintf (stderr, "\n%s:\n", matches [file_index]);
|
|
|
|
result = pack_file (matches [file_index], outfilename, out2filename, &config);
|
|
|
|
if (result != NO_ERROR)
|
|
++error_count;
|
|
|
|
if (result == HARD_ERROR)
|
|
break;
|
|
|
|
// delete source file if that option is enabled
|
|
|
|
if (result == NO_ERROR && delete_source)
|
|
error_line ("%s source file %s", DoDeleteFile (matches [file_index]) ?
|
|
"deleted" : "can't delete", matches [file_index]);
|
|
|
|
// clean up in preparation for potentially another file
|
|
|
|
if (outpath)
|
|
*filespec_name (outfilename) = '\0';
|
|
else if (*outfilename != '-') {
|
|
free (outfilename);
|
|
outfilename = NULL;
|
|
}
|
|
|
|
if (out2filename) {
|
|
free (out2filename);
|
|
out2filename = NULL;
|
|
}
|
|
|
|
free (matches [file_index]);
|
|
}
|
|
|
|
if (num_files > 1) {
|
|
if (error_count)
|
|
fprintf (stderr, "\n **** warning: errors occurred in %d of %d files! ****\n", error_count, num_files);
|
|
else if (!(config.flags & CONFIG_QUIET_MODE))
|
|
fprintf (stderr, "\n **** %d files successfully processed ****\n", num_files);
|
|
}
|
|
|
|
free (matches);
|
|
}
|
|
else {
|
|
error_line ("nothing to do!");
|
|
++error_count;
|
|
}
|
|
|
|
if (outfilename)
|
|
free (outfilename);
|
|
|
|
#ifdef DEBUG_ALLOC
|
|
error_line ("malloc_count = %d", dump_alloc ());
|
|
#endif
|
|
|
|
return error_count ? 1 : 0;
|
|
}
|
|
|
|
// This structure and function are used to write completed WavPack blocks in
|
|
// a device independent way.
|
|
|
|
typedef struct {
|
|
uint32_t bytes_written, first_block_size;
|
|
FILE *file;
|
|
int error;
|
|
} write_id;
|
|
|
|
static int write_block (void *id, void *data, int32_t length)
|
|
{
|
|
write_id *wid = (write_id *) id;
|
|
uint32_t bcount;
|
|
|
|
if (wid->error)
|
|
return FALSE;
|
|
|
|
if (wid && wid->file && data && length) {
|
|
if (!DoWriteFile (wid->file, data, length, &bcount) || bcount != length) {
|
|
DoTruncateFile (wid->file);
|
|
DoCloseHandle (wid->file);
|
|
wid->file = NULL;
|
|
wid->error = 1;
|
|
return FALSE;
|
|
}
|
|
else {
|
|
wid->bytes_written += length;
|
|
|
|
if (!wid->first_block_size)
|
|
wid->first_block_size = bcount;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Special version of fopen() that allows a wildcard specification for the
|
|
// filename. If a wildcard is specified, then it must match 1 and only 1
|
|
// file to be acceptable (i.e. it won't match just the "first" file).
|
|
|
|
#if defined (WIN32)
|
|
|
|
static FILE *wild_fopen (char *filename, const char *mode)
|
|
{
|
|
struct _finddata_t _finddata_t;
|
|
char *matchname = NULL;
|
|
FILE *res = NULL;
|
|
int i;
|
|
|
|
if (!filespec_wild (filename) || !filespec_name (filename))
|
|
return fopen (filename, mode);
|
|
|
|
if ((i = _findfirst (filename, &_finddata_t)) != -1L) {
|
|
do {
|
|
if (!(_finddata_t.attrib & _A_SUBDIR)) {
|
|
if (matchname) {
|
|
free (matchname);
|
|
matchname = NULL;
|
|
break;
|
|
}
|
|
else {
|
|
matchname = malloc (strlen (filename) + strlen (_finddata_t.name));
|
|
strcpy (matchname, filename);
|
|
strcpy (filespec_name (matchname), _finddata_t.name);
|
|
}
|
|
}
|
|
} while (_findnext (i, &_finddata_t) == 0);
|
|
|
|
_findclose (i);
|
|
}
|
|
|
|
if (matchname) {
|
|
res = fopen (matchname, mode);
|
|
free (matchname);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
#else
|
|
|
|
static FILE *wild_fopen (char *filename, const char *mode)
|
|
{
|
|
return fopen (filename, mode);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
// This function packs a single file "infilename" and stores the result at
|
|
// "outfilename". If "out2filename" is specified, then the "correction"
|
|
// file would go there. The files are opened and closed in this function
|
|
// and the "config" structure specifies the mode of compression.
|
|
|
|
static void AnsiToUTF8 (char *string, int len);
|
|
|
|
static int pack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config)
|
|
{
|
|
uint32_t total_samples = 0, bcount;
|
|
WavpackConfig loc_config = *config;
|
|
RiffChunkHeader riff_chunk_header;
|
|
write_id wv_file, wvc_file;
|
|
ChunkHeader chunk_header;
|
|
WaveHeader WaveHeader;
|
|
WavpackContext *wpc;
|
|
double dtime;
|
|
FILE *infile;
|
|
int result;
|
|
|
|
#if defined(WIN32)
|
|
struct _timeb time1, time2;
|
|
#else
|
|
struct timeval time1, time2;
|
|
struct timezone timez;
|
|
#endif
|
|
|
|
CLEAR (wv_file);
|
|
CLEAR (wvc_file);
|
|
wpc = WavpackOpenFileOutput (write_block, &wv_file, out2filename ? &wvc_file : NULL);
|
|
|
|
// open the source file for reading
|
|
|
|
if (*infilename == '-') {
|
|
infile = stdin;
|
|
#if defined(WIN32)
|
|
setmode (fileno (stdin), O_BINARY);
|
|
#endif
|
|
}
|
|
else if ((infile = fopen (infilename, "rb")) == NULL) {
|
|
error_line ("can't open file %s!", infilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
|
|
// check both output files for overwrite warning required
|
|
|
|
if (*outfilename != '-' && !overwrite_all && (wv_file.file = fopen (outfilename, "rb")) != NULL) {
|
|
DoCloseHandle (wv_file.file);
|
|
fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (outfilename));
|
|
SetConsoleTitle ("overwrite?");
|
|
|
|
switch (yna ()) {
|
|
case 'n':
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
|
|
case 'a':
|
|
overwrite_all = 1;
|
|
}
|
|
}
|
|
|
|
if (out2filename && !overwrite_all && (wvc_file.file = fopen (out2filename, "rb")) != NULL) {
|
|
DoCloseHandle (wvc_file.file);
|
|
fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (out2filename));
|
|
SetConsoleTitle ("overwrite?");
|
|
|
|
switch (yna ()) {
|
|
|
|
case 'n':
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
|
|
case 'a':
|
|
overwrite_all = 1;
|
|
}
|
|
}
|
|
|
|
#if defined(WIN32)
|
|
_ftime (&time1);
|
|
#else
|
|
gettimeofday(&time1,&timez);
|
|
#endif
|
|
|
|
// open output file for writing
|
|
|
|
if (*outfilename == '-') {
|
|
wv_file.file = stdout;
|
|
#if defined(WIN32)
|
|
setmode (fileno (stdout), O_BINARY);
|
|
#endif
|
|
}
|
|
else if ((wv_file.file = fopen (outfilename, "w+b")) == NULL) {
|
|
error_line ("can't create file %s!", outfilename);
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
|
|
if (!(loc_config.flags & CONFIG_QUIET_MODE)) {
|
|
if (*outfilename == '-')
|
|
fprintf (stderr, "packing %s to stdout,", *infilename == '-' ? "stdin" : FN_FIT (infilename));
|
|
else if (out2filename)
|
|
fprintf (stderr, "creating %s (+%s),", FN_FIT (outfilename), filespec_ext (out2filename));
|
|
else
|
|
fprintf (stderr, "creating %s,", FN_FIT (outfilename));
|
|
}
|
|
|
|
#if defined (WIN32)
|
|
if (loc_config.flags & CONFIG_CREATE_EXE)
|
|
if (!DoWriteFile (wv_file.file, wvselfx_image, wvselfx_size, &bcount) || bcount != wvselfx_size) {
|
|
error_line ("can't write WavPack data, disk probably full!");
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
#endif
|
|
|
|
// if not in "raw" mode, read (and copy to output) initial RIFF form header
|
|
|
|
if (!(loc_config.flags & CONFIG_RAW_FLAG)) {
|
|
if ((!DoReadFile (infile, &riff_chunk_header, sizeof (RiffChunkHeader), &bcount) ||
|
|
bcount != sizeof (RiffChunkHeader) || strncmp (riff_chunk_header.ckID, "RIFF", 4) ||
|
|
strncmp (riff_chunk_header.formType, "WAVE", 4))) {
|
|
error_line ("%s is not a valid .WAV file!", infilename);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
else if (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) &&
|
|
!WavpackAddWrapper (wpc, &riff_chunk_header, sizeof (RiffChunkHeader))) {
|
|
error_line ("%s", wpc->error_message);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
// if not in "raw" mode, loop through all elements of the RIFF wav header
|
|
// (until the data chuck) and copy them to the output file
|
|
|
|
while (!(loc_config.flags & CONFIG_RAW_FLAG)) {
|
|
|
|
if (!DoReadFile (infile, &chunk_header, sizeof (ChunkHeader), &bcount) ||
|
|
bcount != sizeof (ChunkHeader)) {
|
|
error_line ("%s is not a valid .WAV file!", infilename);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
else if (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) &&
|
|
!WavpackAddWrapper (wpc, &chunk_header, sizeof (ChunkHeader))) {
|
|
error_line ("%s", wpc->error_message);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
|
|
little_endian_to_native (&chunk_header, ChunkHeaderFormat);
|
|
|
|
// if it's the format chunk, we want to get some info out of there and
|
|
// make sure it's a .wav file we can handle
|
|
|
|
if (!strncmp (chunk_header.ckID, "fmt ", 4)) {
|
|
int supported = TRUE, format;
|
|
|
|
if (chunk_header.ckSize < 16 || chunk_header.ckSize > sizeof (WaveHeader) ||
|
|
!DoReadFile (infile, &WaveHeader, chunk_header.ckSize, &bcount) ||
|
|
bcount != chunk_header.ckSize) {
|
|
error_line ("%s is not a valid .WAV file!", infilename);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
else if (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) &&
|
|
!WavpackAddWrapper (wpc, &WaveHeader, chunk_header.ckSize)) {
|
|
error_line ("%s", wpc->error_message);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
|
|
little_endian_to_native (&WaveHeader, WaveHeaderFormat);
|
|
|
|
if (debug_logging_mode) {
|
|
error_line ("format tag size = %d", chunk_header.ckSize);
|
|
error_line ("FormatTag = %x, NumChannels = %d, BitsPerSample = %d",
|
|
WaveHeader.FormatTag, WaveHeader.NumChannels, WaveHeader.BitsPerSample);
|
|
error_line ("BlockAlign = %d, SampleRate = %d, BytesPerSecond = %d",
|
|
WaveHeader.BlockAlign, WaveHeader.SampleRate, WaveHeader.BytesPerSecond);
|
|
|
|
if (chunk_header.ckSize > 16)
|
|
error_line ("cbSize = %d, ValidBitsPerSample = %d", WaveHeader.cbSize,
|
|
WaveHeader.ValidBitsPerSample);
|
|
|
|
if (chunk_header.ckSize > 20)
|
|
error_line ("ChannelMask = %x, SubFormat = %d",
|
|
WaveHeader.ChannelMask, WaveHeader.SubFormat);
|
|
}
|
|
|
|
if (chunk_header.ckSize > 16 && WaveHeader.cbSize == 2)
|
|
loc_config.flags |= CONFIG_ADOBE_MODE;
|
|
|
|
format = (WaveHeader.FormatTag == 0xfffe && chunk_header.ckSize == 40) ?
|
|
WaveHeader.SubFormat : WaveHeader.FormatTag;
|
|
|
|
loc_config.bits_per_sample = chunk_header.ckSize == 40 ?
|
|
WaveHeader.ValidBitsPerSample : WaveHeader.BitsPerSample;
|
|
|
|
if (format != 1 && format != 3)
|
|
supported = FALSE;
|
|
|
|
if (!WaveHeader.NumChannels ||
|
|
WaveHeader.BlockAlign / WaveHeader.NumChannels > 4)
|
|
supported = FALSE;
|
|
|
|
if (loc_config.bits_per_sample < 1 || loc_config.bits_per_sample > 32)
|
|
supported = FALSE;
|
|
|
|
if (!supported) {
|
|
error_line ("%s is an unsupported .WAV format!", infilename);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
|
|
if (chunk_header.ckSize < 40) {
|
|
if (WaveHeader.NumChannels <= 2)
|
|
loc_config.channel_mask = 0x5 - WaveHeader.NumChannels;
|
|
else
|
|
loc_config.channel_mask = (1 << WaveHeader.NumChannels) - 1;
|
|
}
|
|
else
|
|
loc_config.channel_mask = WaveHeader.ChannelMask;
|
|
|
|
if (format == 3)
|
|
loc_config.float_norm_exp = 127;
|
|
else if ((loc_config.flags & CONFIG_ADOBE_MODE) &&
|
|
WaveHeader.BlockAlign / WaveHeader.NumChannels == 4) {
|
|
if (WaveHeader.BitsPerSample == 24)
|
|
loc_config.float_norm_exp = 127 + 23;
|
|
else if (WaveHeader.BitsPerSample == 32)
|
|
loc_config.float_norm_exp = 127 + 15;
|
|
}
|
|
|
|
if (debug_logging_mode) {
|
|
if (loc_config.float_norm_exp == 127)
|
|
error_line ("data format: normalized 32-bit floating point");
|
|
else if (loc_config.float_norm_exp)
|
|
error_line ("data format: 32-bit floating point (Audition %d:%d float type 1)",
|
|
loc_config.float_norm_exp - 126, 150 - loc_config.float_norm_exp);
|
|
else
|
|
error_line ("data format: %d-bit integers stored in %d byte(s)",
|
|
loc_config.bits_per_sample, WaveHeader.BlockAlign / WaveHeader.NumChannels);
|
|
}
|
|
}
|
|
else if (!strncmp (chunk_header.ckID, "data", 4)) {
|
|
|
|
// on the data chunk, get size and exit loop
|
|
|
|
total_samples = chunk_header.ckSize / WaveHeader.BlockAlign;
|
|
break;
|
|
}
|
|
else { // just copy unknown chunks to output file
|
|
|
|
int bytes_to_copy = (chunk_header.ckSize + 1) & ~1L;
|
|
char *buff = malloc (bytes_to_copy);
|
|
|
|
if (debug_logging_mode)
|
|
error_line ("extra unknown chunk \"%c%c%c%c\" of %d bytes",
|
|
chunk_header.ckID [0], chunk_header.ckID [1], chunk_header.ckID [2],
|
|
chunk_header.ckID [3], chunk_header.ckSize);
|
|
|
|
if (!DoReadFile (infile, buff, bytes_to_copy, &bcount) ||
|
|
bcount != bytes_to_copy ||
|
|
(!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) &&
|
|
!WavpackAddWrapper (wpc, buff, bytes_to_copy))) {
|
|
error_line ("%s", wpc->error_message);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
free (buff);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
|
|
free (buff);
|
|
}
|
|
}
|
|
|
|
loc_config.bytes_per_sample = WaveHeader.BlockAlign / WaveHeader.NumChannels;
|
|
loc_config.num_channels = WaveHeader.NumChannels;
|
|
loc_config.sample_rate = WaveHeader.SampleRate;
|
|
|
|
WavpackSetConfiguration (wpc, &loc_config, total_samples);
|
|
|
|
// if we are creating a "correction" file, open it now for writing
|
|
|
|
if (out2filename) {
|
|
if ((wvc_file.file = fopen (out2filename, "w+b")) == NULL) {
|
|
error_line ("can't create correction file!");
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (outfilename);
|
|
WavpackCloseFile (wpc);
|
|
return SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
// pack the audio portion of the file now
|
|
|
|
result = pack_audio (wpc, infile);
|
|
|
|
// if everything went well (and we're not ignoring length) try to read
|
|
// anything else that might be appended to the audio data and write that
|
|
// to the WavPack metadata as "wrapper"
|
|
|
|
if (result == NO_ERROR && !(loc_config.flags & CONFIG_IGNORE_LENGTH)) {
|
|
uchar buff [16];
|
|
|
|
while (DoReadFile (infile, buff, sizeof (buff), &bcount) && bcount)
|
|
if (!(loc_config.flags & CONFIG_NEW_RIFF_HEADER) &&
|
|
!WavpackAddWrapper (wpc, buff, bcount)) {
|
|
error_line ("%s", wpc->error_message);
|
|
result = HARD_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DoCloseHandle (infile); // we're now done with input file, so close
|
|
|
|
// we're now done with any WavPack blocks, so flush any remaining data
|
|
|
|
if (result == NO_ERROR && !WavpackFlushSamples (wpc)) {
|
|
error_line ("%s", wpc->error_message);
|
|
result = HARD_ERROR;
|
|
}
|
|
|
|
// if still no errors, check to see if we need to create & write a tag
|
|
// (which is NOT stored in regular WavPack blocks)
|
|
|
|
if (result == NO_ERROR && config->num_tag_strings) {
|
|
int i;
|
|
|
|
for (i = 0; i < config->num_tag_strings; ++i) {
|
|
int item_len = strchr (config->tag_strings [i], '=') - config->tag_strings [i];
|
|
int value_len = strlen (config->tag_strings [i]) - item_len - 1;
|
|
|
|
if (value_len) {
|
|
char *item = malloc (item_len + 1);
|
|
char *value = malloc (value_len * 2 + 1);
|
|
|
|
strncpy (item, config->tag_strings [i], item_len);
|
|
item [item_len] = 0;
|
|
strcpy (value, config->tag_strings [i] + item_len + 1);
|
|
AnsiToUTF8 (value, value_len * 2 + 1);
|
|
WavpackAppendTagItem (wpc, item, value, strlen (value));
|
|
free (value);
|
|
free (item);
|
|
}
|
|
}
|
|
|
|
if (!WavpackWriteTag (wpc)) {
|
|
error_line ("%s", wpc->error_message);
|
|
result = HARD_ERROR;
|
|
}
|
|
}
|
|
|
|
// At this point we're done writing to the output files. However, in some
|
|
// situations we might have to back up and re-write the initial blocks.
|
|
// Currently the only case is if we're ignoring length.
|
|
|
|
if (result == NO_ERROR && WavpackGetNumSamples (wpc) != WavpackGetSampleIndex (wpc)) {
|
|
if (loc_config.flags & CONFIG_IGNORE_LENGTH) {
|
|
char *block_buff = malloc (wv_file.first_block_size);
|
|
uint32_t wrapper_size;
|
|
|
|
if (block_buff && !DoSetFilePositionAbsolute (wv_file.file, 0) &&
|
|
DoReadFile (wv_file.file, block_buff, wv_file.first_block_size, &bcount) &&
|
|
bcount == wv_file.first_block_size && !strncmp (block_buff, "wvpk", 4)) {
|
|
|
|
WavpackUpdateNumSamples (wpc, block_buff);
|
|
|
|
if (WavpackGetWrapperLocation (block_buff, &wrapper_size)) {
|
|
RiffChunkHeader *riffhdr = WavpackGetWrapperLocation (block_buff, NULL);
|
|
ChunkHeader *datahdr = (ChunkHeader *)((char *) riffhdr + wrapper_size - sizeof (ChunkHeader));
|
|
uint32_t data_size = WavpackGetSampleIndex (wpc) * WavpackGetNumChannels (wpc) * WavpackGetBytesPerSample (wpc);
|
|
|
|
if (!strncmp (riffhdr->ckID, "RIFF", 4)) {
|
|
little_endian_to_native (riffhdr, ChunkHeaderFormat);
|
|
riffhdr->ckSize = wrapper_size + data_size - 8;
|
|
native_to_little_endian (riffhdr, ChunkHeaderFormat);
|
|
}
|
|
|
|
if (!strncmp (datahdr->ckID, "data", 4)) {
|
|
little_endian_to_native (datahdr, ChunkHeaderFormat);
|
|
datahdr->ckSize = data_size;
|
|
native_to_little_endian (datahdr, ChunkHeaderFormat);
|
|
}
|
|
}
|
|
|
|
if (DoSetFilePositionAbsolute (wv_file.file, 0) ||
|
|
!DoWriteFile (wv_file.file, block_buff, wv_file.first_block_size, &bcount) ||
|
|
bcount != wv_file.first_block_size) {
|
|
error_line ("couldn't update WavPack header with actual length!!");
|
|
result = SOFT_ERROR;
|
|
}
|
|
|
|
free (block_buff);
|
|
}
|
|
else {
|
|
error_line ("couldn't update WavPack header with actual length!!");
|
|
result = SOFT_ERROR;
|
|
}
|
|
|
|
if (result == NO_ERROR && wvc_file.file) {
|
|
block_buff = malloc (wvc_file.first_block_size);
|
|
|
|
if (block_buff && !DoSetFilePositionAbsolute (wvc_file.file, 0) &&
|
|
DoReadFile (wvc_file.file, block_buff, wvc_file.first_block_size, &bcount) &&
|
|
bcount == wvc_file.first_block_size && !strncmp (block_buff, "wvpk", 4)) {
|
|
|
|
WavpackUpdateNumSamples (wpc, block_buff);
|
|
|
|
if (DoSetFilePositionAbsolute (wvc_file.file, 0) ||
|
|
!DoWriteFile (wvc_file.file, block_buff, wvc_file.first_block_size, &bcount) ||
|
|
bcount != wvc_file.first_block_size) {
|
|
error_line ("couldn't update WavPack header with actual length!!");
|
|
result = SOFT_ERROR;
|
|
}
|
|
}
|
|
else {
|
|
error_line ("couldn't update WavPack header with actual length!!");
|
|
result = SOFT_ERROR;
|
|
}
|
|
|
|
free (block_buff);
|
|
}
|
|
|
|
if (result == NO_ERROR)
|
|
error_line ("warning: length was %s by %d samples, corrected",
|
|
WavpackGetSampleIndex (wpc) < total_samples ? "short" : "long",
|
|
abs (total_samples - WavpackGetSampleIndex (wpc)));
|
|
}
|
|
else {
|
|
error_line ("couldn't read all samples, file may be corrupt!!");
|
|
result = SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
// at this point we're done with the files, so close 'em whether there
|
|
// were any other errors or not
|
|
|
|
if (!DoCloseHandle (wv_file.file)) {
|
|
error_line ("can't close WavPack file!");
|
|
|
|
if (result == NO_ERROR)
|
|
result = SOFT_ERROR;
|
|
}
|
|
|
|
if (out2filename && !DoCloseHandle (wvc_file.file)) {
|
|
error_line ("can't close correction file!");
|
|
|
|
if (result == NO_ERROR)
|
|
result = SOFT_ERROR;
|
|
}
|
|
|
|
// if there were any errors, delete the output files, close the context,
|
|
// and return the error
|
|
|
|
if (result != NO_ERROR) {
|
|
DoDeleteFile (outfilename);
|
|
|
|
if (out2filename)
|
|
DoDeleteFile (out2filename);
|
|
|
|
WavpackCloseFile (wpc);
|
|
return result;
|
|
}
|
|
|
|
if (result == NO_ERROR && (loc_config.flags & CONFIG_COPY_TIME))
|
|
if (!copy_timestamp (infilename, outfilename) ||
|
|
(out2filename && !copy_timestamp (infilename, out2filename)))
|
|
error_line ("failure copying time stamp!");
|
|
|
|
// compute and display the time consumed along with some other details of
|
|
// the packing operation, and then return NO_ERROR
|
|
|
|
#if defined(WIN32)
|
|
_ftime (&time2);
|
|
dtime = time2.time + time2.millitm / 1000.0;
|
|
dtime -= time1.time + time1.millitm / 1000.0;
|
|
#else
|
|
gettimeofday(&time2,&timez);
|
|
dtime = time2.tv_sec + time2.tv_usec / 1000000.0;
|
|
dtime -= time1.tv_sec + time1.tv_usec / 1000000.0;
|
|
#endif
|
|
|
|
if ((loc_config.flags & CONFIG_CALC_NOISE) && pack_noise (wpc, NULL) > 0.0) {
|
|
int full_scale_bits = WavpackGetBitsPerSample (wpc);
|
|
double full_scale_rms = 0.5, sum, peak;
|
|
|
|
while (full_scale_bits--)
|
|
full_scale_rms *= 2.0;
|
|
|
|
full_scale_rms = full_scale_rms * (full_scale_rms - 1.0) * 0.5;
|
|
sum = pack_noise (wpc, &peak);
|
|
|
|
error_line ("ave noise = %.2f dB, peak noise = %.2f dB",
|
|
log10 (sum / WavpackGetNumSamples (wpc) / full_scale_rms) * 10,
|
|
log10 (peak / full_scale_rms) * 10);
|
|
}
|
|
|
|
if (!(loc_config.flags & CONFIG_QUIET_MODE)) {
|
|
char *file, *fext, *oper, *cmode, cratio [16] = "";
|
|
|
|
if (outfilename && *outfilename != '-') {
|
|
file = FN_FIT (outfilename);
|
|
fext = wvc_file.bytes_written ? " (+.wvc)" : "";
|
|
oper = "created";
|
|
}
|
|
else {
|
|
file = (*infilename == '-') ? "stdin" : FN_FIT (infilename);
|
|
fext = "";
|
|
oper = "packed";
|
|
}
|
|
|
|
if (WavpackLossyBlocks (wpc)) {
|
|
cmode = "lossy";
|
|
|
|
if (WavpackGetAverageBitrate (wpc, TRUE) != 0.0)
|
|
sprintf (cratio, ", %d kbps", (int) (WavpackGetAverageBitrate (wpc, TRUE) / 1000.0));
|
|
}
|
|
else {
|
|
cmode = "lossless";
|
|
|
|
if (WavpackGetRatio (wpc) != 0.0)
|
|
sprintf (cratio, ", %.2f%%", 100.0 - WavpackGetRatio (wpc) * 100.0);
|
|
}
|
|
|
|
error_line ("%s %s%s in %.2f secs (%s%s)", oper, file, fext, dtime, cmode, cratio);
|
|
}
|
|
|
|
WavpackCloseFile (wpc);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// This function handles the actual audio data compression. It assumes that the
|
|
// input file is positioned at the beginning of the audio data and that the
|
|
// WavPack configuration has been set. This is where the conversion from RIFF
|
|
// little-endian standard the executing processor's format is done and where
|
|
// (if selected) the MD5 sum is calculated and displayed.
|
|
|
|
#define INPUT_SAMPLES 65536
|
|
|
|
static int pack_audio (WavpackContext *wpc, FILE *infile)
|
|
{
|
|
uint32_t samples_remaining, samples_read = 0;
|
|
double progress = -1.0;
|
|
int bytes_per_sample;
|
|
int32_t *sample_buffer;
|
|
uchar *input_buffer;
|
|
MD5_CTX md5_context;
|
|
|
|
if (wpc->config.flags & CONFIG_MD5_CHECKSUM)
|
|
MD5Init (&md5_context);
|
|
|
|
WavpackPackInit (wpc);
|
|
bytes_per_sample = WavpackGetBytesPerSample (wpc) * WavpackGetNumChannels (wpc);
|
|
input_buffer = malloc (INPUT_SAMPLES * bytes_per_sample);
|
|
sample_buffer = malloc (INPUT_SAMPLES * sizeof (int32_t) * WavpackGetNumChannels (wpc));
|
|
samples_remaining = WavpackGetNumSamples (wpc);
|
|
|
|
while (1) {
|
|
uint32_t bytes_to_read, bytes_read = 0;
|
|
uint sample_count;
|
|
|
|
if ((wpc->config.flags & CONFIG_IGNORE_LENGTH) || samples_remaining > INPUT_SAMPLES)
|
|
bytes_to_read = INPUT_SAMPLES * bytes_per_sample;
|
|
else
|
|
bytes_to_read = samples_remaining * bytes_per_sample;
|
|
|
|
samples_remaining -= bytes_to_read / bytes_per_sample;
|
|
DoReadFile (infile, input_buffer, bytes_to_read, &bytes_read);
|
|
samples_read += sample_count = bytes_read / bytes_per_sample;
|
|
|
|
if (wpc->config.flags & CONFIG_MD5_CHECKSUM)
|
|
MD5Update (&md5_context, input_buffer, bytes_read);
|
|
|
|
if (!sample_count)
|
|
break;
|
|
|
|
if (sample_count) {
|
|
uint cnt = sample_count * WavpackGetNumChannels (wpc);
|
|
uchar *sptr = input_buffer;
|
|
int32_t *dptr = sample_buffer;
|
|
|
|
switch (WavpackGetBytesPerSample (wpc)) {
|
|
|
|
case 1:
|
|
while (cnt--)
|
|
*dptr++ = *sptr++ - 128;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (cnt--) {
|
|
*dptr++ = sptr [0] | ((int32_t)(signed char) sptr [1] << 8);
|
|
sptr += 2;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (cnt--) {
|
|
*dptr++ = sptr [0] | ((int32_t) sptr [1] << 8) | ((int32_t)(signed char) sptr [2] << 16);
|
|
sptr += 3;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (cnt--) {
|
|
*dptr++ = sptr [0] | ((int32_t) sptr [1] << 8) | ((int32_t) sptr [2] << 16) | ((int32_t)(signed char) sptr [3] << 24);
|
|
sptr += 4;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!WavpackPackSamples (wpc, sample_buffer, sample_count)) {
|
|
error_line ("%s", wpc->error_message);
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
return HARD_ERROR;
|
|
}
|
|
|
|
if (check_break ()) {
|
|
fprintf (stderr, "^C\n");
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
return SOFT_ERROR;
|
|
}
|
|
|
|
if (WavpackGetProgress (wpc) != -1.0 &&
|
|
progress != floor (WavpackGetProgress (wpc) * 100.0 + 0.5)) {
|
|
int nobs = progress == -1.0;
|
|
|
|
progress = WavpackGetProgress (wpc);
|
|
display_progress (progress);
|
|
progress = floor (progress * 100.0 + 0.5);
|
|
|
|
if (!(wpc->config.flags & CONFIG_QUIET_MODE))
|
|
fprintf (stderr, "%s%3d%% done...",
|
|
nobs ? " " : "\b\b\b\b\b\b\b\b\b\b\b\b", (int) progress);
|
|
}
|
|
}
|
|
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
|
|
if (!WavpackFlushSamples (wpc)) {
|
|
error_line ("%s", wpc->error_message);
|
|
return HARD_ERROR;
|
|
}
|
|
|
|
if (wpc->config.flags & CONFIG_MD5_CHECKSUM) {
|
|
char md5_string [] = "original md5 signature: 00000000000000000000000000000000";
|
|
uchar md5_digest [16];
|
|
int i;
|
|
|
|
MD5Final (md5_digest, &md5_context);
|
|
|
|
for (i = 0; i < 16; ++i)
|
|
sprintf (md5_string + 24 + (i * 2), "%02x", md5_digest [i]);
|
|
|
|
if (!(wpc->config.flags & CONFIG_QUIET_MODE))
|
|
error_line (md5_string);
|
|
|
|
WavpackStoreMD5Sum (wpc, md5_digest);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// Convert the Unicode wide-format string into a UTF-8 string using no more
|
|
// than the specified buffer length. The wide-format string must be NULL
|
|
// terminated and the resulting string will be NULL terminated. The actual
|
|
// number of characters converted (not counting terminator) is returned, which
|
|
// may be less than the number of characters in the wide string if the buffer
|
|
// length is exceeded.
|
|
|
|
static int WideCharToUTF8 (const ushort *Wide, uchar *pUTF8, int len)
|
|
{
|
|
const ushort *pWide = Wide;
|
|
int outndx = 0;
|
|
|
|
while (*pWide) {
|
|
if (*pWide < 0x80 && outndx + 1 < len)
|
|
pUTF8 [outndx++] = (uchar) *pWide++;
|
|
else if (*pWide < 0x800 && outndx + 2 < len) {
|
|
pUTF8 [outndx++] = (uchar) (0xc0 | ((*pWide >> 6) & 0x1f));
|
|
pUTF8 [outndx++] = (uchar) (0x80 | (*pWide++ & 0x3f));
|
|
}
|
|
else if (outndx + 3 < len) {
|
|
pUTF8 [outndx++] = (uchar) (0xe0 | ((*pWide >> 12) & 0xf));
|
|
pUTF8 [outndx++] = (uchar) (0x80 | ((*pWide >> 6) & 0x3f));
|
|
pUTF8 [outndx++] = (uchar) (0x80 | (*pWide++ & 0x3f));
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
pUTF8 [outndx] = 0;
|
|
return pWide - Wide;
|
|
}
|
|
|
|
// Convert a Ansi string into its Unicode UTF-8 format equivalent. The
|
|
// conversion is done in-place so the maximum length of the string buffer must
|
|
// be specified because the string may become longer or shorter. If the
|
|
// resulting string will not fit in the specified buffer size then it is
|
|
// truncated.
|
|
|
|
static void AnsiToUTF8 (char *string, int len)
|
|
{
|
|
int max_chars = strlen (string);
|
|
#if defined(WIN32)
|
|
ushort *temp = (ushort *) malloc ((max_chars + 1) * 2);
|
|
|
|
MultiByteToWideChar (CP_ACP, 0, string, -1, temp, max_chars + 1);
|
|
WideCharToUTF8 (temp, (uchar *) string, len);
|
|
#else
|
|
char *temp = malloc (len);
|
|
char *outp = temp;
|
|
char *inp = string;
|
|
size_t insize = max_chars;
|
|
size_t outsize = len - 1;
|
|
int err = 0;
|
|
char *old_locale;
|
|
|
|
memset(temp, 0, len);
|
|
old_locale = setlocale (LC_CTYPE, "");
|
|
iconv_t converter = iconv_open ("UTF-8", "");
|
|
err = iconv (converter, &inp, &insize, &outp, &outsize);
|
|
iconv_close (converter);
|
|
setlocale (LC_CTYPE, old_locale);
|
|
|
|
if (err == -1) {
|
|
free(temp);
|
|
return;
|
|
}
|
|
|
|
memmove (string, temp, len);
|
|
#endif
|
|
free (temp);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// This function displays the progress status on the title bar of the DOS //
|
|
// window that WavPack is running in. The "file_progress" argument is for //
|
|
// the current file only and ranges from 0 - 1; this function takes into //
|
|
// account the total number of files to generate a batch progress number. //
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void display_progress (double file_progress)
|
|
{
|
|
char title [40];
|
|
|
|
file_progress = (file_index + file_progress) / num_files;
|
|
sprintf (title, "%d%% (WavPack)", (int) ((file_progress * 100.0) + 0.5));
|
|
SetConsoleTitle (title);
|
|
}
|