4390 lines
159 KiB
C
4390 lines
159 KiB
C
////////////////////////////////////////////////////////////////////////////
|
|
// **** WAVPACK **** //
|
|
// Hybrid Lossless Wavefile Compressor //
|
|
// Copyright (c) 1998 - 2022 David Bryant. //
|
|
// 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)
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <conio.h>
|
|
#include <io.h>
|
|
#else
|
|
#if defined(__OS2__)
|
|
#define INCL_DOSPROCESS
|
|
#include <os2.h>
|
|
#include <io.h>
|
|
#endif
|
|
#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 <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include "wavpack.h"
|
|
#include "utils.h"
|
|
#include "md5.h"
|
|
|
|
#if (defined(__GNUC__) || defined(__sun)) && !defined(_WIN32)
|
|
#include <unistd.h>
|
|
#include <glob.h>
|
|
#include <sys/time.h>
|
|
#else
|
|
#include <sys/timeb.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#include "win32_unicode_support.h"
|
|
#define fputs fputs_utf8
|
|
#define fprintf fprintf_utf8
|
|
#define remove(f) unlink_utf8(f)
|
|
#define rename(o,n) rename_utf8(o,n)
|
|
#define fopen(f,m) fopen_utf8(f,m)
|
|
#define strdup(x) _strdup(x)
|
|
#define stricmp(x,y) _stricmp(x,y)
|
|
#define strdup(x) _strdup(x)
|
|
#define exp2(e) pow(2.0,e)
|
|
#else
|
|
#define stricmp strcasecmp
|
|
#endif
|
|
|
|
///////////////////////////// local variable storage //////////////////////////
|
|
|
|
static const char *sign_on = "\n"
|
|
" WAVPACK Hybrid Lossless Audio Compressor %s Version %s\n"
|
|
" Copyright (c) 1998 - 2022 David Bryant. All Rights Reserved.\n\n";
|
|
|
|
static const char *version_warning = "\n"
|
|
" WARNING: WAVPACK using libwavpack version %s, expected %s (see README)\n\n";
|
|
|
|
static const char *usage =
|
|
#if defined (_WIN32)
|
|
" Usage: WAVPACK [-options] infile[.wav]|infile.ext|- [outfile[.wv]|outpath|-]\n"
|
|
" WAVPACK --drop [-options] infile[.wav]|infile.ext [...]\n"
|
|
" (default is lossless; infile may contain wildcards: ?,*)\n\n"
|
|
#else
|
|
" Usage: WAVPACK [-options] infile[.wav]|infile.ext|- [...] [-o outfile[.wv]|outpath|-]\n"
|
|
" (default is lossless; multiple input files allowed)\n\n"
|
|
#endif
|
|
" Utils: WAVPACK: create or transcode WavPack files\n"
|
|
" WVUNPACK: unpack or verify existing WavPack files\n"
|
|
" WVGAIN: apply ReplayGain to WavPack files\n"
|
|
" WVTAG: apply or edit metadata tags on WavPack files\n\n"
|
|
" Formats: .wav (default, bwf/rf64 okay) .aif (Apple AIFF)\n"
|
|
" .w64 (Sony Wave64) .caf (Core Audio Format)\n"
|
|
" .dff (Philips DSDIFF) .dsf (Sony DSD stream)\n"
|
|
" .wv (transcode from existing WavPack file, with tags)\n\n"
|
|
" Options: -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"
|
|
#ifdef _WIN32
|
|
" --drop = drag-and-drop (multiple infiles only, no outfile spec)\n"
|
|
#endif
|
|
" -f = fast mode (fast, but some compromise in compression ratio)\n"
|
|
" -h = high quality (better compression ratio, but slower)\n"
|
|
" -m = compute & store MD5 signature of raw audio data\n"
|
|
#ifdef _WIN32
|
|
" --pause = pause before exiting (if console window disappears)\n"
|
|
#else
|
|
" -o FILENAME | PATH = specify output filename or path\n"
|
|
#endif
|
|
" -v = verify output file integrity after write (no pipes)\n"
|
|
" -x = extra encode processing (no decoding speed penalty)\n"
|
|
" --help = complete help\n\n"
|
|
" Web: Visit www.wavpack.com for latest version and info\n";
|
|
|
|
static const char *help =
|
|
#if defined (_WIN32)
|
|
" Usage:\n"
|
|
" WAVPACK [-options] infile[.wav]|infile.ext|- [outfile[.wv]|outpath|-]\n"
|
|
" WAVPACK --drop [-options] infile[.wav]|infile.ext [...]\n\n"
|
|
" The default operation is lossless. Wildcard characters (*,?) may be included\n"
|
|
" in the filename and the source file type is automatically determined (see\n"
|
|
" accepted formats below). Raw PCM or DSD may also be used (see --raw-pcm option).\n\n"
|
|
#else
|
|
" Usage:\n"
|
|
" WAVPACK [-options] infile[.wav]|infile.ext|- [...] [-o outfile[.wv]|outpath|-]\n\n"
|
|
" The default operation is lossless. Multiple input files may be specified\n"
|
|
" and the source file type is automatically determined (see accepted formats\n"
|
|
" below). Raw PCM or DSD data may also be used (see --raw-pcm option).\n\n"
|
|
#endif
|
|
" All Utilities: WAVPACK: create or transcode WavPack files\n"
|
|
" WVUNPACK: unpack or verify existing WavPack files\n"
|
|
" WVGAIN: apply ReplayGain to WavPack files\n"
|
|
" WVTAG: apply or edit metadata tags on WavPack files\n\n"
|
|
" Input Formats: .wav (default, includes bwf/rf64 variants)\n"
|
|
" .wv (WavPack transcode operation, tags copied)\n"
|
|
" .aif (Apple AIFF)\n"
|
|
" .caf (Core Audio Format)\n"
|
|
" .w64 (Sony Wave64)\n"
|
|
" .dff (Philips DSDIFF)\n"
|
|
" .dsf (Sony DSD stream)\n\n"
|
|
" Options:\n"
|
|
" -a Adobe Audition (CoolEdit) mode for 32-bit floats\n"
|
|
" --allow-huge-tags allow tag data up to 16 MB (embedding > 1 MB is not\n"
|
|
" recommended for portable devices and may not work\n"
|
|
" with some programs including WavPack pre-4.70)\n"
|
|
" -bn enable hybrid compression\n"
|
|
" n = 2.0 to 23.9 bits/sample, or\n"
|
|
" n = 24-9600 kbits/second (kbps)\n"
|
|
" add -c to create correction file (.wvc)\n"
|
|
" --blocksize=n specify block size in samples (max = 131072 and\n"
|
|
" min = 16 with --merge-blocks, otherwise 128)\n"
|
|
" -c hybrid lossless mode (use with -b to create\n"
|
|
" correction file (.wvc) in hybrid mode)\n"
|
|
" -cc maximum hybrid lossless compression (but degrades\n"
|
|
" decode speed and may result in lower quality)\n"
|
|
" --channel-order=<list> specify (comma separated) channel order if not\n"
|
|
" Microsoft standard (which is FL,FR,FC,LFE,BL,BR,\n"
|
|
" FLC,FRC,BC,SL,SR,TC,TFL,TFC,TFR,TBL,TBC,TBR);\n"
|
|
" specify '...' to indicate that channels are not\n"
|
|
" assigned to specific speakers, or terminate list\n"
|
|
" with '...' to indicate that any channels beyond\n"
|
|
" those specified are unassigned\n"
|
|
" --cross-decorr use cross-channel correlation in hybrid mode (on by\n"
|
|
" default in lossless mode and with -cc option)\n"
|
|
" -d delete source file if successful (use with caution!)\n"
|
|
#ifdef _WIN32
|
|
" --drop drag-and-drop (multiple infiles only, no outfile spec)\n"
|
|
#endif
|
|
" -f fast mode (faster encode and decode, but some\n"
|
|
" compromise in compression ratio)\n"
|
|
" --force-even-byte-depth ignore non-whole-byte bit depths (e.g., 12-bit or\n"
|
|
" 20-bit) and round up to the next whole byte\n"
|
|
" -g general/normal mode (cancels -f and -h options)\n"
|
|
" -h high quality (better compression ratio, but slightly\n"
|
|
" slower encode and decode than normal mode)\n"
|
|
" -hh very high quality (best compression, but slowest\n"
|
|
" and not recommended for vintage hardware use)\n"
|
|
" --help this extended help display\n"
|
|
" -i parse header for audio format but ignore length in\n"
|
|
" header and just assume everything to EOF is audio\n"
|
|
" --import-id3 attempt to import ID3v2 tags from the trailer of files\n"
|
|
" (standard on DSF, optional on WAV and DSDIFF)\n"
|
|
" -jn joint-stereo override (0 = left/right, 1 = mid/side)\n"
|
|
#if defined (_WIN32) || defined (__OS2__)
|
|
" -l run at lower priority for smoother multitasking\n"
|
|
#endif
|
|
" -m compute & store MD5 signature of raw audio data\n"
|
|
" --merge-blocks merge consecutive blocks with equal redundancy\n"
|
|
" (used with --blocksize option and is useful for\n"
|
|
" files generated by the lossyWAV program or\n"
|
|
" decoded HDCD files)\n"
|
|
" -n calculate average and peak quantization noise\n"
|
|
" (for hybrid mode only, reference fullscale sine)\n"
|
|
" --no-overwrite never overwrite existing files (and don't ask)\n"
|
|
#ifdef _WIN32
|
|
" --no-utf8-convert assume tag values read from files are already UTF-8,\n"
|
|
" don't attempt to convert from local encoding\n"
|
|
#else
|
|
" --no-utf8-convert don't recode passed tags from local encoding to\n"
|
|
" UTF-8, assume they are in UTF-8 already\n"
|
|
" -o FILENAME | PATH specify output filename or path\n"
|
|
#endif
|
|
" --pair-unassigned-chans encode unassigned channels into stereo pairs\n"
|
|
#ifdef _WIN32
|
|
" --pause pause before exiting (if console window disappears)\n"
|
|
#endif
|
|
" --pre-quantize=bits pre-quantize samples to <bits> BEFORE encoding and MD5\n"
|
|
" (common use would be --pre-quantize=20 for 24-bit or\n"
|
|
" float material recorded with typical converters)\n"
|
|
" -q quiet (keep console output to a minimum)\n"
|
|
" -r parse headers for audio information but do not store in\n"
|
|
" WavPack file (minimum header regenerated on unpack)\n"
|
|
" --raw-pcm input data is raw pcm (default is 44100 Hz, 16-bit\n"
|
|
" signed, 2-channels, little-endian)\n"
|
|
" --raw-pcm=sr,bps[f|s|u],nch,[le|be]\n"
|
|
" input data is raw pcm with specified sample rate,\n"
|
|
" sample bit depth (float or signed or unsigned), number\n"
|
|
" of channels, and little-endian or big-endian\n"
|
|
" (defaulted parameters may be omitted)\n"
|
|
" --raw-pcm-skip=begin[,end]\n"
|
|
" skip <begin> bytes before encoding (i.e., a header)\n"
|
|
" and <end> bytes at the end-of-file (i.e., a trailer)\n"
|
|
" -sn override default noise shaping where n is a float\n"
|
|
" value between -1.0 and 1.0; negative values move noise\n"
|
|
" lower in freq, positive values move noise higher\n"
|
|
" in freq, use '0' for no shaping (white noise)\n"
|
|
" -t copy input file's time stamp to output file(s)\n"
|
|
" --use-dns force use of dynamic noise shaping (hybrid mode only)\n"
|
|
" -v verify output file integrity after write (no pipes)\n"
|
|
" --version write the version to stdout\n"
|
|
" -w Encoder write actual \"Encoder\" information to APEv2 tag\n"
|
|
" -w Settings write actual \"Settings\" information to APEv2 tag\n"
|
|
" -w \"Field=Value\" write specified text metadata to APEv2 tag\n"
|
|
" -w \"Field=@file.ext\" write specified text metadata from file to APEv2\n"
|
|
" tag, normally used for embedded cuesheets and logs\n"
|
|
" (field names \"Cuesheet\" and \"Log\")\n"
|
|
" --write-binary-tag \"Field=@file.ext\"\n"
|
|
" write the specified binary metadata file to APEv2\n"
|
|
" tag, normally used for cover art with the specified\n"
|
|
" field name \"Cover Art (Front)\"\n"
|
|
" -x[n] extra encode processing (optional n = 0 to 6, 1=default)\n"
|
|
" -x0 no extra processing (fastest encode speed)\n"
|
|
" -x1 to -x3 to choose best of predefined filters\n"
|
|
" -x4 to -x6 to generate custom filters (very slow!)\n"
|
|
" -y yes to all warnings (use with caution!)\n"
|
|
#if defined (_WIN32)
|
|
" -z don't set console title to indicate progress\n\n"
|
|
#else
|
|
" -z1 set console title to indicate progress\n\n"
|
|
#endif
|
|
" Web:\n"
|
|
" Visit www.wavpack.com for latest version and complete information\n";
|
|
|
|
static const char *speakers [] = {
|
|
"FL", "FR", "FC", "LFE", "BL", "BR", "FLC", "FRC", "BC",
|
|
"SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL", "TBC", "TBR"
|
|
};
|
|
|
|
#define NUM_SPEAKERS (sizeof (speakers) / sizeof (speakers [0]))
|
|
|
|
int ParseRiffHeaderConfig (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config);
|
|
int ParseWave64HeaderConfig (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config);
|
|
int ParseCaffHeaderConfig (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config);
|
|
int ParseDsdiffHeaderConfig (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config);
|
|
int ParseDsfHeaderConfig (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config);
|
|
int ParseAiffHeaderConfig (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config);
|
|
|
|
static struct {
|
|
unsigned char id;
|
|
char *fourcc, *default_extension;
|
|
int (* ParseHeader) (FILE *infile, char *infilename, char *fourcc, WavpackContext *wpc, WavpackConfig *config);
|
|
int chunk_alignment;
|
|
} file_formats [] = {
|
|
{ WP_FORMAT_WAV, "RIFF", "wav", ParseRiffHeaderConfig, 2 },
|
|
{ WP_FORMAT_WAV, "RF64", "wav", ParseRiffHeaderConfig, 2 },
|
|
{ WP_FORMAT_W64, "riff", "w64", ParseWave64HeaderConfig, 8 },
|
|
{ WP_FORMAT_CAF, "caff", "caf", ParseCaffHeaderConfig, 1 },
|
|
{ WP_FORMAT_DFF, "FRM8", "dff", ParseDsdiffHeaderConfig, 2 },
|
|
{ WP_FORMAT_DSF, "DSD ", "dsf", ParseDsfHeaderConfig, 1 },
|
|
{ WP_FORMAT_AIF, "FORM", "aif", ParseAiffHeaderConfig, 2 }
|
|
};
|
|
|
|
#define NUM_FILE_FORMATS (sizeof (file_formats) / sizeof (file_formats [0]))
|
|
|
|
// 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, no_overwrite, num_files, file_index, copy_time, quiet_mode, verify_mode, delete_source,
|
|
no_utf8_convert, set_console_title, allow_huge_tags, quantize_bits, quantize_round, import_id3,
|
|
raw_pcm_skip_bytes_begin, raw_pcm_skip_bytes_end;
|
|
|
|
static int num_channels_order;
|
|
static unsigned char channel_order [18];
|
|
static double encode_time_percent;
|
|
|
|
// These two statics are used to keep track of tags that the user specifies on the
|
|
// command line. The "num_tag_strings" and "tag_strings" fields in the WavpackConfig
|
|
// structure are no longer used for anything (they should not have been there in
|
|
// the first place).
|
|
|
|
static int num_tag_items, total_tag_size;
|
|
|
|
static struct tag_item {
|
|
char *item, *value, *ext;
|
|
int vsize, binary;
|
|
} *tag_items;
|
|
|
|
#if defined (_WIN32)
|
|
static int pause_mode, drop_mode;
|
|
#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, int qmode, unsigned char *new_order, unsigned char *md5_digest_source);
|
|
static int pack_dsd_audio (WavpackContext *wpc, FILE *infile, int qmode, unsigned char *new_order, unsigned char *md5_digest_source);
|
|
static int repack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config);
|
|
static int repack_audio (WavpackContext *wpc, WavpackContext *infile, unsigned char *md5_digest_source);
|
|
static int verify_audio (char *infilename, unsigned char *md5_digest_source);
|
|
static void make_settings_string (char *settings, WavpackConfig *config);
|
|
static void display_progress (double file_progress);
|
|
static void TextToUTF8 (void *string, int len);
|
|
|
|
#define WAVPACK_NO_ERROR 0
|
|
#define WAVPACK_SOFT_ERROR 1
|
|
#define WAVPACK_HARD_ERROR 2
|
|
|
|
// The "main" function for the command-line WavPack compressor. Note that on Windows
|
|
// this is actually a static function that is called from the "real" main() defined
|
|
// immediately afterward that converts the wchar argument list into UTF-8 strings
|
|
// and sets the console to UTF-8 for better Unicode support.
|
|
|
|
#ifdef _WIN32
|
|
static int wavpack_main(int argc, char **argv)
|
|
#else
|
|
int main (int argc, char **argv)
|
|
#endif
|
|
{
|
|
#ifdef __EMX__ /* OS/2 */
|
|
_wildcard (&argc, &argv);
|
|
#endif
|
|
int error_count = 0, tag_next_arg = 0, output_spec = 0, argc_fn = 0, use_stdin = 0, use_stdout = 0;
|
|
char *outfilename = NULL, *out2filename = NULL;
|
|
char selfname [PATH_MAX];
|
|
char **argv_fn = NULL;
|
|
char **matches = NULL;
|
|
WavpackConfig config;
|
|
int result, argi, i;
|
|
|
|
#if defined(_WIN32)
|
|
if (!GetModuleFileName (NULL, selfname, sizeof (selfname)))
|
|
#endif
|
|
strncpy (selfname, *argv, sizeof (selfname) - 1);
|
|
selfname [sizeof (selfname) - 1] = '\0';
|
|
|
|
if (filespec_name (selfname)) {
|
|
char *filename = filespec_name (selfname);
|
|
|
|
if (strstr (filename, "ebug") || strstr (filename, "DEBUG"))
|
|
debug_logging_mode = TRUE;
|
|
|
|
while (strchr (filename, '{')) {
|
|
char *open_brace = strchr (filename, '{');
|
|
char *close_brace = strchr (open_brace, '}');
|
|
|
|
if (!close_brace)
|
|
break;
|
|
|
|
if (close_brace - open_brace > 1) {
|
|
int option_len = (int)(close_brace - open_brace) - 1;
|
|
char *option = malloc (option_len + 1);
|
|
|
|
argv_fn = realloc (argv_fn, sizeof (char *) * ++argc_fn);
|
|
memcpy (option, open_brace + 1, option_len);
|
|
argv_fn [argc_fn - 1] = option;
|
|
option [option_len] = 0;
|
|
|
|
if (debug_logging_mode)
|
|
error_line ("file arg %d: %s", argc_fn, option);
|
|
}
|
|
|
|
filename = close_brace;
|
|
}
|
|
}
|
|
|
|
if (debug_logging_mode) {
|
|
char **argv_t = argv;
|
|
int argc_t = argc;
|
|
|
|
while (--argc_t)
|
|
error_line ("cli arg %d: %s", argc - argc_t, *++argv_t);
|
|
}
|
|
|
|
#if defined (_WIN32)
|
|
set_console_title = 1; // on Windows, we default to messing with the console title
|
|
#endif // on Linux, this is considered uncool to do by default
|
|
|
|
CLEAR (config);
|
|
|
|
// loop through command-line arguments
|
|
|
|
for (argi = 0; argi < argc + argc_fn - 1; ++argi) {
|
|
char *argcp;
|
|
|
|
if (argi < argc_fn)
|
|
argcp = argv_fn [argi];
|
|
else
|
|
argcp = argv [argi - argc_fn + 1];
|
|
|
|
if (argcp [0] == '-' && argcp [1] == '-' && argcp [2]) {
|
|
char *long_option = argcp + 2, *long_param = long_option;
|
|
|
|
while (*long_param)
|
|
if (*long_param++ == '=')
|
|
break;
|
|
|
|
if (!strcmp (long_option, "help")) { // --help
|
|
printf ("%s", help);
|
|
return 0;
|
|
}
|
|
else if (!strcmp (long_option, "version")) { // --version
|
|
printf ("wavpack %s\n", PACKAGE_VERSION);
|
|
printf ("libwavpack %s\n", WavpackGetLibraryVersionString ());
|
|
return 0;
|
|
}
|
|
#ifdef _WIN32
|
|
else if (!strcmp (long_option, "pause")) // --pause
|
|
pause_mode = 1;
|
|
else if (!strcmp (long_option, "drop")) // --drop
|
|
drop_mode = 1;
|
|
#endif
|
|
else if (!strcmp (long_option, "optimize-mono")) // --optimize-mono
|
|
error_line ("warning: --optimize-mono deprecated, now enabled by default");
|
|
else if (!strcmp (long_option, "dns")) { // --dns
|
|
error_line ("warning: --dns deprecated, use --use-dns");
|
|
++error_count;
|
|
}
|
|
else if (!strcmp (long_option, "use-dns")) // --use-dns
|
|
config.flags |= CONFIG_DYNAMIC_SHAPING;
|
|
else if (!strcmp (long_option, "cross-decorr")) // --cross-decorr
|
|
config.flags |= CONFIG_CROSS_DECORR;
|
|
else if (!strcmp (long_option, "merge-blocks")) // --merge-blocks
|
|
config.flags |= CONFIG_MERGE_BLOCKS;
|
|
else if (!strcmp (long_option, "pair-unassigned-chans")) // --pair-unassigned-chans
|
|
config.flags |= CONFIG_PAIR_UNDEF_CHANS;
|
|
else if (!strcmp (long_option, "force-even-byte-depth")) // --force-even-byte_depth
|
|
config.qmode |= QMODE_EVEN_BYTE_DEPTH;
|
|
else if (!strcmp (long_option, "import-id3")) // --import-id3
|
|
import_id3 = 1;
|
|
else if (!strcmp (long_option, "no-utf8-convert")) // --no-utf8-convert
|
|
no_utf8_convert = 1;
|
|
else if (!strcmp (long_option, "no-overwrite")) // --no-overwrite
|
|
no_overwrite = 1;
|
|
else if (!strcmp (long_option, "allow-huge-tags")) // --allow-huge-tags
|
|
allow_huge_tags = 1;
|
|
else if (!strcmp (long_option, "write-binary-tag")) // --write-binary-tag
|
|
tag_next_arg = 2;
|
|
else if (!strncmp (long_option, "raw-pcm-skip", 12)) { // --raw-pcm-skip
|
|
raw_pcm_skip_bytes_begin = strtol (long_param, &long_param, 10);
|
|
|
|
if (*long_param == ',')
|
|
raw_pcm_skip_bytes_end = strtol (++long_param, &long_param, 10);
|
|
|
|
if (*long_param || raw_pcm_skip_bytes_begin < 0 || raw_pcm_skip_bytes_end < 0) {
|
|
error_line ("syntax error in raw-pcm-skip specification!");
|
|
++error_count;
|
|
}
|
|
|
|
error_line ("raw_pcm_skip = %d, %d bytes", raw_pcm_skip_bytes_begin, raw_pcm_skip_bytes_end);
|
|
}
|
|
else if (!strncmp (long_option, "raw-pcm", 7)) { // --raw-pcm & --raw-pcm-ex
|
|
int extended = !strncmp (long_option, "raw-pcm-ex", 10);
|
|
int params [] = { 44100, 16, 2 };
|
|
int pi, fp = 0, be = 0, us = 0, s = 0;
|
|
|
|
for (pi = 0; *long_param && pi < 3; ++pi) {
|
|
if (isdigit (*long_param))
|
|
params [pi] = strtol (long_param, &long_param, 10);
|
|
|
|
if (pi == 1) {
|
|
if (*long_param == 'f' || *long_param == 'F') {
|
|
long_param++;
|
|
fp = 1;
|
|
}
|
|
else if (*long_param == 'u' || *long_param == 'U') {
|
|
long_param++;
|
|
us = 1;
|
|
}
|
|
else if (*long_param == 's' || *long_param == 'S') {
|
|
long_param++;
|
|
s = 1;
|
|
}
|
|
}
|
|
|
|
if (*long_param == ',')
|
|
long_param++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (*long_param && pi == 3) {
|
|
if (!stricmp (long_param, "be")) {
|
|
long_param += 2;
|
|
be = 1;
|
|
}
|
|
else if (!stricmp (long_param, "le"))
|
|
long_param += 2;
|
|
}
|
|
|
|
if (*long_param) {
|
|
error_line ("syntax error in raw PCM specification!");
|
|
++error_count;
|
|
}
|
|
else if (params [0] < 1 || params [0] > 1000000000 ||
|
|
params [1] < 1 || params [1] > 32 || (fp && params [1] != 32) ||
|
|
params [2] < 1 || params [2] > (extended ? WAVPACK_MAX_CHANS : WAVPACK_MAX_CLI_CHANS)) {
|
|
error_line ("argument range error in raw PCM specification!");
|
|
++error_count;
|
|
}
|
|
else if (params [1] == 1) {
|
|
config.sample_rate = params [0] / 8;
|
|
config.bits_per_sample = params [1] * 8;
|
|
config.bytes_per_sample = 1;
|
|
config.num_channels = params [2];
|
|
config.qmode |= QMODE_DSD_MSB_FIRST | QMODE_RAW_PCM;
|
|
}
|
|
else {
|
|
config.sample_rate = params [0];
|
|
config.bits_per_sample = params [1];
|
|
config.bytes_per_sample = (params [1] + 7) / 8;
|
|
config.num_channels = params [2];
|
|
config.float_norm_exp = fp ? 127 : 0;
|
|
config.qmode |= QMODE_RAW_PCM;
|
|
|
|
if (params [1] > 8) {
|
|
if (us)
|
|
config.qmode |= QMODE_UNSIGNED_WORDS;
|
|
|
|
if (be)
|
|
config.qmode |= QMODE_BIG_ENDIAN;
|
|
}
|
|
else if (s)
|
|
config.qmode |= QMODE_SIGNED_BYTES;
|
|
}
|
|
}
|
|
else if (!strncmp (long_option, "blocksize", 9)) { // --blocksize
|
|
config.block_samples = strtol (long_param, NULL, 10);
|
|
|
|
if (config.block_samples < 16 || config.block_samples > 131072) {
|
|
error_line ("invalid blocksize!");
|
|
++error_count;
|
|
}
|
|
}
|
|
else if (!strncmp (long_option, "channel-order", 13)) { // --channel-order
|
|
char name [6], channel_error = 0;
|
|
uint32_t mask = 0;
|
|
int chan, ci, si;
|
|
|
|
for (chan = 0; chan < sizeof (channel_order); ++chan) {
|
|
|
|
if (!*long_param)
|
|
break;
|
|
|
|
if (*long_param == '.') {
|
|
if (*++long_param == '.' && *++long_param == '.' && !*++long_param)
|
|
config.qmode |= QMODE_CHANS_UNASSIGNED;
|
|
else
|
|
channel_error = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
for (ci = 0; isalpha (*long_param) && ci < sizeof (name) - 1; ci++)
|
|
name [ci] = *long_param++;
|
|
|
|
if (!ci) {
|
|
channel_error = 1;
|
|
break;
|
|
}
|
|
|
|
name [ci] = 0;
|
|
|
|
for (si = 0; si < NUM_SPEAKERS; ++si)
|
|
if (!stricmp (name, speakers [si])) {
|
|
if (mask & (1L << si))
|
|
channel_error = 1;
|
|
|
|
channel_order [chan] = si;
|
|
mask |= (1L << si);
|
|
break;
|
|
}
|
|
|
|
if (channel_error || si == NUM_SPEAKERS) {
|
|
error_line ("unknown or repeated channel spec: %s!", name);
|
|
channel_error = 1;
|
|
break;
|
|
}
|
|
|
|
if (*long_param && *long_param++ != ',') {
|
|
channel_error = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (channel_error) {
|
|
error_line ("syntax error in channel order specification!");
|
|
++error_count;
|
|
}
|
|
else if (*long_param) {
|
|
error_line ("too many channels specified!");
|
|
++error_count;
|
|
}
|
|
else {
|
|
config.channel_mask = mask;
|
|
num_channels_order = chan;
|
|
}
|
|
}
|
|
else if (!strncmp (long_option, "pre-quantize-round", 18)) { // --pre-quantize-round=
|
|
quantize_round = quantize_bits = strtol(long_param, NULL, 10);
|
|
|
|
if (quantize_bits < 4 || quantize_bits > 32) {
|
|
error_line ("invalid quantize bits!");
|
|
++error_count;
|
|
}
|
|
}
|
|
else if (!strncmp (long_option, "pre-quantize", 12)) { // --pre-quantize=
|
|
quantize_bits = strtol(long_param, NULL, 10);
|
|
|
|
if (quantize_bits < 4 || quantize_bits > 32) {
|
|
error_line ("invalid quantize bits!");
|
|
++error_count;
|
|
}
|
|
}
|
|
else {
|
|
error_line ("unknown option: %s !", long_option);
|
|
++error_count;
|
|
}
|
|
}
|
|
#if defined (_WIN32)
|
|
else if ((argcp [0] == '-' || argcp [0] == '/') && argcp [1])
|
|
#else
|
|
else if (argcp [0] == '-' && argcp [1])
|
|
#endif
|
|
while (*++argcp)
|
|
switch (*argcp) {
|
|
|
|
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':
|
|
if (isdigit (*++argcp))
|
|
config.xmode = strtol (argcp, &argcp, 10);
|
|
else
|
|
config.xmode = 1; // 'x' with no value specified = 1
|
|
|
|
if (config.xmode < 0 || config.xmode > 6) {
|
|
error_line ("extra mode only goes from 0 to 6!");
|
|
++error_count;
|
|
}
|
|
else if (config.xmode)
|
|
config.flags |= CONFIG_EXTRA_MODE;
|
|
else
|
|
config.flags &= ~CONFIG_EXTRA_MODE;
|
|
|
|
--argcp;
|
|
break;
|
|
|
|
case 'F': case 'f':
|
|
config.flags &= ~(CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG);
|
|
config.flags |= CONFIG_FAST_FLAG;
|
|
break;
|
|
|
|
case 'G': case 'g':
|
|
config.flags &= ~(CONFIG_FAST_FLAG | CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG);
|
|
break;
|
|
|
|
case 'H': case 'h':
|
|
config.flags &= ~CONFIG_FAST_FLAG;
|
|
|
|
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.qmode |= QMODE_ADOBE_MODE;
|
|
break;
|
|
#if defined (_WIN32)
|
|
case 'L': case 'l':
|
|
SetPriorityClass (GetCurrentProcess(), IDLE_PRIORITY_CLASS);
|
|
break;
|
|
#elif defined (__OS2__)
|
|
case 'L': case 'l':
|
|
DosSetPriority (0, PRTYC_IDLETIME, 0, 0);
|
|
break;
|
|
#endif
|
|
#if defined (_WIN32)
|
|
case 'O': case 'o': // ignore -o in Windows to be Linux compatible
|
|
break;
|
|
#else
|
|
case 'O': case 'o':
|
|
output_spec = 1;
|
|
break;
|
|
#endif
|
|
case 'T': case 't':
|
|
copy_time = 1;
|
|
break;
|
|
|
|
case 'Q': case 'q':
|
|
quiet_mode = 1;
|
|
break;
|
|
|
|
case 'Z': case 'z':
|
|
set_console_title = strtol (++argcp, &argcp, 10);
|
|
--argcp;
|
|
break;
|
|
|
|
case 'M': case 'm':
|
|
config.flags |= CONFIG_MD5_CHECKSUM;
|
|
break;
|
|
|
|
case 'I': case 'i':
|
|
config.qmode |= QMODE_IGNORE_LENGTH;
|
|
break;
|
|
|
|
case 'R': case 'r':
|
|
config.qmode |= QMODE_NO_STORE_WRAPPER;
|
|
break;
|
|
|
|
case 'V': case 'v':
|
|
verify_mode = 1;
|
|
break;
|
|
|
|
case 'B': case 'b':
|
|
config.flags |= CONFIG_HYBRID_FLAG;
|
|
config.bitrate = (float) strtod (++argcp, &argcp);
|
|
--argcp;
|
|
|
|
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 (++argcp, &argcp, 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;
|
|
}
|
|
|
|
--argcp;
|
|
break;
|
|
|
|
case 'S': case 's':
|
|
config.shaping_weight = (float) strtod (++argcp, &argcp);
|
|
|
|
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;
|
|
}
|
|
|
|
--argcp;
|
|
break;
|
|
|
|
case 'W': case 'w':
|
|
if (++tag_next_arg == 2) {
|
|
error_line ("warning: -ww deprecated, use --write-binary-tag");
|
|
++error_count;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
error_line ("illegal option: %c !", *argcp);
|
|
++error_count;
|
|
}
|
|
else if (tag_next_arg) {
|
|
char *cp;
|
|
|
|
// check for and allow "encoder" or "settings" without a value and create
|
|
// an appropriate value for them (otherwise missing value is an error)
|
|
|
|
if (!stricmp (argcp, "encoder")) {
|
|
char *tag_arg = malloc (80);
|
|
sprintf (tag_arg, "%s=WavPack %s", argcp, PACKAGE_VERSION);
|
|
argcp = tag_arg;
|
|
}
|
|
else if (!stricmp (argcp, "settings")) {
|
|
char settings [256], *tag_arg;
|
|
|
|
make_settings_string (settings, &config);
|
|
tag_arg = malloc (strlen (settings) + 16);
|
|
sprintf (tag_arg, "%s=%s", argcp, settings);
|
|
argcp = tag_arg;
|
|
}
|
|
|
|
cp = strchr (argcp, '=');
|
|
|
|
if (cp && cp > argcp) {
|
|
int i = num_tag_items;
|
|
|
|
tag_items = realloc (tag_items, ++num_tag_items * sizeof (*tag_items));
|
|
tag_items [i].item = malloc (cp - argcp + 1);
|
|
memcpy (tag_items [i].item, argcp, cp - argcp);
|
|
tag_items [i].item [cp - argcp] = 0;
|
|
tag_items [i].vsize = (int) strlen (cp + 1);
|
|
tag_items [i].value = malloc (tag_items [i].vsize + 1);
|
|
strcpy (tag_items [i].value, cp + 1);
|
|
tag_items [i].binary = (tag_next_arg == 2);
|
|
tag_items [i].ext = NULL;
|
|
}
|
|
else {
|
|
error_line ("error in tag spec: %s !", argcp);
|
|
++error_count;
|
|
}
|
|
|
|
tag_next_arg = 0;
|
|
}
|
|
else if (argi < argc_fn) {
|
|
error_line ("invalid use of filename-embedded args: %s !", argcp);
|
|
++error_count;
|
|
}
|
|
#if defined (_WIN32)
|
|
else if (drop_mode || !num_files) {
|
|
matches = realloc (matches, (num_files + 1) * sizeof (*matches));
|
|
matches [num_files] = malloc (strlen (argcp) + 10);
|
|
strcpy (matches [num_files], argcp);
|
|
use_stdin |= (*argcp == '-');
|
|
|
|
if (*(matches [num_files]) != '-' && *(matches [num_files]) != '@' &&
|
|
!filespec_ext (matches [num_files]))
|
|
strcat (matches [num_files], (config.qmode & QMODE_RAW_PCM) ? ".raw" : ".wav");
|
|
|
|
num_files++;
|
|
}
|
|
else if (!outfilename) {
|
|
outfilename = malloc (strlen (argcp) + PATH_MAX);
|
|
strcpy (outfilename, argcp);
|
|
use_stdout = (*argcp == '-');
|
|
}
|
|
else if (!out2filename) {
|
|
out2filename = malloc (strlen (argcp) + PATH_MAX);
|
|
strcpy (out2filename, argcp);
|
|
}
|
|
else {
|
|
error_line ("extra unknown argument: %s !", argcp);
|
|
++error_count;
|
|
}
|
|
#else
|
|
else if (output_spec) {
|
|
outfilename = malloc (strlen (argcp) + PATH_MAX);
|
|
strcpy (outfilename, argcp);
|
|
use_stdout = (*argcp == '-');
|
|
output_spec = 0;
|
|
}
|
|
else {
|
|
matches = realloc (matches, (num_files + 1) * sizeof (*matches));
|
|
matches [num_files] = malloc (strlen (argcp) + 10);
|
|
strcpy (matches [num_files], argcp);
|
|
use_stdin |= (*argcp == '-');
|
|
|
|
if (*(matches [num_files]) != '-' && *(matches [num_files]) != '@' &&
|
|
!filespec_ext (matches [num_files]))
|
|
strcat (matches [num_files], (config.qmode & QMODE_RAW_PCM) ? ".raw" : ".wav");
|
|
|
|
num_files++;
|
|
}
|
|
#endif
|
|
if (argi < argc_fn)
|
|
free (argv_fn [argi]);
|
|
}
|
|
|
|
free (argv_fn);
|
|
|
|
setup_break (); // set up console and detect ^C and ^Break
|
|
|
|
// check for various command-line argument problems
|
|
|
|
if (output_spec) {
|
|
error_line ("no output filename or path specified with -o option!");
|
|
++error_count;
|
|
}
|
|
|
|
if (use_stdin && num_files > 1) {
|
|
error_line ("when stdin is used for input, it must be the only file!");
|
|
++error_count;
|
|
}
|
|
|
|
if (use_stdin && !outfilename) // for stdin source, no output specification implies stdout
|
|
use_stdout = 1;
|
|
|
|
if (overwrite_all && no_overwrite) {
|
|
error_line ("overwrite all and no overwrite and mutually exclusive!");
|
|
++error_count;
|
|
}
|
|
|
|
if (tag_next_arg) {
|
|
error_line ("no tag specified with %s option!", tag_next_arg == 1 ? "-w" : "--write-binary-tag");
|
|
++error_count;
|
|
}
|
|
|
|
if ((config.qmode & QMODE_IGNORE_LENGTH) && use_stdin && use_stdout && !overwrite_all) {
|
|
error_line ("can't ignore length in header when both input and output are pipes, '-y' to override");
|
|
++error_count;
|
|
}
|
|
|
|
if ((config.qmode & QMODE_RAW_PCM) && use_stdin && use_stdout && !overwrite_all) {
|
|
error_line ("can't process raw PCM when both input and output are pipes, '-y' to override");
|
|
++error_count;
|
|
}
|
|
|
|
if (verify_mode && use_stdout) {
|
|
error_line ("can't verify output file when using stdout!");
|
|
++error_count;
|
|
}
|
|
|
|
if (config.flags & CONFIG_HYBRID_FLAG) {
|
|
if ((config.flags & CONFIG_CREATE_WVC) && use_stdout) {
|
|
error_line ("can't create correction file when using stdout!");
|
|
++error_count;
|
|
}
|
|
if (config.flags & CONFIG_MERGE_BLOCKS) {
|
|
error_line ("--merge-blocks option is for lossless mode only!");
|
|
++error_count;
|
|
}
|
|
if ((config.flags & CONFIG_SHAPE_OVERRIDE) && (config.flags & CONFIG_DYNAMIC_SHAPING)) {
|
|
error_line ("-s and --use-dns options are mutually exclusive!");
|
|
++error_count;
|
|
}
|
|
}
|
|
else {
|
|
if (config.flags & (CONFIG_CALC_NOISE | CONFIG_SHAPE_OVERRIDE | CONFIG_CREATE_WVC | CONFIG_DYNAMIC_SHAPING)) {
|
|
error_line ("-c, -n, -s, and --use-dns options are for hybrid mode (-b) only!");
|
|
++error_count;
|
|
}
|
|
}
|
|
|
|
if (config.flags & CONFIG_MERGE_BLOCKS) {
|
|
if (!config.block_samples) {
|
|
error_line ("--merge-blocks only makes sense when --blocksize is specified!");
|
|
++error_count;
|
|
}
|
|
}
|
|
else if (config.block_samples && config.block_samples < 128) {
|
|
error_line ("minimum blocksize is 128 when --merge-blocks is not specified!");
|
|
++error_count;
|
|
}
|
|
|
|
if (strcmp (WavpackGetLibraryVersionString (), PACKAGE_VERSION)) {
|
|
fprintf (stderr, version_warning, WavpackGetLibraryVersionString (), PACKAGE_VERSION);
|
|
fflush (stderr);
|
|
}
|
|
else if (!quiet_mode && !error_count) {
|
|
fprintf (stderr, sign_on, VERSION_OS, WavpackGetLibraryVersionString ());
|
|
fflush (stderr);
|
|
}
|
|
|
|
// Loop through any tag specification strings and check for file access, convert text
|
|
// strings to UTF-8, and otherwise prepare for writing to APE tags. This is done here
|
|
// rather than after encoding so that any errors can be reported to the user now.
|
|
|
|
for (i = 0; i < num_tag_items; ++i) {
|
|
#ifdef _WIN32
|
|
int tag_came_from_file = 0;
|
|
#endif
|
|
if (*tag_items [i].value == '@') {
|
|
char *fn = tag_items [i].value + 1, *new_value = NULL;
|
|
FILE *file = wild_fopen (fn, "rb");
|
|
|
|
// if the file is not found, try using any input and output directories that the
|
|
// user may have specified on the command line
|
|
|
|
if (!file && num_files && filespec_name (matches [0]) && *matches [0] != '-') {
|
|
char *temp = malloc (strlen (matches [0]) + PATH_MAX);
|
|
|
|
strcpy (temp, matches [0]);
|
|
strcpy (filespec_name (temp), fn);
|
|
file = wild_fopen (temp, "rb");
|
|
free (temp);
|
|
}
|
|
|
|
if (!file && outfilename && filespec_name (outfilename) && *outfilename != '-') {
|
|
char *temp = malloc (strlen (outfilename) + PATH_MAX);
|
|
|
|
strcpy (temp, outfilename);
|
|
strcpy (filespec_name (temp), fn);
|
|
file = wild_fopen (temp, "rb");
|
|
free (temp);
|
|
}
|
|
|
|
if (file) {
|
|
uint32_t bcount;
|
|
|
|
tag_items [i].vsize = (int) DoGetFileSize (file);
|
|
|
|
if (filespec_ext (fn))
|
|
tag_items [i].ext = strdup (filespec_ext (fn));
|
|
|
|
if (tag_items [i].vsize < 1048576 * (allow_huge_tags ? 16 : 1)) {
|
|
new_value = malloc (tag_items [i].vsize + 2);
|
|
memset (new_value, 0, tag_items [i].vsize + 2);
|
|
|
|
if (!DoReadFile (file, new_value, tag_items [i].vsize, &bcount) ||
|
|
bcount != tag_items [i].vsize) {
|
|
free (new_value);
|
|
new_value = NULL;
|
|
}
|
|
}
|
|
|
|
DoCloseHandle (file);
|
|
}
|
|
|
|
if (!new_value) {
|
|
error_line ("error in tag spec: %s !", tag_items [i].value);
|
|
++error_count;
|
|
}
|
|
else {
|
|
free (tag_items [i].value);
|
|
tag_items [i].value = new_value;
|
|
#ifdef _WIN32
|
|
tag_came_from_file = 1;
|
|
#endif
|
|
}
|
|
}
|
|
else if (tag_items [i].binary) {
|
|
error_line ("binary tags must be from files: %s !", tag_items [i].value);
|
|
++error_count;
|
|
}
|
|
|
|
if (tag_items [i].binary) {
|
|
int isize = (int) strlen (tag_items [i].item);
|
|
int esize = tag_items [i].ext ? (int) strlen (tag_items [i].ext) : 0;
|
|
|
|
tag_items [i].value = realloc (tag_items [i].value, isize + esize + 1 + tag_items [i].vsize);
|
|
memmove (tag_items [i].value + isize + esize + 1, tag_items [i].value, tag_items [i].vsize);
|
|
strcpy (tag_items [i].value, tag_items [i].item);
|
|
|
|
if (tag_items [i].ext)
|
|
strcat (tag_items [i].value, tag_items [i].ext);
|
|
|
|
tag_items [i].vsize += isize + esize + 1;
|
|
}
|
|
else if (tag_items [i].vsize) {
|
|
tag_items [i].value = realloc (tag_items [i].value, tag_items [i].vsize * 2 + 1);
|
|
|
|
#ifdef _WIN32
|
|
if (tag_came_from_file && !no_utf8_convert)
|
|
#else
|
|
if (!no_utf8_convert)
|
|
#endif
|
|
TextToUTF8 (tag_items [i].value, (int) tag_items [i].vsize * 2 + 1);
|
|
|
|
// if a UTF8 BOM gets through to here, delete it now (redundant in APEv2 tags)
|
|
|
|
if (tag_items [i].vsize >= 3 && (unsigned char) tag_items [i].value [0] == 0xEF &&
|
|
(unsigned char) tag_items [i].value [1] == 0xBB && (unsigned char) tag_items [i].value [2] == 0xBF) {
|
|
memmove (tag_items [i].value, tag_items [i].value + 3, tag_items [i].vsize -= 3);
|
|
tag_items [i].value [tag_items [i].vsize] = 0;
|
|
}
|
|
|
|
tag_items [i].vsize = (int) strlen (tag_items [i].value);
|
|
}
|
|
|
|
if ((total_tag_size += tag_items [i].vsize) > 1048576 * (allow_huge_tags ? 16 : 1)) {
|
|
error_line ("total APEv2 tag size exceeds %d MB !", allow_huge_tags ? 16 : 1);
|
|
++error_count;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (error_count) {
|
|
fprintf (stderr, "\ntype 'wavpack' for short help or 'wavpack --help' for full help\n");
|
|
fflush (stderr);
|
|
return 1;
|
|
}
|
|
|
|
if (!num_files) {
|
|
printf ("%s", usage);
|
|
return 1;
|
|
}
|
|
|
|
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, "rb");
|
|
char *listbuff = NULL, *cp;
|
|
int listbytes = 0, 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 (1) {
|
|
int bytes_read;
|
|
|
|
listbuff = realloc (listbuff, listbytes + 1024);
|
|
memset (listbuff + listbytes, 0, 1024);
|
|
listbytes += bytes_read = (int) fread (listbuff + listbytes, 1, 1024, list);
|
|
|
|
if (bytes_read < 1024)
|
|
break;
|
|
}
|
|
|
|
#if defined (_WIN32)
|
|
listbuff = realloc (listbuff, listbytes *= 2);
|
|
TextToUTF8 (listbuff, listbytes);
|
|
#endif
|
|
cp = listbuff;
|
|
|
|
while ((c = *cp++)) {
|
|
|
|
while (c == '\n' || c == '\r')
|
|
c = *cp++;
|
|
|
|
if (c) {
|
|
char *fname = malloc (PATH_MAX);
|
|
int ci = 0;
|
|
|
|
do
|
|
fname [ci++] = c;
|
|
while ((c = *cp++) != '\n' && c != '\r' && c && ci < PATH_MAX);
|
|
|
|
fname [ci++] = '\0';
|
|
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;
|
|
}
|
|
|
|
if (!c)
|
|
break;
|
|
}
|
|
|
|
fclose (list);
|
|
free (listbuff);
|
|
free (infilename);
|
|
}
|
|
#if defined (_WIN32)
|
|
else if (filespec_wild (infilename)) {
|
|
wchar_t *winfilename = utf8_to_utf16(infilename);
|
|
struct _wfinddata_t _wfinddata_t;
|
|
intptr_t file;
|
|
int di;
|
|
|
|
for (di = file_index; di < num_files - 1; di++)
|
|
matches [di] = matches [di + 1];
|
|
|
|
file_index--;
|
|
num_files--;
|
|
|
|
if ((file = _wfindfirst (winfilename, &_wfinddata_t)) != (intptr_t) -1) {
|
|
do {
|
|
char *name_utf8;
|
|
|
|
if (!(_wfinddata_t.attrib & _A_SUBDIR) && (name_utf8 = utf16_to_utf8(_wfinddata_t.name))) {
|
|
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 (name_utf8) + 10);
|
|
strcpy (matches [file_index], infilename);
|
|
*filespec_name (matches [file_index]) = '\0';
|
|
strcat (matches [file_index], name_utf8);
|
|
free (name_utf8);
|
|
}
|
|
} while (_wfindnext (file, &_wfinddata_t) == 0);
|
|
|
|
_findclose (file);
|
|
}
|
|
|
|
free (winfilename);
|
|
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] == '@') {
|
|
char listbuff [PATH_MAX * 2], *lp = listbuff;
|
|
FILE *list = fopen (outfilename+1, "rb");
|
|
int c;
|
|
|
|
if (list == NULL) {
|
|
error_line ("file %s not found!", outfilename+1);
|
|
free(outfilename);
|
|
return 1;
|
|
}
|
|
|
|
memset (listbuff, 0, sizeof (listbuff));
|
|
c = (int) fread (listbuff, 1, sizeof (listbuff) - 1, list); // assign c only to suppress warning
|
|
|
|
#if defined (_WIN32)
|
|
TextToUTF8 (listbuff, PATH_MAX * 2);
|
|
#endif
|
|
|
|
while ((c = *lp++) == '\n' || c == '\r');
|
|
|
|
if (c) {
|
|
int ci = 0;
|
|
|
|
do
|
|
outfilename [ci++] = c;
|
|
while ((c = *lp++) != '\n' && c != '\r' && c && 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;
|
|
|
|
// calculate an estimate for the percentage of the time that will be used for the encoding (as opposed
|
|
// to the optional verification step) based on the "extra" mode processing; this is only used for
|
|
// displaying the progress and so is not very critical
|
|
|
|
if (verify_mode) {
|
|
if (config.flags & CONFIG_EXTRA_MODE) {
|
|
if (config.xmode)
|
|
encode_time_percent = 100.0 * (1.0 - (1.0 / ((1 << config.xmode) + 1)));
|
|
else
|
|
encode_time_percent = 66.7;
|
|
}
|
|
else
|
|
encode_time_percent = 50.0;
|
|
}
|
|
else
|
|
encode_time_percent = 100.0;
|
|
|
|
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, ".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 && !quiet_mode) {
|
|
fprintf (stderr, "\n%s:\n", matches [file_index]);
|
|
fflush (stderr);
|
|
}
|
|
|
|
if (filespec_ext (matches [file_index]) && !stricmp (filespec_ext (matches [file_index]), ".wv"))
|
|
result = repack_file (matches [file_index], outfilename, out2filename, &config);
|
|
else
|
|
result = pack_file (matches [file_index], outfilename, out2filename, &config);
|
|
|
|
if (result != WAVPACK_NO_ERROR)
|
|
++error_count;
|
|
|
|
if (result == WAVPACK_HARD_ERROR)
|
|
break;
|
|
|
|
// 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);
|
|
fflush (stderr);
|
|
}
|
|
else if (!quiet_mode) {
|
|
fprintf (stderr, "\n **** %d files successfully processed ****\n", num_files);
|
|
fflush (stderr);
|
|
}
|
|
}
|
|
|
|
free (matches);
|
|
}
|
|
else {
|
|
error_line ("nothing to do!");
|
|
++error_count;
|
|
}
|
|
|
|
if (outfilename)
|
|
free (outfilename);
|
|
|
|
if (set_console_title)
|
|
DoSetConsoleTitle ("WavPack Completed");
|
|
|
|
return error_count ? 1 : 0;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
// On Windows, this "real" main() acts as a shell to our static wavpack_main().
|
|
// Its purpose is to convert the wchar command-line arguments into UTF-8 encoded
|
|
// strings.
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int ret = -1, argc_utf8 = -1;
|
|
char **argv_utf8 = NULL;
|
|
|
|
init_commandline_arguments_utf8(&argc_utf8, &argv_utf8);
|
|
ret = wavpack_main(argc_utf8, argv_utf8);
|
|
free_commandline_arguments_utf8(&argc_utf8, &argv_utf8);
|
|
if (pause_mode) do_pause_mode ();
|
|
return ret;
|
|
}
|
|
|
|
#endif
|
|
|
|
// 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 _wfinddata_t _wfinddata_t;
|
|
char *matchname = NULL;
|
|
wchar_t *wfilename;
|
|
FILE *res = NULL;
|
|
intptr_t file;
|
|
|
|
if (!filespec_wild (filename) || !filespec_name (filename))
|
|
return fopen (filename, mode);
|
|
|
|
wfilename = utf8_to_utf16(filename);
|
|
|
|
if (!wfilename)
|
|
return NULL;
|
|
|
|
if ((file = _wfindfirst (wfilename, &_wfinddata_t)) != (intptr_t) -1) {
|
|
do {
|
|
if (!(_wfinddata_t.attrib & _A_SUBDIR)) {
|
|
char *name_utf8;
|
|
|
|
if (matchname) {
|
|
free (matchname);
|
|
matchname = NULL;
|
|
break;
|
|
}
|
|
else if ((name_utf8 = utf16_to_utf8(_wfinddata_t.name))) {
|
|
matchname = malloc (strlen (filename) + strlen(name_utf8));
|
|
strcpy (matchname, filename);
|
|
strcpy (filespec_name (matchname), name_utf8);
|
|
free (name_utf8);
|
|
}
|
|
}
|
|
} while (_wfindnext (file, &_wfinddata_t) == 0);
|
|
|
|
_findclose (file);
|
|
}
|
|
|
|
if (matchname) {
|
|
res = fopen (matchname, mode);
|
|
free (matchname);
|
|
}
|
|
|
|
free (wfilename);
|
|
return res;
|
|
}
|
|
|
|
#else
|
|
|
|
static FILE *wild_fopen (char *filename, const char *mode)
|
|
{
|
|
char *matchname = NULL;
|
|
struct stat statbuf;
|
|
FILE *res = NULL;
|
|
glob_t globbuf;
|
|
int i;
|
|
|
|
glob (filename, 0, NULL, &globbuf);
|
|
|
|
for (i = 0; i < globbuf.gl_pathc; ++i) {
|
|
if (stat (globbuf.gl_pathv [i], &statbuf) == -1 || S_ISDIR (statbuf.st_mode))
|
|
continue;
|
|
|
|
if (matchname) {
|
|
free (matchname);
|
|
matchname = NULL;
|
|
break;
|
|
}
|
|
else {
|
|
matchname = malloc (strlen (globbuf.gl_pathv [i]) + 10);
|
|
strcpy (matchname, globbuf.gl_pathv [i]);
|
|
}
|
|
}
|
|
|
|
globfree (&globbuf);
|
|
|
|
if (matchname) {
|
|
res = fopen (matchname, mode);
|
|
free (matchname);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
#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.
|
|
|
|
int ImportID3v2 (WavpackContext *wpc, unsigned char *tag_data, int tag_size, char *error, int32_t *bytes_used); // import_id3.c
|
|
|
|
static int pack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config)
|
|
{
|
|
char *outfilename_temp = NULL, *out2filename_temp = NULL, dummy;
|
|
int use_tempfiles = (out2filename != NULL), chunk_alignment = 1;
|
|
int imported_tag_items = 0;
|
|
uint32_t bcount;
|
|
WavpackConfig loc_config = *config;
|
|
unsigned char *new_channel_order = NULL;
|
|
unsigned char md5_digest [16];
|
|
write_id wv_file, wvc_file;
|
|
WavpackContext *wpc;
|
|
double dtime;
|
|
FILE *infile;
|
|
int result;
|
|
|
|
#if defined(__WATCOMC__)
|
|
struct _timeb time1, time2;
|
|
#elif defined(_WIN32)
|
|
struct __timeb64 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
|
|
#if defined(__OS2__)
|
|
setmode (fileno (stdin), O_BINARY);
|
|
#endif
|
|
}
|
|
else if ((infile = fopen (infilename, "rb")) == NULL) {
|
|
error_line ("can't open file %s!", infilename);
|
|
WavpackCloseFile (wpc);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (loc_config.qmode & QMODE_RAW_PCM) {
|
|
int64_t infilesize = DoGetFileSize (infile), total_samples;
|
|
|
|
if (infilesize) {
|
|
int sample_size = loc_config.bytes_per_sample * loc_config.num_channels;
|
|
|
|
infilesize -= raw_pcm_skip_bytes_begin + raw_pcm_skip_bytes_end;
|
|
total_samples = infilesize / sample_size;
|
|
|
|
if (total_samples <= 0) {
|
|
error_line ("no raw PCM data to encode!");
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (infilesize % sample_size)
|
|
error_line ("warning: raw PCM infile length does not divide evenly, %d bytes will be discarded",
|
|
(int)(infilesize % sample_size));
|
|
}
|
|
else {
|
|
if (raw_pcm_skip_bytes_end) {
|
|
error_line ("can't skip trailer in raw PCM read from stdin!");
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
loc_config.qmode |= QMODE_IGNORE_LENGTH;
|
|
total_samples = -1;
|
|
}
|
|
|
|
if (!loc_config.channel_mask && !(loc_config.qmode & QMODE_CHANS_UNASSIGNED)) {
|
|
if (loc_config.num_channels <= 2)
|
|
loc_config.channel_mask = 0x5 - loc_config.num_channels;
|
|
else if (loc_config.num_channels <= 18)
|
|
loc_config.channel_mask = (1 << loc_config.num_channels) - 1;
|
|
else
|
|
loc_config.channel_mask = 0x3ffff;
|
|
}
|
|
|
|
if (!WavpackSetConfiguration64 (wpc, &loc_config, total_samples, NULL)) {
|
|
error_line ("%s", WavpackGetErrorMessage (wpc));
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
// check both output files for overwrite warning required
|
|
|
|
// note that for a file to be considered "overwritable", it must both be openable for reading
|
|
// and have at least 1 readable byte - this prevents us getting stuck on "nul" (Windows)
|
|
|
|
if (*outfilename != '-' && (wv_file.file = fopen (outfilename, "rb")) != NULL) {
|
|
size_t res = fread (&dummy, 1, 1, wv_file.file);
|
|
|
|
DoCloseHandle (wv_file.file);
|
|
|
|
if (res == 1) {
|
|
if (no_overwrite) {
|
|
error_line ("not overwriting %s", FN_FIT (outfilename));
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
use_tempfiles = 1;
|
|
|
|
if (!overwrite_all) {
|
|
fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (outfilename));
|
|
fflush (stderr);
|
|
|
|
if (set_console_title)
|
|
DoSetConsoleTitle ("overwrite?");
|
|
|
|
switch (yna ()) {
|
|
case 'n':
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return WAVPACK_SOFT_ERROR;
|
|
|
|
case 'a':
|
|
overwrite_all = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (out2filename && !overwrite_all && (wvc_file.file = fopen (out2filename, "rb")) != NULL) {
|
|
size_t res = fread (&dummy, 1, 1, wvc_file.file);
|
|
|
|
DoCloseHandle (wvc_file.file);
|
|
|
|
if (res == 1) {
|
|
if (no_overwrite) {
|
|
error_line ("not overwriting %s", FN_FIT (outfilename));
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (out2filename));
|
|
fflush (stderr);
|
|
|
|
if (set_console_title)
|
|
DoSetConsoleTitle ("overwrite?");
|
|
|
|
switch (yna ()) {
|
|
|
|
case 'n':
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
return WAVPACK_SOFT_ERROR;
|
|
|
|
case 'a':
|
|
overwrite_all = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we are using temp files, either because the output filename already exists or we are creating a
|
|
// "correction" file, search for and generate the corresponding names here
|
|
|
|
if (use_tempfiles) {
|
|
FILE *testfile;
|
|
int count = 0;
|
|
|
|
outfilename_temp = malloc (strlen (outfilename) + 16);
|
|
|
|
if (out2filename)
|
|
out2filename_temp = malloc (strlen (outfilename) + 16);
|
|
|
|
while (1) {
|
|
strcpy (outfilename_temp, outfilename);
|
|
|
|
if (filespec_ext (outfilename_temp)) {
|
|
if (count++)
|
|
sprintf (filespec_ext (outfilename_temp), ".tmp%d", count-1);
|
|
else
|
|
strcpy (filespec_ext (outfilename_temp), ".tmp");
|
|
|
|
strcat (outfilename_temp, filespec_ext (outfilename));
|
|
}
|
|
else {
|
|
if (count++)
|
|
sprintf (outfilename_temp + strlen (outfilename_temp), ".tmp%d", count-1);
|
|
else
|
|
strcat (outfilename_temp, ".tmp");
|
|
}
|
|
|
|
testfile = fopen (outfilename_temp, "rb");
|
|
|
|
if (testfile) {
|
|
int res = (int) fread (&dummy, 1, 1, testfile);
|
|
|
|
fclose (testfile);
|
|
|
|
if (res == 1)
|
|
continue;
|
|
}
|
|
|
|
if (out2filename) {
|
|
strcpy (out2filename_temp, outfilename_temp);
|
|
strcat (out2filename_temp, "c");
|
|
|
|
testfile = fopen (out2filename_temp, "rb");
|
|
|
|
if (testfile) {
|
|
int res = (int) fread (&dummy, 1, 1, testfile);
|
|
|
|
fclose (testfile);
|
|
|
|
if (res == 1)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if defined(__WATCOMC__)
|
|
_ftime (&time1);
|
|
#elif defined(_WIN32)
|
|
_ftime64 (&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
|
|
#if defined(__OS2__)
|
|
setmode (fileno (stdout), O_BINARY);
|
|
#endif
|
|
}
|
|
else if ((wv_file.file = fopen (use_tempfiles ? outfilename_temp : outfilename, "w+b")) == NULL) {
|
|
error_line ("can't create file %s!", use_tempfiles ? outfilename_temp : outfilename);
|
|
DoCloseHandle (infile);
|
|
WavpackCloseFile (wpc);
|
|
free (outfilename_temp);
|
|
free (out2filename_temp);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (!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));
|
|
|
|
fflush (stderr);
|
|
}
|
|
|
|
// for now, raw 1-bit PCM is only DSDIFF format
|
|
|
|
if (loc_config.qmode & QMODE_RAW_PCM)
|
|
if (loc_config.qmode & QMODE_DSD_AUDIO)
|
|
WavpackSetFileInformation (wpc, "dff", WP_FORMAT_DFF);
|
|
|
|
// if not in "raw" mode, process RIFF form header and set configuration
|
|
|
|
if (!(loc_config.qmode & QMODE_RAW_PCM)) {
|
|
char fourcc [4];
|
|
int i;
|
|
|
|
if (!DoReadFile (infile, fourcc, sizeof (fourcc), &bcount) || bcount != sizeof (fourcc)) {
|
|
error_line ("can't read file %s!", infilename);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (wpc);
|
|
free (outfilename_temp);
|
|
free (out2filename_temp);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < NUM_FILE_FORMATS; ++i)
|
|
if (!strncmp (fourcc, file_formats [i].fourcc, 4)) {
|
|
|
|
WavpackSetFileInformation (wpc,
|
|
filespec_ext (infilename) ? filespec_ext (infilename) + 1 : file_formats [i].default_extension,
|
|
file_formats [i].id);
|
|
|
|
if (file_formats [i].ParseHeader (infile, infilename, fourcc, wpc, &loc_config)) {
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (wpc);
|
|
free (outfilename_temp);
|
|
free (out2filename_temp);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
chunk_alignment = file_formats [i].chunk_alignment;
|
|
break;
|
|
}
|
|
|
|
if (i == NUM_FILE_FORMATS) {
|
|
error_line ("%s is not a recognized file type!", infilename);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (wpc);
|
|
free (outfilename_temp);
|
|
free (out2filename_temp);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
else if (raw_pcm_skip_bytes_begin) { // if raw pcm mode and bytes to skip, do that here
|
|
int bytes_to_skip = raw_pcm_skip_bytes_begin;
|
|
char dummy [256];
|
|
|
|
while (bytes_to_skip) {
|
|
int requested_bytes = (bytes_to_skip >= sizeof (dummy)) ? sizeof (dummy) : bytes_to_skip;
|
|
|
|
if (DoReadFile (infile, dummy, requested_bytes, &bcount) && bcount == requested_bytes)
|
|
bytes_to_skip -= bcount;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (bytes_to_skip) {
|
|
error_line ("can't read file %s!", infilename);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (wpc);
|
|
free (outfilename_temp);
|
|
free (out2filename_temp);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
// handle case where the CAF header indicated a channel layout that requires reordering
|
|
|
|
if (loc_config.qmode & QMODE_REORDERED_CHANS) {
|
|
int layout = WavpackGetChannelLayout (wpc, NULL), i;
|
|
|
|
if ((layout & 0xff) <= loc_config.num_channels) {
|
|
new_channel_order = malloc (loc_config.num_channels);
|
|
|
|
for (i = 0; i < loc_config.num_channels; ++i)
|
|
new_channel_order [i] = i;
|
|
|
|
WavpackGetChannelLayout (wpc, new_channel_order);
|
|
}
|
|
}
|
|
|
|
// handle case where the user specified channel configuration on the command-line
|
|
|
|
if (num_channels_order || (loc_config.qmode & QMODE_CHANS_UNASSIGNED)) {
|
|
int i, j;
|
|
|
|
if (loc_config.num_channels < num_channels_order ||
|
|
(loc_config.num_channels > num_channels_order && !(loc_config.qmode & QMODE_CHANS_UNASSIGNED))) {
|
|
error_line ("file does not have %d channel(s)!", num_channels_order);
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (wpc);
|
|
free (outfilename_temp);
|
|
free (out2filename_temp);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (num_channels_order) {
|
|
new_channel_order = malloc (loc_config.num_channels);
|
|
|
|
for (i = 0; i < loc_config.num_channels; ++i)
|
|
new_channel_order [i] = i;
|
|
|
|
memcpy (new_channel_order, channel_order, num_channels_order);
|
|
|
|
for (i = 0; i < num_channels_order;) {
|
|
for (j = 0; j < num_channels_order; ++j)
|
|
if (new_channel_order [j] == i) {
|
|
i++;
|
|
break;
|
|
}
|
|
|
|
if (j == num_channels_order)
|
|
for (j = 0; j < num_channels_order; ++j)
|
|
if (new_channel_order [j] > i)
|
|
new_channel_order [j]--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we are creating a "correction" file, open it now for writing
|
|
|
|
if (out2filename) {
|
|
if ((wvc_file.file = fopen (use_tempfiles ? out2filename_temp : out2filename, "w+b")) == NULL) {
|
|
error_line ("can't create correction file!");
|
|
DoCloseHandle (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (wpc);
|
|
free (outfilename_temp);
|
|
free (out2filename_temp);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
// pack the audio portion of the file now; calculate md5 if we're writing it to the file or verify mode is active
|
|
|
|
if (loc_config.qmode & QMODE_DSD_AUDIO)
|
|
result = pack_dsd_audio (wpc, infile, loc_config.qmode, new_channel_order, ((loc_config.flags & CONFIG_MD5_CHECKSUM) || verify_mode) ? md5_digest : NULL);
|
|
else
|
|
result = pack_audio (wpc, infile, loc_config.qmode, new_channel_order, ((loc_config.flags & CONFIG_MD5_CHECKSUM) || verify_mode) ? md5_digest : NULL);
|
|
|
|
if (new_channel_order)
|
|
free (new_channel_order);
|
|
|
|
// write the md5 sum if the user asked for it to be included
|
|
|
|
if (result == WAVPACK_NO_ERROR && (loc_config.flags & CONFIG_MD5_CHECKSUM))
|
|
WavpackStoreMD5Sum (wpc, md5_digest);
|
|
|
|
// if everything went well, and we're not ignoring length or encoding raw
|
|
// pcm, read past any required data chunk padding and then try to read anything
|
|
// else that might be appended to the audio data and write that to the WavPack
|
|
// metadata as "wrapper"
|
|
|
|
if (result == WAVPACK_NO_ERROR && !(loc_config.qmode & (QMODE_IGNORE_LENGTH | QMODE_RAW_PCM))) {
|
|
int wrapper_size = 0, buffer_size;
|
|
unsigned char *buffer;
|
|
|
|
// if this file format has chunk alignment padding, read past that here
|
|
|
|
if (chunk_alignment != 1) {
|
|
int64_t data_chunk_bytes = WavpackGetNumSamples64 (wpc) * WavpackGetNumChannels (wpc) * WavpackGetBytesPerSample (wpc);
|
|
int bytes_over = (int)(data_chunk_bytes % chunk_alignment);
|
|
int padding_bytes = bytes_over ? chunk_alignment - bytes_over : 0;
|
|
unsigned char pad_byte;
|
|
|
|
while (padding_bytes--) {
|
|
if (!DoReadFile (infile, &pad_byte, 1, &bcount) || bcount != 1)
|
|
error_line ("warning: input file missing required padding byte!");
|
|
else if (pad_byte)
|
|
error_line ("warning: input file has non-zero padding byte!");
|
|
}
|
|
}
|
|
|
|
// now read everything remaining in the file into a new buffer
|
|
|
|
buffer = malloc (buffer_size = 65536);
|
|
|
|
while (DoReadFile (infile, buffer + wrapper_size, buffer_size - wrapper_size, &bcount) && bcount)
|
|
if ((wrapper_size += bcount) == buffer_size)
|
|
buffer = realloc (buffer, buffer_size += 65536);
|
|
|
|
// if we got something and are storing wrapper, write it to the outfile file
|
|
|
|
if (wrapper_size && !(loc_config.qmode & QMODE_NO_STORE_WRAPPER) && !WavpackAddWrapper (wpc, buffer, wrapper_size)) {
|
|
error_line ("%s", WavpackGetErrorMessage (wpc));
|
|
result = WAVPACK_HARD_ERROR;
|
|
}
|
|
else if (wrapper_size && debug_logging_mode)
|
|
error_line ("%d bytes of trailing data stored in file", wrapper_size);
|
|
|
|
// if we're supposed to try to import ID3 tags, check for and do that now
|
|
// (but only error on a bad tag, not just a missing one or one with no applicable items)
|
|
|
|
if (result == WAVPACK_NO_ERROR && import_id3 && wrapper_size > 10) {
|
|
int32_t bytes_used, id3_res;
|
|
char error [80];
|
|
|
|
// first we do a "dry run" pass through the ID3 tag, and only if that passes do we try to write the tag items
|
|
|
|
id3_res = ImportID3v2 (NULL, buffer, wrapper_size, error, &bytes_used);
|
|
|
|
if (!allow_huge_tags && bytes_used > 1048576) {
|
|
error_line ("imported tag items exceed 1 MB, use --allow-huge-tags to override");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
else if (bytes_used > 1048576 * 16) {
|
|
error_line ("imported tag items exceed 16 MB");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
else {
|
|
if (id3_res > 0)
|
|
id3_res = ImportID3v2 (wpc, buffer, wrapper_size, error, NULL);
|
|
|
|
if (id3_res < 0) {
|
|
error_line ("ID3v2 import: %s", error);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
else if (id3_res > 0)
|
|
imported_tag_items = id3_res;
|
|
}
|
|
}
|
|
|
|
free (buffer);
|
|
}
|
|
|
|
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 == WAVPACK_NO_ERROR && !WavpackFlushSamples (wpc)) {
|
|
error_line ("%s", WavpackGetErrorMessage (wpc));
|
|
result = WAVPACK_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 == WAVPACK_NO_ERROR && (num_tag_items || imported_tag_items)) {
|
|
int i, res = TRUE;
|
|
|
|
for (i = 0; i < num_tag_items && res; ++i)
|
|
if (tag_items [i].vsize) {
|
|
if (tag_items [i].binary)
|
|
res = WavpackAppendBinaryTagItem (wpc, tag_items [i].item, tag_items [i].value, tag_items [i].vsize);
|
|
else
|
|
res = WavpackAppendTagItem (wpc, tag_items [i].item, tag_items [i].value, tag_items [i].vsize);
|
|
}
|
|
|
|
if (!res || !WavpackWriteTag (wpc)) {
|
|
error_line ("%s", WavpackGetErrorMessage (wpc));
|
|
result = WAVPACK_HARD_ERROR;
|
|
}
|
|
}
|
|
|
|
// At this point we're done writing to the output files. If the number of samples converted
|
|
// did not match the number we were expecting, and the "ignore length" option was NOT specified,
|
|
// then that's an error.
|
|
|
|
if (result == WAVPACK_NO_ERROR && WavpackGetNumSamples64 (wpc) != WavpackGetSampleIndex64 (wpc) &&
|
|
!(loc_config.qmode & QMODE_IGNORE_LENGTH)) {
|
|
error_line ("couldn't read all samples; specify '-i' to ignore length in header");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
// If we're ignoring the length in the header, or we were not able to determine the length of
|
|
// a "raw" file in advance, then we'll need to back up and read the first frame written and
|
|
// update the length stored there and potentially fix the header stored from the source file.
|
|
|
|
if (result == WAVPACK_NO_ERROR &&
|
|
((loc_config.qmode & QMODE_IGNORE_LENGTH) || WavpackGetNumSamples64 (wpc) == -1)) {
|
|
char *block_buff = malloc (wv_file.first_block_size);
|
|
int update_error = 0;
|
|
|
|
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)) {
|
|
|
|
// If we got the RIFF header from the source file, we try to update it here if it's a simple
|
|
// header (not RF64) and the audio data length is appropriate. Note that this means we're no
|
|
// longer strictly lossless, but the user essentially told us the length in the header was
|
|
// wrong, so we're fixing it.
|
|
|
|
if (!(loc_config.qmode & (QMODE_NO_STORE_WRAPPER | QMODE_RAW_PCM)) && WavpackGetWrapperLocation (block_buff, NULL)) {
|
|
uint32_t wrapper_size;
|
|
unsigned char *wrapper_location = WavpackGetWrapperLocation (block_buff, &wrapper_size);
|
|
int64_t data_size = WavpackGetSampleIndex64 (wpc) * WavpackGetNumChannels (wpc) * WavpackGetBytesPerSample (wpc);
|
|
ChunkHeader chunk_header;
|
|
|
|
memcpy (&chunk_header, wrapper_location, sizeof (ChunkHeader));
|
|
|
|
if (data_size <= 0xff000000 && !strncmp (chunk_header.ckID, "RIFF", 4)) {
|
|
chunk_header.ckSize = (uint32_t) (wrapper_size + data_size - 8);
|
|
WavpackNativeToLittleEndian (&chunk_header, ChunkHeaderFormat);
|
|
memcpy (wrapper_location, &chunk_header, sizeof (ChunkHeader));
|
|
memcpy (&chunk_header, wrapper_location + wrapper_size - sizeof (ChunkHeader), sizeof (ChunkHeader));
|
|
|
|
if (!strncmp (chunk_header.ckID, "data", 4)) {
|
|
chunk_header.ckSize = (uint32_t) data_size;
|
|
WavpackNativeToLittleEndian (&chunk_header, ChunkHeaderFormat);
|
|
memcpy (wrapper_location + wrapper_size - sizeof (ChunkHeader), &chunk_header, sizeof (ChunkHeader));
|
|
}
|
|
}
|
|
}
|
|
|
|
// this call will take care of the initial WavPack header and any RIFF header the library made
|
|
// (and also make sure the block checksum is correct)
|
|
|
|
WavpackUpdateNumSamples (wpc, block_buff);
|
|
|
|
if (DoSetFilePositionAbsolute (wv_file.file, 0) ||
|
|
!DoWriteFile (wv_file.file, block_buff, wv_file.first_block_size, &bcount) ||
|
|
bcount != wv_file.first_block_size)
|
|
update_error = 1;
|
|
}
|
|
else
|
|
update_error = 1;
|
|
|
|
if (block_buff)
|
|
free (block_buff);
|
|
|
|
if (!update_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)
|
|
update_error = 1;
|
|
}
|
|
else
|
|
update_error = 1;
|
|
|
|
if (block_buff)
|
|
free (block_buff);
|
|
}
|
|
|
|
// If we're writing to stdout then we really shouldn't be too surprised that we can't rewrite the
|
|
// header, and we can't delete the output file either, so let them off with a warning this time.
|
|
|
|
if (update_error) {
|
|
if (*outfilename != '-') {
|
|
error_line ("couldn't update WavPack header with actual length!!");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
else
|
|
error_line ("warning: couldn't update WavPack header with actual length!");
|
|
}
|
|
}
|
|
|
|
// at this point we're completely 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 == WAVPACK_NO_ERROR)
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (out2filename && !DoCloseHandle (wvc_file.file)) {
|
|
error_line ("can't close correction file!");
|
|
|
|
if (result == WAVPACK_NO_ERROR)
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
// if there have been no errors up to now, and verify mode is enabled, do that now; only pass in the md5 if this
|
|
// was a lossless operation (either explicitly or because a high lossy bitrate resulted in lossless)
|
|
|
|
if (result == WAVPACK_NO_ERROR && verify_mode)
|
|
result = verify_audio (use_tempfiles ? outfilename_temp : outfilename, !WavpackLossyBlocks (wpc) ? md5_digest : NULL);
|
|
|
|
// if there were any errors, delete the output files, close the context, and return the error
|
|
|
|
if (result != WAVPACK_NO_ERROR) {
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
|
|
if (out2filename)
|
|
DoDeleteFile (use_tempfiles ? out2filename_temp : out2filename);
|
|
|
|
WavpackCloseFile (wpc);
|
|
free (outfilename_temp);
|
|
free (out2filename_temp);
|
|
return result;
|
|
}
|
|
|
|
// if we were writing to a temp file because the target file already existed,
|
|
// do the rename / overwrite now (and if that fails, return the error)
|
|
|
|
if (use_tempfiles) {
|
|
#if defined(_WIN32)
|
|
FILE *temp;
|
|
|
|
if (remove (outfilename) && (temp = fopen (outfilename, "rb"))) {
|
|
error_line ("can not remove file %s, result saved in %s!", outfilename, outfilename_temp);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
fclose (temp);
|
|
}
|
|
else
|
|
#endif
|
|
if (rename (outfilename_temp, outfilename)) {
|
|
error_line ("can not rename temp file %s to %s!", outfilename_temp, outfilename);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (out2filename) {
|
|
#if defined(_WIN32)
|
|
FILE *temp;
|
|
|
|
if (remove (out2filename) && (temp = fopen (out2filename, "rb"))) {
|
|
error_line ("can not remove file %s, result saved in %s!", out2filename, out2filename_temp);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
fclose (temp);
|
|
}
|
|
else
|
|
#endif
|
|
if (rename (out2filename_temp, out2filename)) {
|
|
error_line ("can not rename temp file %s to %s!", out2filename_temp, out2filename);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
free (outfilename_temp);
|
|
if (out2filename) free (out2filename_temp);
|
|
|
|
if (result != WAVPACK_NO_ERROR) {
|
|
WavpackCloseFile (wpc);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (result == WAVPACK_NO_ERROR && copy_time)
|
|
if (!copy_timestamp (infilename, outfilename) ||
|
|
(out2filename && !copy_timestamp (infilename, out2filename)))
|
|
error_line ("failure copying time stamp!");
|
|
|
|
// delete source file if that option is enabled
|
|
|
|
if (result == WAVPACK_NO_ERROR && delete_source) {
|
|
int res = DoDeleteFile (infilename);
|
|
|
|
if (!quiet_mode || !res)
|
|
error_line ("%s source file %s", res ?
|
|
"deleted" : "can't delete", infilename);
|
|
}
|
|
|
|
// compute and display the time consumed along with some other details of
|
|
// the packing operation, and then return WAVPACK_NO_ERROR
|
|
|
|
#if defined(__WATCOMC__)
|
|
_ftime (&time2);
|
|
dtime = time2.time + time2.millitm / 1000.0;
|
|
dtime -= time1.time + time1.millitm / 1000.0;
|
|
#elif defined(_WIN32)
|
|
_ftime64 (&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) && WavpackGetEncodedNoise (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 = WavpackGetEncodedNoise (wpc, &peak);
|
|
|
|
error_line ("ave noise = %.2f dB, peak noise = %.2f dB",
|
|
log10 (sum / WavpackGetNumSamples64 (wpc) / full_scale_rms) * 10,
|
|
log10 (peak / full_scale_rms) * 10);
|
|
}
|
|
|
|
if (!quiet_mode) {
|
|
char *file, *fext, *oper, *cmode, cratio [16] = "";
|
|
|
|
if (imported_tag_items)
|
|
error_line ("successfully imported %d items from ID3v2 tag", imported_tag_items);
|
|
|
|
if (loc_config.flags & CONFIG_MD5_CHECKSUM) {
|
|
char md5_string [] = "original md5 signature: 00000000000000000000000000000000";
|
|
int i;
|
|
|
|
for (i = 0; i < 16; ++i)
|
|
sprintf (md5_string + 24 + (i * 2), "%02x", md5_digest [i]);
|
|
|
|
error_line (md5_string);
|
|
}
|
|
|
|
if (outfilename && *outfilename != '-') {
|
|
file = FN_FIT (outfilename);
|
|
fext = wvc_file.bytes_written ? " (+.wvc)" : "";
|
|
oper = verify_mode ? "created (and verified)" : "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 WAVPACK_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.
|
|
|
|
static void reorder_channels (void *data, unsigned char *new_order, int num_chans,
|
|
int num_samples, int bytes_per_sample);
|
|
|
|
static void load_samples (int32_t *dst, void *src, int qmode, int bps, int count);
|
|
static void *store_samples (void *dst, int32_t *src, int qmode, int bps, int count);
|
|
static void unreorder_channels (int32_t *data, unsigned char *order, int num_chans, int num_samples);
|
|
|
|
#define INPUT_SAMPLES 65536
|
|
|
|
static int pack_audio (WavpackContext *wpc, FILE *infile, int qmode, unsigned char *new_order, unsigned char *md5_digest_source)
|
|
{
|
|
int64_t samples_remaining, input_samples = INPUT_SAMPLES;
|
|
double progress = -1.0;
|
|
int bytes_per_sample;
|
|
int32_t *sample_buffer;
|
|
unsigned char *input_buffer;
|
|
MD5_CTX md5_context;
|
|
int32_t padding_error_bit_mask = 0, quantize_bit_mask = 0;
|
|
double fquantize_scale = 1.0, fquantize_iscale = 1.0;
|
|
|
|
// don't use an absurd amount of memory just because we have an absurd number of channels
|
|
|
|
while (input_samples * sizeof (int32_t) * WavpackGetNumChannels (wpc) > 2048*1024)
|
|
input_samples >>= 1;
|
|
|
|
if (md5_digest_source)
|
|
MD5_Init (&md5_context);
|
|
|
|
WavpackPackInit (wpc);
|
|
bytes_per_sample = WavpackGetBytesPerSample (wpc) * WavpackGetNumChannels (wpc);
|
|
input_buffer = malloc ((uint32_t) input_samples * bytes_per_sample);
|
|
sample_buffer = malloc ((uint32_t) input_samples * sizeof (int32_t) * WavpackGetNumChannels (wpc));
|
|
samples_remaining = WavpackGetNumSamples64 (wpc);
|
|
|
|
if (quantize_bits && quantize_bits < WavpackGetBytesPerSample (wpc) * 8) {
|
|
quantize_bit_mask = ~((1<<(WavpackGetBytesPerSample (wpc)*8-quantize_bits))-1);
|
|
if (MODE_FLOAT == (WavpackGetMode(wpc) & MODE_FLOAT)) {
|
|
int float_norm_exp = WavpackGetFloatNormExp (wpc);
|
|
fquantize_scale = exp2 (quantize_bits + 126 - float_norm_exp);
|
|
fquantize_iscale = exp2 (float_norm_exp - 126 - quantize_bits);
|
|
}
|
|
}
|
|
|
|
if (WavpackGetBitsPerSample (wpc) % 8)
|
|
padding_error_bit_mask = (1 << (8 - (WavpackGetBitsPerSample (wpc) % 8))) - 1;
|
|
|
|
while (1) {
|
|
uint32_t bytes_to_read, bytes_read = 0;
|
|
int32_t sample_count;
|
|
|
|
if ((qmode & QMODE_IGNORE_LENGTH) || samples_remaining > input_samples)
|
|
bytes_to_read = (uint32_t) input_samples * bytes_per_sample;
|
|
else
|
|
bytes_to_read = (uint32_t) samples_remaining * bytes_per_sample;
|
|
|
|
samples_remaining -= bytes_to_read / bytes_per_sample;
|
|
DoReadFile (infile, input_buffer, bytes_to_read, &bytes_read);
|
|
sample_count = bytes_read / bytes_per_sample;
|
|
|
|
// if we have reordering to do because the user used the --channel-order option to define
|
|
// an order that does not match the Microsoft order, then we do that BEFORE the MD5 because
|
|
// this reordering is permanent (i.e., we will not unreorder on decode) and we want the
|
|
// MD5 to match the new order
|
|
|
|
if (new_order && !(qmode & QMODE_REORDERED_CHANS))
|
|
reorder_channels (input_buffer, new_order, WavpackGetNumChannels (wpc),
|
|
sample_count, WavpackGetBytesPerSample (wpc));
|
|
|
|
if (md5_digest_source && quantize_bit_mask == 0)
|
|
MD5_Update (&md5_context, input_buffer, sample_count * bytes_per_sample);
|
|
|
|
// if we have reordering to do because this is a CAF channel layout that is not in Microsoft
|
|
// order, then we do the reordering AFTER the MD5 because we will be unreordering them at
|
|
// decode time, and so we want the MD5 to match the original order
|
|
|
|
if (new_order && (qmode & QMODE_REORDERED_CHANS))
|
|
reorder_channels (input_buffer, new_order, WavpackGetNumChannels (wpc),
|
|
sample_count, WavpackGetBytesPerSample (wpc));
|
|
|
|
if (!sample_count)
|
|
break;
|
|
|
|
if (sample_count) {
|
|
int bps = WavpackGetBytesPerSample (wpc);
|
|
|
|
load_samples (sample_buffer, input_buffer, qmode, bps, sample_count * WavpackGetNumChannels (wpc));
|
|
|
|
if (quantize_bit_mask) {
|
|
unsigned int x,l = sample_count * WavpackGetNumChannels (wpc);
|
|
if (0 == (WavpackGetMode(wpc) & MODE_FLOAT)) {
|
|
if (quantize_round) {
|
|
int32_t offset = (quantize_bit_mask >> 1) ^ quantize_bit_mask;
|
|
int shift = 32 - WavpackGetBytesPerSample (wpc) * 8;
|
|
|
|
for (x = 0; x < l; x ++)
|
|
if (sample_buffer[x] < 0 || ((sample_buffer[x] + offset) << shift) > 0)
|
|
sample_buffer[x] += offset;
|
|
}
|
|
|
|
for (x = 0; x < l; x ++) sample_buffer[x] &= quantize_bit_mask;
|
|
}
|
|
else {
|
|
for (x = 0; x < l; x ++) {
|
|
const float f = *(float *)&sample_buffer[x];
|
|
*(float *)&sample_buffer[x] = (float) (floor(f * fquantize_scale + 0.5) * fquantize_iscale);
|
|
}
|
|
}
|
|
|
|
if (md5_digest_source) {
|
|
store_samples (input_buffer, sample_buffer, qmode, bps, sample_count * WavpackGetNumChannels (wpc));
|
|
MD5_Update (&md5_context, input_buffer, WavpackGetBytesPerSample (wpc) * l);
|
|
}
|
|
}
|
|
|
|
if (padding_error_bit_mask) {
|
|
unsigned int x,l = sample_count * WavpackGetNumChannels (wpc);
|
|
|
|
for (x = 0; x < l; x ++)
|
|
if (sample_buffer[x] & padding_error_bit_mask) {
|
|
int bits = WavpackGetBitsPerSample (wpc);
|
|
error_line ("\"%d-bit\" file has non-zero PCM padding bits!!", bits);
|
|
error_line ("use --force-even-byte-depth to encode as %d-bit", (bits + 7) / 8 * 8);
|
|
if (bits >= 4)
|
|
error_line ("or --pre-quantize=%d to zero those bits before encoding", bits);
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!WavpackPackSamples (wpc, sample_buffer, sample_count)) {
|
|
error_line ("%s", WavpackGetErrorMessage (wpc));
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
return WAVPACK_HARD_ERROR;
|
|
}
|
|
|
|
if (check_break ()) {
|
|
#if defined(_WIN32)
|
|
fprintf (stderr, "^C\n");
|
|
#else
|
|
fprintf (stderr, "\n");
|
|
#endif
|
|
fflush (stderr);
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (WavpackGetProgress (wpc) != -1.0 &&
|
|
progress != floor (WavpackGetProgress (wpc) * encode_time_percent + 0.5)) {
|
|
int nobs = progress == -1.0;
|
|
|
|
progress = floor (WavpackGetProgress (wpc) * encode_time_percent + 0.5);
|
|
display_progress (progress / 100.0);
|
|
|
|
if (!quiet_mode) {
|
|
fprintf (stderr, "%s%3d%% done...",
|
|
nobs ? " " : "\b\b\b\b\b\b\b\b\b\b\b\b", (int) progress);
|
|
fflush (stderr);
|
|
}
|
|
}
|
|
}
|
|
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
|
|
if (!WavpackFlushSamples (wpc)) {
|
|
error_line ("%s", WavpackGetErrorMessage (wpc));
|
|
return WAVPACK_HARD_ERROR;
|
|
}
|
|
|
|
if (md5_digest_source)
|
|
MD5_Final (md5_digest_source, &md5_context);
|
|
|
|
return WAVPACK_NO_ERROR;
|
|
}
|
|
|
|
static const unsigned char bit_reverse_table [] = {
|
|
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
|
|
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
|
|
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
|
|
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
|
|
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
|
|
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
|
|
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
|
|
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
|
|
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
|
|
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
|
|
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
|
|
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
|
|
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
|
|
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
|
|
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
|
|
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
|
|
};
|
|
|
|
#define DSD_BLOCKSIZE 4096
|
|
|
|
static int pack_dsd_audio (WavpackContext *wpc, FILE *infile, int qmode, unsigned char *new_order, unsigned char *md5_digest_source)
|
|
{
|
|
int64_t samples_remaining;
|
|
double progress = -1.0;
|
|
int num_channels;
|
|
int32_t *sample_buffer;
|
|
unsigned char *input_buffer;
|
|
MD5_CTX md5_context;
|
|
|
|
if (md5_digest_source)
|
|
MD5_Init (&md5_context);
|
|
|
|
WavpackPackInit (wpc);
|
|
num_channels = WavpackGetNumChannels (wpc);
|
|
input_buffer = malloc (DSD_BLOCKSIZE * num_channels);
|
|
sample_buffer = malloc (DSD_BLOCKSIZE * sizeof (int32_t) * num_channels);
|
|
samples_remaining = WavpackGetNumSamples64 (wpc);
|
|
|
|
while (samples_remaining) {
|
|
uint32_t bytes_to_read, bytes_read = 0;
|
|
int32_t sample_count;
|
|
|
|
if ((qmode & (QMODE_DSD_IN_BLOCKS | QMODE_IGNORE_LENGTH)) || samples_remaining > DSD_BLOCKSIZE)
|
|
bytes_to_read = DSD_BLOCKSIZE * num_channels;
|
|
else
|
|
bytes_to_read = (uint32_t) samples_remaining * num_channels;
|
|
|
|
DoReadFile (infile, input_buffer, bytes_to_read, &bytes_read);
|
|
|
|
if (qmode & QMODE_DSD_IN_BLOCKS) {
|
|
if (bytes_read != bytes_to_read) {
|
|
error_line ("incomplete DSD block!");
|
|
samples_remaining = sample_count = 0;
|
|
}
|
|
else if (samples_remaining < DSD_BLOCKSIZE)
|
|
sample_count = (int32_t) samples_remaining;
|
|
else
|
|
sample_count = DSD_BLOCKSIZE;
|
|
}
|
|
else
|
|
sample_count = bytes_read / num_channels;
|
|
|
|
samples_remaining -= sample_count;
|
|
|
|
// if we have reordering to do because the user used the --channel-order option to define
|
|
// an order that does not match the Microsoft order, then we do that BEFORE the MD5 because
|
|
// this reordering is permanent (i.e., we will not unreorder on decode) and we want the
|
|
// MD5 to match the new order
|
|
|
|
if (new_order && !(qmode & QMODE_REORDERED_CHANS)) {
|
|
if (qmode & QMODE_DSD_IN_BLOCKS)
|
|
reorder_channels (input_buffer, new_order, num_channels, 1, DSD_BLOCKSIZE);
|
|
else
|
|
reorder_channels (input_buffer, new_order, num_channels, sample_count, 1);
|
|
}
|
|
|
|
if (md5_digest_source)
|
|
MD5_Update (&md5_context, input_buffer, bytes_read);
|
|
|
|
if (!sample_count)
|
|
break;
|
|
|
|
if (sample_count) {
|
|
if (qmode & QMODE_DSD_IN_BLOCKS) {
|
|
int32_t sindex, *sptr = sample_buffer, non_null = 0;
|
|
|
|
for (sindex = 0; sindex < DSD_BLOCKSIZE; ++sindex) {
|
|
unsigned char *srcp = input_buffer + sindex;
|
|
int cc;
|
|
|
|
for (cc = num_channels; cc--; srcp += DSD_BLOCKSIZE)
|
|
if (sindex < sample_count)
|
|
*sptr++ = (qmode & QMODE_DSD_LSB_FIRST) ? bit_reverse_table [*srcp] : *srcp;
|
|
else if (*srcp)
|
|
non_null++;
|
|
}
|
|
|
|
if (non_null)
|
|
error_line ("blocks not padded with NULLs, MD5 will not match!");
|
|
}
|
|
else {
|
|
int32_t scount = sample_count * num_channels, *sptr = sample_buffer;
|
|
unsigned char *iptr = input_buffer;
|
|
|
|
while (scount--)
|
|
*sptr++ = *iptr++;
|
|
}
|
|
}
|
|
|
|
if (!WavpackPackSamples (wpc, sample_buffer, sample_count)) {
|
|
error_line ("%s", WavpackGetErrorMessage (wpc));
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
return WAVPACK_HARD_ERROR;
|
|
}
|
|
|
|
if (check_break ()) {
|
|
#if defined(_WIN32)
|
|
fprintf (stderr, "^C\n");
|
|
#else
|
|
fprintf (stderr, "\n");
|
|
#endif
|
|
fflush (stderr);
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (WavpackGetProgress (wpc) != -1.0 &&
|
|
progress != floor (WavpackGetProgress (wpc) * encode_time_percent + 0.5)) {
|
|
int nobs = progress == -1.0;
|
|
|
|
progress = floor (WavpackGetProgress (wpc) * encode_time_percent + 0.5);
|
|
display_progress (progress / 100.0);
|
|
|
|
if (!quiet_mode) {
|
|
fprintf (stderr, "%s%3d%% done...",
|
|
nobs ? " " : "\b\b\b\b\b\b\b\b\b\b\b\b", (int) progress);
|
|
fflush (stderr);
|
|
}
|
|
}
|
|
}
|
|
|
|
free (sample_buffer);
|
|
free (input_buffer);
|
|
|
|
if (!WavpackFlushSamples (wpc)) {
|
|
error_line ("%s", WavpackGetErrorMessage (wpc));
|
|
return WAVPACK_HARD_ERROR;
|
|
}
|
|
|
|
if (md5_digest_source)
|
|
MD5_Final (md5_digest_source, &md5_context);
|
|
|
|
return WAVPACK_NO_ERROR;
|
|
}
|
|
|
|
// This function transcodes a single WavPack file "infilename" and stores the resulting
|
|
// WavPack file 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. Note that lossy to lossless transcoding
|
|
// is not allowed (no technical reason, it's just dumb, and could result in files that
|
|
// fail their MD5 verification test).
|
|
|
|
static int repack_file (char *infilename, char *outfilename, char *out2filename, const WavpackConfig *config)
|
|
{
|
|
int output_lossless = !(config->flags & CONFIG_HYBRID_FLAG) || (config->flags & CONFIG_CREATE_WVC);
|
|
int flags = OPEN_WVC | OPEN_TAGS | OPEN_DSD_NATIVE | OPEN_ALT_TYPES, imported_tag_items = 0;
|
|
char *outfilename_temp = NULL, *out2filename_temp = NULL;
|
|
int use_tempfiles = (out2filename != NULL), input_mode;
|
|
unsigned char md5_verify [16], md5_display [16];
|
|
WavpackConfig loc_config = *config;
|
|
WavpackContext *infile, *outfile;
|
|
write_id wv_file, wvc_file;
|
|
int64_t total_samples = 0;
|
|
unsigned char *chan_ids;
|
|
char error [80];
|
|
double dtime;
|
|
int result;
|
|
|
|
#if defined(__WATCOMC__)
|
|
struct _timeb time1, time2;
|
|
#elif defined(_WIN32)
|
|
struct __timeb64 time1, time2;
|
|
#else
|
|
struct timeval time1, time2;
|
|
struct timezone timez;
|
|
#endif
|
|
|
|
if (!(loc_config.qmode & QMODE_NO_STORE_WRAPPER) || import_id3)
|
|
flags |= OPEN_WRAPPER;
|
|
|
|
#if defined(_WIN32)
|
|
flags |= OPEN_FILE_UTF8;
|
|
#endif
|
|
|
|
// use library to open input WavPack file
|
|
|
|
infile = WavpackOpenFileInput (infilename, error, flags, 0);
|
|
|
|
if (!infile) {
|
|
error_line (error);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
input_mode = WavpackGetMode (infile);
|
|
|
|
if (!(input_mode & MODE_LOSSLESS) && output_lossless) {
|
|
error_line ("can't transcode lossy file %s to lossless...not allowed!", infilename);
|
|
WavpackCloseFile (infile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
total_samples = WavpackGetNumSamples64 (infile);
|
|
|
|
if (total_samples == -1) {
|
|
error_line ("can't transcode file %s of unknown length!", infilename);
|
|
WavpackCloseFile (infile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
// open an output context
|
|
|
|
CLEAR (wv_file);
|
|
CLEAR (wvc_file);
|
|
outfile = WavpackOpenFileOutput (write_block, &wv_file, out2filename ? &wvc_file : NULL);
|
|
|
|
// check both output files for overwrite warning required
|
|
|
|
if (*outfilename != '-' && (wv_file.file = fopen (outfilename, "rb")) != NULL) {
|
|
DoCloseHandle (wv_file.file);
|
|
use_tempfiles = 1;
|
|
|
|
if (no_overwrite) {
|
|
error_line ("not overwriting %s", FN_FIT (outfilename));
|
|
WavpackCloseFile (infile);
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (!overwrite_all) {
|
|
if (output_lossless)
|
|
fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (outfilename));
|
|
else
|
|
fprintf (stderr, "overwrite %s with lossy transcode (yes/no/all)? ", FN_FIT (outfilename));
|
|
|
|
fflush (stderr);
|
|
|
|
if (set_console_title)
|
|
DoSetConsoleTitle ("overwrite?");
|
|
|
|
switch (yna ()) {
|
|
case 'n':
|
|
WavpackCloseFile (infile);
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
|
|
case 'a':
|
|
overwrite_all = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (out2filename && !overwrite_all && (wvc_file.file = fopen (out2filename, "rb")) != NULL) {
|
|
DoCloseHandle (wvc_file.file);
|
|
|
|
if (no_overwrite) {
|
|
error_line ("not overwriting %s", FN_FIT (outfilename));
|
|
WavpackCloseFile (infile);
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
fprintf (stderr, "overwrite %s (yes/no/all)? ", FN_FIT (out2filename));
|
|
fflush (stderr);
|
|
|
|
if (set_console_title)
|
|
DoSetConsoleTitle ("overwrite?");
|
|
|
|
switch (yna ()) {
|
|
|
|
case 'n':
|
|
WavpackCloseFile (infile);
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
|
|
case 'a':
|
|
overwrite_all = 1;
|
|
}
|
|
}
|
|
|
|
// if we are using temp files, either because the output filename already exists or we are creating a
|
|
// "correction" file, search for and generate the corresponding names here
|
|
|
|
if (use_tempfiles) {
|
|
FILE *testfile;
|
|
int count = 0;
|
|
|
|
outfilename_temp = malloc (strlen (outfilename) + 16);
|
|
|
|
if (out2filename)
|
|
out2filename_temp = malloc (strlen (outfilename) + 16);
|
|
|
|
while (1) {
|
|
strcpy (outfilename_temp, outfilename);
|
|
|
|
if (filespec_ext (outfilename_temp)) {
|
|
if (count++)
|
|
sprintf (filespec_ext (outfilename_temp), ".tmp%d", count-1);
|
|
else
|
|
strcpy (filespec_ext (outfilename_temp), ".tmp");
|
|
|
|
strcat (outfilename_temp, filespec_ext (outfilename));
|
|
}
|
|
else {
|
|
if (count++)
|
|
sprintf (outfilename_temp + strlen (outfilename_temp), ".tmp%d", count-1);
|
|
else
|
|
strcat (outfilename_temp, ".tmp");
|
|
}
|
|
|
|
testfile = fopen (outfilename_temp, "rb");
|
|
|
|
if (testfile) {
|
|
fclose (testfile);
|
|
continue;
|
|
}
|
|
|
|
if (out2filename) {
|
|
strcpy (out2filename_temp, outfilename_temp);
|
|
strcat (out2filename_temp, "c");
|
|
|
|
testfile = fopen (out2filename_temp, "rb");
|
|
|
|
if (testfile) {
|
|
fclose (testfile);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if defined(__WATCOMC__)
|
|
_ftime (&time1);
|
|
#elif defined(_WIN32)
|
|
_ftime64 (&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
|
|
#if defined(__OS2__)
|
|
setmode (fileno (stdout), O_BINARY);
|
|
#endif
|
|
}
|
|
else if ((wv_file.file = fopen (use_tempfiles ? outfilename_temp : outfilename, "w+b")) == NULL) {
|
|
error_line ("can't create file %s!", use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (infile);
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (!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));
|
|
|
|
fflush (stderr);
|
|
}
|
|
|
|
WavpackSetFileInformation (outfile, WavpackGetFileExtension (infile), WavpackGetFileFormat (infile));
|
|
|
|
// unless we've been specifically told not to, copy RIFF header
|
|
|
|
if (WavpackGetWrapperBytes (infile)) {
|
|
if (!(loc_config.qmode & QMODE_NO_STORE_WRAPPER) && !WavpackAddWrapper (outfile, WavpackGetWrapperData (infile), WavpackGetWrapperBytes (infile))) {
|
|
error_line ("%s", WavpackGetErrorMessage (outfile));
|
|
WavpackCloseFile (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
WavpackFreeWrapper (infile);
|
|
}
|
|
|
|
loc_config.bytes_per_sample = WavpackGetBytesPerSample (infile);
|
|
loc_config.bits_per_sample = WavpackGetBitsPerSample (infile);
|
|
loc_config.channel_mask = WavpackGetChannelMask (infile);
|
|
loc_config.num_channels = WavpackGetNumChannels (infile);
|
|
loc_config.sample_rate = WavpackGetSampleRate (infile);
|
|
loc_config.qmode |= WavpackGetQualifyMode (infile);
|
|
chan_ids = malloc (loc_config.num_channels + 1);
|
|
WavpackGetChannelIdentities (infile, chan_ids);
|
|
|
|
if (input_mode & MODE_FLOAT)
|
|
loc_config.float_norm_exp = WavpackGetFloatNormExp (infile);
|
|
|
|
if (input_mode & MODE_MD5)
|
|
loc_config.flags |= CONFIG_MD5_CHECKSUM;
|
|
|
|
if (!WavpackSetConfiguration64 (outfile, &loc_config, total_samples, chan_ids)) {
|
|
error_line ("%s", WavpackGetErrorMessage (outfile));
|
|
WavpackCloseFile (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
free (chan_ids);
|
|
|
|
if (loc_config.qmode & QMODE_REORDERED_CHANS) {
|
|
uint32_t layout = WavpackGetChannelLayout (infile, NULL);
|
|
unsigned char order [256];
|
|
|
|
if (layout & 0xff) {
|
|
WavpackGetChannelLayout (infile, order);
|
|
WavpackSetChannelLayout (outfile, layout, order);
|
|
}
|
|
}
|
|
else
|
|
WavpackSetChannelLayout (outfile, WavpackGetChannelLayout (infile, NULL), NULL);
|
|
|
|
// if we are creating a "correction" file, open it now for writing
|
|
|
|
if (out2filename) {
|
|
if ((wvc_file.file = fopen (use_tempfiles ? out2filename_temp : out2filename, "w+b")) == NULL) {
|
|
error_line ("can't create correction file!");
|
|
WavpackCloseFile (infile);
|
|
DoCloseHandle (wv_file.file);
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
// pack the audio portion of the file now; calculate md5 if we're writing it to the file or verify mode is active
|
|
|
|
result = repack_audio (outfile, infile, md5_verify);
|
|
|
|
// before anything else, make sure the source file was read without errors
|
|
|
|
if (result == WAVPACK_NO_ERROR) {
|
|
if (WavpackGetNumErrors (infile)) {
|
|
error_line ("missing data or crc errors detected in %d block(s)!", WavpackGetNumErrors (infile));
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (WavpackGetSampleIndex64 (outfile) != total_samples) {
|
|
error_line ("incorrect number of samples read from source file!");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if ((input_mode & MODE_LOSSLESS) && !quantize_bits) {
|
|
unsigned char md5_source [16];
|
|
|
|
if (WavpackGetMD5Sum (infile, md5_source) && memcmp (md5_source, md5_verify, sizeof (md5_source))) {
|
|
error_line ("MD5 signature in source should match, but does not!");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy the md5 sum if present in source; if there's not one there and the user asked to add it,
|
|
// store the one we just calculated
|
|
|
|
if (result == WAVPACK_NO_ERROR) {
|
|
if (WavpackGetMD5Sum (infile, md5_display)) {
|
|
if ((input_mode & MODE_LOSSLESS) && quantize_bits)
|
|
memcpy (md5_display, md5_verify, sizeof (md5_verify));
|
|
|
|
WavpackStoreMD5Sum (outfile, md5_display);
|
|
}
|
|
else if (loc_config.flags & CONFIG_MD5_CHECKSUM) {
|
|
memcpy (md5_display, md5_verify, sizeof (md5_display));
|
|
WavpackStoreMD5Sum (outfile, md5_verify);
|
|
}
|
|
}
|
|
|
|
// this is where we deal with a trailer (i.e., trailing wrapper) if there is one
|
|
|
|
if (result == WAVPACK_NO_ERROR && WavpackGetWrapperBytes (infile)) {
|
|
unsigned char *buffer = WavpackGetWrapperData (infile);
|
|
int wrapper_size = WavpackGetWrapperBytes (infile);
|
|
|
|
// unless we've been specifically told not to, copy RIFF trailer to output file
|
|
|
|
if (!(loc_config.qmode & QMODE_NO_STORE_WRAPPER) && !WavpackAddWrapper (outfile, buffer, wrapper_size)) {
|
|
error_line ("%s", WavpackGetErrorMessage (outfile));
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
// if we're supposed to try to import ID3 tags, check for and do that now
|
|
// (but only error on a bad tag, not just a missing one or one with no applicable items)
|
|
|
|
if (result == WAVPACK_NO_ERROR && import_id3 && wrapper_size > 10 && !strncmp ((char *) buffer, "ID3", 3)) {
|
|
int32_t bytes_used, id3_res;
|
|
char error [80];
|
|
|
|
// first we do a "dry run" pass through the ID3 tag, and only if that passes do we try to write the tag items
|
|
|
|
id3_res = ImportID3v2 (NULL, buffer, wrapper_size, error, &bytes_used);
|
|
|
|
if (!allow_huge_tags && bytes_used > 1048576) {
|
|
error_line ("imported tag items exceed 1 MB, use --allow-huge-tags to override");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
else if (bytes_used > 1048576 * 16) {
|
|
error_line ("imported tag items exceed 16 MB");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
else {
|
|
if (id3_res > 0)
|
|
id3_res = ImportID3v2 (outfile, buffer, wrapper_size, error, NULL);
|
|
|
|
if (id3_res < 0) {
|
|
error_line ("ID3v2 import: %s", error);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
else if (id3_res > 0)
|
|
imported_tag_items = id3_res;
|
|
}
|
|
}
|
|
|
|
WavpackFreeWrapper (infile);
|
|
}
|
|
|
|
// we're now done with any WavPack blocks, so flush any remaining data
|
|
|
|
if (result == WAVPACK_NO_ERROR && !WavpackFlushSamples (outfile)) {
|
|
error_line ("%s", WavpackGetErrorMessage (outfile));
|
|
result = WAVPACK_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 == WAVPACK_NO_ERROR && ((input_mode & MODE_VALID_TAG) || num_tag_items || imported_tag_items)) {
|
|
int num_binary_items = WavpackGetNumBinaryTagItems (infile);
|
|
int num_items = WavpackGetNumTagItems (infile), i;
|
|
int item_len, value_len;
|
|
char *item, *value;
|
|
int res = TRUE;
|
|
|
|
for (i = 0; i < num_items && res; ++i) {
|
|
item_len = WavpackGetTagItemIndexed (infile, i, NULL, 0);
|
|
item = malloc (item_len + 1);
|
|
WavpackGetTagItemIndexed (infile, i, item, item_len + 1);
|
|
|
|
// don't copy the values from the "encoder" or "settings" items because
|
|
// these should be based on the current encoder and user settings
|
|
|
|
if (!stricmp (item, "encoder")) {
|
|
value = malloc (80);
|
|
sprintf (value, "WavPack %s", PACKAGE_VERSION);
|
|
value_len = (int) strlen (value);
|
|
}
|
|
else if (!stricmp (item, "settings")) {
|
|
value = malloc (256);
|
|
make_settings_string (value, &loc_config);
|
|
value_len = (int) strlen (value);
|
|
}
|
|
else {
|
|
value_len = WavpackGetTagItem (infile, item, NULL, 0);
|
|
value = malloc (value_len + 1);
|
|
WavpackGetTagItem (infile, item, value, value_len + 1);
|
|
}
|
|
|
|
res = WavpackAppendTagItem (outfile, item, value, value_len);
|
|
free (value);
|
|
free (item);
|
|
}
|
|
|
|
for (i = 0; i < num_binary_items && res; ++i) {
|
|
item_len = WavpackGetBinaryTagItemIndexed (infile, i, NULL, 0);
|
|
item = malloc (item_len + 1);
|
|
WavpackGetBinaryTagItemIndexed (infile, i, item, item_len + 1);
|
|
value_len = WavpackGetBinaryTagItem (infile, item, NULL, 0);
|
|
value = malloc (value_len);
|
|
value_len = WavpackGetBinaryTagItem (infile, item, value, value_len);
|
|
res = WavpackAppendBinaryTagItem (outfile, item, value, value_len);
|
|
free (value);
|
|
free (item);
|
|
}
|
|
|
|
for (i = 0; i < num_tag_items && res; ++i)
|
|
if (tag_items [i].vsize) {
|
|
if (tag_items [i].binary)
|
|
res = WavpackAppendBinaryTagItem (outfile, tag_items [i].item, tag_items [i].value, tag_items [i].vsize);
|
|
else
|
|
res = WavpackAppendTagItem (outfile, tag_items [i].item, tag_items [i].value, tag_items [i].vsize);
|
|
}
|
|
else
|
|
WavpackDeleteTagItem (outfile, tag_items [i].item);
|
|
|
|
if (!res || !WavpackWriteTag (outfile)) {
|
|
error_line ("%s", WavpackGetErrorMessage (outfile));
|
|
result = WAVPACK_HARD_ERROR;
|
|
}
|
|
}
|
|
|
|
WavpackCloseFile (infile); // we're now done with input file, so close
|
|
|
|
// at this point we're completely 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 == WAVPACK_NO_ERROR)
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (out2filename && !DoCloseHandle (wvc_file.file)) {
|
|
error_line ("can't close correction file!");
|
|
|
|
if (result == WAVPACK_NO_ERROR)
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
// if there have been no errors up to now, and verify mode is enabled, do that now; only pass in the md5 if this
|
|
// was a lossless operation (either explicitly or because a high lossy bitrate resulted in lossless)
|
|
|
|
if (result == WAVPACK_NO_ERROR && verify_mode)
|
|
result = verify_audio (use_tempfiles ? outfilename_temp : outfilename, !WavpackLossyBlocks (outfile) ? md5_verify : NULL);
|
|
|
|
// if there were any errors, delete the output files, close the context, and return the error
|
|
|
|
if (result != WAVPACK_NO_ERROR) {
|
|
DoDeleteFile (use_tempfiles ? outfilename_temp : outfilename);
|
|
|
|
if (out2filename)
|
|
DoDeleteFile (use_tempfiles ? out2filename_temp : out2filename);
|
|
|
|
WavpackCloseFile (outfile);
|
|
return result;
|
|
}
|
|
|
|
if (result == WAVPACK_NO_ERROR && copy_time)
|
|
if (!copy_timestamp (infilename, use_tempfiles ? outfilename_temp : outfilename) ||
|
|
(out2filename && !copy_timestamp (infilename, use_tempfiles ? out2filename_temp : out2filename)))
|
|
error_line ("failure copying time stamp!");
|
|
|
|
// delete source file(s) if that option is enabled (this is done before temp file rename to make sure
|
|
// we don't delete the file(s) we just created)
|
|
|
|
if (result == WAVPACK_NO_ERROR && delete_source) {
|
|
int res;
|
|
|
|
if (stricmp (infilename, outfilename)) {
|
|
res = DoDeleteFile (infilename);
|
|
|
|
if (!quiet_mode || !res)
|
|
error_line ("%s source file %s", res ?
|
|
"deleted" : "can't delete", infilename);
|
|
}
|
|
|
|
if (input_mode & MODE_WVC) {
|
|
char in2filename [PATH_MAX];
|
|
|
|
strcpy (in2filename, infilename);
|
|
strcat (in2filename, "c");
|
|
|
|
if (!out2filename || stricmp (in2filename, out2filename)) {
|
|
res = DoDeleteFile (in2filename);
|
|
|
|
if (!quiet_mode || !res)
|
|
error_line ("%s source file %s", res ?
|
|
"deleted" : "can't delete", in2filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we were writing to a temp file because the target file already existed,
|
|
// do the rename / overwrite now (and if that fails, return the error)
|
|
|
|
if (use_tempfiles) {
|
|
#if defined(_WIN32)
|
|
FILE *temp;
|
|
|
|
if (remove (outfilename) && (temp = fopen (outfilename, "rb"))) {
|
|
error_line ("can not remove file %s, result saved in %s!", outfilename, outfilename_temp);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
fclose (temp);
|
|
}
|
|
else
|
|
#endif
|
|
if (rename (outfilename_temp, outfilename)) {
|
|
error_line ("can not rename temp file %s to %s!", outfilename_temp, outfilename);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (out2filename) {
|
|
#if defined(_WIN32)
|
|
FILE *temp;
|
|
|
|
if (remove (out2filename) && (temp = fopen (out2filename, "rb"))) {
|
|
error_line ("can not remove file %s, result saved in %s!", out2filename, out2filename_temp);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
fclose (temp);
|
|
}
|
|
else
|
|
#endif
|
|
if (rename (out2filename_temp, out2filename)) {
|
|
error_line ("can not rename temp file %s to %s!", out2filename_temp, out2filename);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
free (outfilename_temp);
|
|
if (out2filename) free (out2filename_temp);
|
|
|
|
if (result != WAVPACK_NO_ERROR) {
|
|
WavpackCloseFile (outfile);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// compute and display the time consumed along with some other details of
|
|
// the packing operation, and then return WAVPACK_NO_ERROR
|
|
|
|
#if defined(__WATCOMC__)
|
|
_ftime (&time2);
|
|
dtime = time2.time + time2.millitm / 1000.0;
|
|
dtime -= time1.time + time1.millitm / 1000.0;
|
|
#elif defined(_WIN32)
|
|
_ftime64 (&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) && WavpackGetEncodedNoise (outfile, NULL) > 0.0) {
|
|
int full_scale_bits = WavpackGetBitsPerSample (outfile);
|
|
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 = WavpackGetEncodedNoise (outfile, &peak);
|
|
|
|
error_line ("ave noise = %.2f dB, peak noise = %.2f dB",
|
|
log10 (sum / WavpackGetNumSamples (outfile) / full_scale_rms) * 10,
|
|
log10 (peak / full_scale_rms) * 10);
|
|
}
|
|
|
|
if (!quiet_mode) {
|
|
char *file, *fext, *oper, *cmode, cratio [16] = "";
|
|
|
|
if (imported_tag_items)
|
|
error_line ("successfully imported %d items from ID3v2 tag", imported_tag_items);
|
|
|
|
if (config->flags & CONFIG_MD5_CHECKSUM) {
|
|
char md5_string [] = "original md5 signature: 00000000000000000000000000000000";
|
|
int i;
|
|
|
|
for (i = 0; i < 16; ++i)
|
|
sprintf (md5_string + 24 + (i * 2), "%02x", md5_display [i]);
|
|
|
|
error_line (md5_string);
|
|
}
|
|
|
|
if (outfilename && *outfilename != '-') {
|
|
file = FN_FIT (outfilename);
|
|
fext = wvc_file.bytes_written ? " (+.wvc)" : "";
|
|
oper = verify_mode ? "created (and verified)" : "created";
|
|
}
|
|
else {
|
|
file = (*infilename == '-') ? "stdin" : FN_FIT (infilename);
|
|
fext = "";
|
|
oper = "packed";
|
|
}
|
|
|
|
if (WavpackLossyBlocks (outfile)) {
|
|
cmode = "lossy";
|
|
|
|
if (WavpackGetAverageBitrate (outfile, TRUE) != 0.0)
|
|
sprintf (cratio, ", %d kbps", (int) (WavpackGetAverageBitrate (outfile, TRUE) / 1000.0));
|
|
}
|
|
else {
|
|
cmode = "lossless";
|
|
|
|
if (WavpackGetRatio (outfile) != 0.0)
|
|
sprintf (cratio, ", %.2f%%", 100.0 - WavpackGetRatio (outfile) * 100.0);
|
|
}
|
|
|
|
error_line ("%s %s%s in %.2f secs (%s%s)", oper, file, fext, dtime, cmode, cratio);
|
|
}
|
|
|
|
WavpackCloseFile (outfile);
|
|
return WAVPACK_NO_ERROR;
|
|
}
|
|
|
|
// This function handles the actual audio data transcoding. It assumes that the
|
|
// input file is positioned at the beginning of the audio data and that the
|
|
// WavPack configuration has been set. If the "md5_digest_source" pointer is not
|
|
// NULL, then a MD5 sum is calculated on the audio data during the transcoding
|
|
// and stored there at the completion. Note that the md5 requires a conversion
|
|
// to the native data format (endianness and bytes per sample) that is not
|
|
// required otherwise.
|
|
|
|
static int repack_audio (WavpackContext *outfile, WavpackContext *infile, unsigned char *md5_digest_source)
|
|
{
|
|
int bps = WavpackGetBytesPerSample (infile), num_channels = WavpackGetNumChannels (infile);
|
|
int qmode = WavpackGetQualifyMode (infile);
|
|
unsigned char *new_channel_order = NULL;
|
|
uint32_t input_samples = INPUT_SAMPLES;
|
|
unsigned char *format_buffer;
|
|
int32_t *sample_buffer;
|
|
double progress = -1.0;
|
|
MD5_CTX md5_context;
|
|
int32_t quantize_bit_mask = 0;
|
|
double fquantize_scale = 1.0, fquantize_iscale = 1.0;
|
|
|
|
// modify input_samples if we're doing blocked DSD, or we have a large number of channels
|
|
|
|
if (qmode & QMODE_DSD_IN_BLOCKS)
|
|
input_samples = DSD_BLOCKSIZE;
|
|
else
|
|
while (input_samples * sizeof (int32_t) * WavpackGetNumChannels (outfile) > 2048*1024)
|
|
input_samples >>= 1;
|
|
|
|
if (md5_digest_source) {
|
|
format_buffer = malloc (input_samples * bps * WavpackGetNumChannels (outfile));
|
|
MD5_Init (&md5_context);
|
|
|
|
if (qmode & QMODE_REORDERED_CHANS) {
|
|
int layout = WavpackGetChannelLayout (infile, NULL), i;
|
|
|
|
if ((layout & 0xff) <= num_channels) {
|
|
new_channel_order = malloc (num_channels);
|
|
|
|
for (i = 0; i < num_channels; ++i)
|
|
new_channel_order [i] = i;
|
|
|
|
WavpackGetChannelLayout (infile, new_channel_order);
|
|
}
|
|
}
|
|
}
|
|
|
|
WavpackPackInit (outfile);
|
|
sample_buffer = malloc (input_samples * sizeof (int32_t) * WavpackGetNumChannels (outfile));
|
|
|
|
if (quantize_bits && quantize_bits < bps*8) {
|
|
quantize_bit_mask = ~((1<<(bps*8-quantize_bits))-1);
|
|
if (MODE_FLOAT == (WavpackGetMode(infile) & MODE_FLOAT)) {
|
|
int float_norm_exp = WavpackGetFloatNormExp (infile);
|
|
fquantize_scale = exp2 (quantize_bits + 126 - float_norm_exp);
|
|
fquantize_iscale = exp2 (float_norm_exp - 126 - quantize_bits);
|
|
}
|
|
}
|
|
|
|
while (1) {
|
|
int32_t sample_count = WavpackUnpackSamples (infile, sample_buffer, input_samples);
|
|
|
|
if (!sample_count)
|
|
break;
|
|
|
|
if (quantize_bit_mask) {
|
|
unsigned int x,l = sample_count * num_channels;
|
|
if (0 == (WavpackGetMode(infile) & MODE_FLOAT)) {
|
|
if (quantize_round) {
|
|
int32_t offset = (quantize_bit_mask >> 1) ^ quantize_bit_mask;
|
|
int shift = 32 - bps * 8;
|
|
|
|
for (x = 0; x < l; x ++)
|
|
if (sample_buffer[x] < 0 || ((sample_buffer[x] + offset) << shift) > 0)
|
|
sample_buffer[x] += offset;
|
|
}
|
|
|
|
for (x = 0; x < l; x ++) sample_buffer[x] &= quantize_bit_mask;
|
|
}
|
|
else {
|
|
for (x = 0; x < l; x ++) {
|
|
const float f = *(float *)&sample_buffer[x];
|
|
*(float *)&sample_buffer[x] = (float) (floor(f * fquantize_scale + 0.5) * fquantize_iscale);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!WavpackPackSamples (outfile, sample_buffer, sample_count)) {
|
|
error_line ("%s", WavpackGetErrorMessage (outfile));
|
|
free (sample_buffer);
|
|
return WAVPACK_HARD_ERROR;
|
|
}
|
|
|
|
if (md5_digest_source) {
|
|
if (new_channel_order)
|
|
unreorder_channels (sample_buffer, new_channel_order, num_channels, sample_count);
|
|
|
|
if (qmode & QMODE_DSD_AUDIO) {
|
|
unsigned char *dptr = format_buffer;
|
|
int32_t *sptr = sample_buffer;
|
|
|
|
if (qmode & QMODE_DSD_IN_BLOCKS) {
|
|
int cc = num_channels;
|
|
|
|
while (cc--) {
|
|
int si;
|
|
|
|
for (si = 0; si < DSD_BLOCKSIZE; si++, sptr += num_channels)
|
|
if (si < sample_count)
|
|
*dptr++ = (qmode & QMODE_DSD_LSB_FIRST) ? bit_reverse_table [*sptr & 0xff] : *sptr;
|
|
else
|
|
*dptr++ = 0;
|
|
|
|
sptr -= (DSD_BLOCKSIZE * num_channels) - 1;
|
|
}
|
|
|
|
sample_count = DSD_BLOCKSIZE;
|
|
}
|
|
else {
|
|
int scount = sample_count * num_channels;
|
|
|
|
while (scount--)
|
|
*dptr++ = *sptr++;
|
|
}
|
|
}
|
|
else
|
|
store_samples (format_buffer, sample_buffer, qmode, bps, sample_count * num_channels);
|
|
|
|
MD5_Update (&md5_context, format_buffer, bps * sample_count * num_channels);
|
|
}
|
|
|
|
if (check_break ()) {
|
|
#if defined(_WIN32)
|
|
fprintf (stderr, "^C\n");
|
|
#else
|
|
fprintf (stderr, "\n");
|
|
#endif
|
|
fflush (stderr);
|
|
free (sample_buffer);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (WavpackGetProgress (outfile) != -1.0 &&
|
|
progress != floor (WavpackGetProgress (outfile) * encode_time_percent + 0.5)) {
|
|
int nobs = progress == -1.0;
|
|
|
|
progress = floor (WavpackGetProgress (outfile) * encode_time_percent + 0.5);
|
|
display_progress (progress / 100.0);
|
|
|
|
if (!quiet_mode) {
|
|
fprintf (stderr, "%s%3d%% done...",
|
|
nobs ? " " : "\b\b\b\b\b\b\b\b\b\b\b\b", (int) progress);
|
|
fflush (stderr);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (new_channel_order)
|
|
free (new_channel_order);
|
|
|
|
free (sample_buffer);
|
|
|
|
if (!WavpackFlushSamples (outfile)) {
|
|
error_line ("%s", WavpackGetErrorMessage (outfile));
|
|
return WAVPACK_HARD_ERROR;
|
|
}
|
|
|
|
if (md5_digest_source) {
|
|
MD5_Final (md5_digest_source, &md5_context);
|
|
free (format_buffer);
|
|
}
|
|
|
|
return WAVPACK_NO_ERROR;
|
|
}
|
|
|
|
static void reorder_channels (void *data, unsigned char *order, int num_chans,
|
|
int num_samples, int bytes_per_sample)
|
|
{
|
|
char reorder_buffer [64], *temp = reorder_buffer;
|
|
char *src = data;
|
|
|
|
if (num_chans * bytes_per_sample > 64)
|
|
temp = malloc (num_chans * bytes_per_sample);
|
|
|
|
while (num_samples--) {
|
|
char *start = src;
|
|
int chan;
|
|
|
|
for (chan = 0; chan < num_chans; ++chan) {
|
|
char *dst = temp + (order [chan] * bytes_per_sample);
|
|
int bc = bytes_per_sample;
|
|
|
|
while (bc--)
|
|
*dst++ = *src++;
|
|
}
|
|
|
|
memcpy (start, temp, num_chans * bytes_per_sample);
|
|
}
|
|
|
|
if (num_chans * bytes_per_sample > 64)
|
|
free (temp);
|
|
}
|
|
|
|
static void unreorder_channels (int32_t *data, unsigned char *order, int num_chans, int num_samples)
|
|
{
|
|
int32_t reorder_buffer [16], *temp = reorder_buffer;
|
|
|
|
if (num_chans > 16)
|
|
temp = malloc (num_chans * sizeof (*data));
|
|
|
|
while (num_samples--) {
|
|
int chan;
|
|
|
|
for (chan = 0; chan < num_chans; ++chan)
|
|
temp [chan] = data [order[chan]];
|
|
|
|
memcpy (data, temp, num_chans * sizeof (*data));
|
|
data += num_chans;
|
|
}
|
|
|
|
if (num_chans > 16)
|
|
free (temp);
|
|
}
|
|
|
|
// Verify the specified WavPack input file. This function uses the library
|
|
// routines provided in wputils.c to do all unpacking. If an MD5 sum is provided
|
|
// by the caller, then this function will take care of reformatting the data
|
|
// (which is returned in native-endian longs) to the standard little-endian
|
|
// for a proper MD5 verification. Otherwise a lossy verification is assumed,
|
|
// and we only verify the exact number of samples and whether the decoding
|
|
// library detected CRC errors in any WavPack blocks.
|
|
|
|
#define VERIFY_BLOCKSIZE DSD_BLOCKSIZE
|
|
|
|
static int verify_audio (char *infilename, unsigned char *md5_digest_source)
|
|
{
|
|
int num_channels, bps, qmode, result = WAVPACK_NO_ERROR;
|
|
unsigned char *new_channel_order = NULL;
|
|
int64_t total_unpacked_samples = 0;
|
|
unsigned char md5_digest_result [16];
|
|
double progress = -1.0;
|
|
int32_t *temp_buffer;
|
|
MD5_CTX md5_context;
|
|
WavpackContext *wpc;
|
|
char error [80];
|
|
|
|
// use library to open WavPack file
|
|
|
|
#ifdef _WIN32
|
|
wpc = WavpackOpenFileInput (infilename, error, OPEN_WVC | OPEN_FILE_UTF8 | OPEN_DSD_NATIVE | OPEN_ALT_TYPES, 0);
|
|
#else
|
|
wpc = WavpackOpenFileInput (infilename, error, OPEN_WVC | OPEN_DSD_NATIVE | OPEN_ALT_TYPES, 0);
|
|
#endif
|
|
|
|
if (!wpc) {
|
|
error_line (error);
|
|
return WAVPACK_SOFT_ERROR;
|
|
}
|
|
|
|
if (md5_digest_source)
|
|
MD5_Init (&md5_context);
|
|
|
|
qmode = WavpackGetQualifyMode (wpc);
|
|
num_channels = WavpackGetNumChannels (wpc);
|
|
bps = WavpackGetBytesPerSample (wpc);
|
|
temp_buffer = malloc (VERIFY_BLOCKSIZE * num_channels * 4);
|
|
|
|
if (qmode & QMODE_REORDERED_CHANS) {
|
|
int layout = WavpackGetChannelLayout (wpc, NULL), i;
|
|
|
|
if ((layout & 0xff) <= num_channels) {
|
|
new_channel_order = malloc (num_channels);
|
|
|
|
for (i = 0; i < num_channels; ++i)
|
|
new_channel_order [i] = i;
|
|
|
|
WavpackGetChannelLayout (wpc, new_channel_order);
|
|
}
|
|
}
|
|
|
|
while (result == WAVPACK_NO_ERROR) {
|
|
int32_t samples_unpacked;
|
|
|
|
samples_unpacked = WavpackUnpackSamples (wpc, temp_buffer, VERIFY_BLOCKSIZE);
|
|
total_unpacked_samples += samples_unpacked;
|
|
|
|
if (samples_unpacked) {
|
|
if (md5_digest_source) {
|
|
if (new_channel_order)
|
|
unreorder_channels (temp_buffer, new_channel_order, num_channels, samples_unpacked);
|
|
|
|
if (qmode & QMODE_DSD_AUDIO) {
|
|
unsigned char *dsd_buffer = malloc (DSD_BLOCKSIZE * num_channels);
|
|
unsigned char *dptr = dsd_buffer;
|
|
int32_t *sptr = temp_buffer;
|
|
|
|
if (qmode & QMODE_DSD_IN_BLOCKS) {
|
|
int cc = num_channels;
|
|
|
|
while (cc--) {
|
|
int si;
|
|
|
|
for (si = 0; si < DSD_BLOCKSIZE; si++, sptr += num_channels)
|
|
if (si < samples_unpacked)
|
|
*dptr++ = (qmode & QMODE_DSD_LSB_FIRST) ? bit_reverse_table [*sptr & 0xff] : *sptr;
|
|
else
|
|
*dptr++ = 0;
|
|
|
|
sptr -= (DSD_BLOCKSIZE * num_channels) - 1;
|
|
}
|
|
|
|
samples_unpacked = DSD_BLOCKSIZE; // count the entire block for MD5 (even if partial/last)
|
|
}
|
|
else {
|
|
int scount = samples_unpacked * num_channels;
|
|
|
|
while (scount--)
|
|
*dptr++ = *sptr++;
|
|
}
|
|
|
|
MD5_Update (&md5_context, dsd_buffer, samples_unpacked * num_channels);
|
|
free (dsd_buffer);
|
|
}
|
|
else {
|
|
store_samples (temp_buffer, temp_buffer, qmode, bps, samples_unpacked * num_channels);
|
|
MD5_Update (&md5_context, (unsigned char *) temp_buffer, bps * samples_unpacked * num_channels);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
|
|
if (check_break ()) {
|
|
#if defined(_WIN32)
|
|
fprintf (stderr, "^C\n");
|
|
#else
|
|
fprintf (stderr, "\n");
|
|
#endif
|
|
fflush (stderr);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (WavpackGetProgress (wpc) != -1.0 &&
|
|
progress != floor (WavpackGetProgress (wpc) * (100.0 - encode_time_percent) + encode_time_percent + 0.5)) {
|
|
|
|
progress = floor (WavpackGetProgress (wpc) * (100.0 - encode_time_percent) + encode_time_percent + 0.5);
|
|
display_progress (progress / 100.0);
|
|
|
|
if (!quiet_mode) {
|
|
fprintf (stderr, "%s%3d%% done...",
|
|
"\b\b\b\b\b\b\b\b\b\b\b\b", (int) progress);
|
|
fflush (stderr);
|
|
}
|
|
}
|
|
}
|
|
|
|
free (temp_buffer);
|
|
|
|
if (new_channel_order)
|
|
free (new_channel_order);
|
|
|
|
// If we have been provided an MD5 sum, then the assumption is that we are doing lossless compression (either explicitly
|
|
// with lossless mode or having a high enough bitrate that the result is lossless) and we can use the MD5 sum as a pretty
|
|
// definitive verification.
|
|
|
|
if (result == WAVPACK_NO_ERROR && md5_digest_source) {
|
|
MD5_Final (md5_digest_result, &md5_context);
|
|
|
|
if (memcmp (md5_digest_result, md5_digest_source, 16)) {
|
|
char md5_string1 [] = "00000000000000000000000000000000";
|
|
char md5_string2 [] = "00000000000000000000000000000000";
|
|
int i;
|
|
|
|
for (i = 0; i < 16; ++i) {
|
|
sprintf (md5_string1 + (i * 2), "%02x", md5_digest_source [i]);
|
|
sprintf (md5_string2 + (i * 2), "%02x", md5_digest_result [i]);
|
|
}
|
|
|
|
error_line ("original md5: %s", md5_string1);
|
|
error_line ("verified md5: %s", md5_string2);
|
|
error_line ("MD5 signatures should match, but do not!");
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
// If we have not been provided an MD5 sum, then the assumption is that we are doing lossy compression and cannot rely
|
|
// (obviously) on that for verification. For these cases we make sure that the number of samples generated was exactly
|
|
// correct and that the WavPack decoding library did not detect an error. There is a simple CRC on every WavPack block
|
|
// that should catch any random corruption, although it's possible that this might miss some decoder bug that occurs
|
|
// late in the decoding process (e.g., after the CRC).
|
|
|
|
if (result == WAVPACK_NO_ERROR) {
|
|
if (WavpackGetNumSamples64 (wpc) != -1) {
|
|
if (total_unpacked_samples < WavpackGetNumSamples64 (wpc)) {
|
|
error_line ("file is missing %llu samples!",
|
|
WavpackGetNumSamples64 (wpc) - total_unpacked_samples);
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
else if (total_unpacked_samples > WavpackGetNumSamples64 (wpc)) {
|
|
error_line ("file has %llu extra samples!",
|
|
total_unpacked_samples - WavpackGetNumSamples64 (wpc));
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
if (WavpackGetNumErrors (wpc)) {
|
|
error_line ("missing data or crc errors detected in %d block(s)!", WavpackGetNumErrors (wpc));
|
|
result = WAVPACK_SOFT_ERROR;
|
|
}
|
|
}
|
|
|
|
WavpackCloseFile (wpc);
|
|
return result;
|
|
}
|
|
|
|
// Create a string from the specified configuration that can be used for the "settings"
|
|
// tag. Note that the module globals allow_huge_tags and quantize_bits are also accessed.
|
|
// Room for 256 characters should be plenty.
|
|
|
|
static void make_settings_string (char *settings, WavpackConfig *config)
|
|
{
|
|
strcpy (settings, "-");
|
|
|
|
// basic settings
|
|
|
|
if (config->flags & CONFIG_FAST_FLAG)
|
|
strcat (settings, "f");
|
|
else if (config->flags & CONFIG_VERY_HIGH_FLAG)
|
|
strcat (settings, "hh");
|
|
else if (config->flags & CONFIG_HIGH_FLAG)
|
|
strcat (settings, "h");
|
|
|
|
if (config->flags & CONFIG_HYBRID_FLAG) {
|
|
sprintf (settings + strlen (settings), "b%g", config->bitrate);
|
|
|
|
if (config->flags & CONFIG_OPTIMIZE_WVC)
|
|
strcat (settings, "cc");
|
|
else if (config->flags & CONFIG_CREATE_WVC)
|
|
strcat (settings, "c");
|
|
}
|
|
|
|
if (config->flags & CONFIG_EXTRA_MODE)
|
|
sprintf (settings + strlen (settings), "x%d", config->xmode ? config->xmode : 1);
|
|
|
|
// override settings
|
|
|
|
if (config->flags & CONFIG_JOINT_OVERRIDE) {
|
|
if (config->flags & CONFIG_JOINT_STEREO)
|
|
strcat (settings, "j1");
|
|
else
|
|
strcat (settings, "j0");
|
|
}
|
|
|
|
if (config->flags & CONFIG_SHAPE_OVERRIDE)
|
|
sprintf (settings + strlen (settings), "s%g", config->shaping_weight);
|
|
|
|
// long options
|
|
|
|
if (quantize_bits)
|
|
sprintf (settings + strlen (settings), " --pre-quantize%s=%d",
|
|
quantize_round ? "-round" : "", quantize_bits);
|
|
|
|
if (config->block_samples)
|
|
sprintf (settings + strlen (settings), " --blocksize=%d", config->block_samples);
|
|
|
|
if (config->flags & CONFIG_DYNAMIC_SHAPING)
|
|
strcat (settings, " --use-dns");
|
|
|
|
if (config->flags & CONFIG_CROSS_DECORR)
|
|
strcat (settings, " --cross-decorr");
|
|
|
|
if (config->flags & CONFIG_MERGE_BLOCKS)
|
|
strcat (settings, " --merge-blocks");
|
|
|
|
if (config->flags & CONFIG_PAIR_UNDEF_CHANS)
|
|
strcat (settings, " --pair-unassigned-chans");
|
|
|
|
if (allow_huge_tags)
|
|
strcat (settings, " --allow-huge-tags");
|
|
}
|
|
|
|
// Code to load samples. Destination is an array of int32_t data (which is what WavPack uses
|
|
// internally), but the source can have from 1 to 4 bytes per sample. Also, the source data
|
|
// is assumed to be little-endian and signed, except for byte data which is unsigned (these
|
|
// are WAV file defaults). The endian and signedness can be overridden with the qmode flags
|
|
// to support other formats.
|
|
|
|
static void load_little_endian_unsigned_samples (int32_t *dst, void *src, int bps, int count);
|
|
static void load_little_endian_signed_samples (int32_t *dst, void *src, int bps, int count);
|
|
static void load_big_endian_unsigned_samples (int32_t *dst, void *src, int bps, int count);
|
|
static void load_big_endian_signed_samples (int32_t *dst, void *src, int bps, int count);
|
|
|
|
static void load_samples (int32_t *dst, void *src, int qmode, int bps, int count)
|
|
{
|
|
if (qmode & QMODE_BIG_ENDIAN) {
|
|
if ((qmode & QMODE_UNSIGNED_WORDS) || (bps == 1 && !(qmode & QMODE_SIGNED_BYTES)))
|
|
load_big_endian_unsigned_samples (dst, src, bps, count);
|
|
else
|
|
load_big_endian_signed_samples (dst, src, bps, count);
|
|
}
|
|
else if ((qmode & QMODE_UNSIGNED_WORDS) || (bps == 1 && !(qmode & QMODE_SIGNED_BYTES)))
|
|
load_little_endian_unsigned_samples (dst, src, bps, count);
|
|
else
|
|
load_little_endian_signed_samples (dst, src, bps, count);
|
|
}
|
|
|
|
static void load_little_endian_unsigned_samples (int32_t *dst, void *src, int bps, int count)
|
|
{
|
|
unsigned char *sptr = src;
|
|
|
|
switch (bps) {
|
|
|
|
case 1:
|
|
while (count--)
|
|
*dst++ = *sptr++ - 0x80;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (count--) {
|
|
*dst++ = (sptr [0] | ((int32_t) sptr [1] << 8)) - 0x8000;
|
|
sptr += 2;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (count--) {
|
|
*dst++ = (sptr [0] | ((int32_t) sptr [1] << 8) | ((int32_t) sptr [2] << 16)) - 0x800000;
|
|
sptr += 3;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (count--) {
|
|
*dst++ = (sptr [0] | sptr [1] << 8 | sptr [2] << 16 | (uint32_t) sptr [3] << 24) ^ 0x80000000;
|
|
sptr += 4;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void load_little_endian_signed_samples (int32_t *dst, void *src, int bps, int count)
|
|
{
|
|
unsigned char *sptr = src;
|
|
|
|
switch (bps) {
|
|
|
|
case 1:
|
|
while (count--)
|
|
*dst++ = (signed char) *sptr++;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (count--) {
|
|
*dst++ = (int16_t)(sptr [0] | sptr [1] << 8);
|
|
sptr += 2;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (count--) {
|
|
*dst++ = (int32_t)((uint32_t)(sptr [0] | sptr [1] << 8 | sptr [2] << 16) << 8) >> 8;
|
|
sptr += 3;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (count--) {
|
|
*dst++ = sptr [0] | sptr [1] << 8 | sptr [2] << 16 | (uint32_t) sptr [3] << 24;
|
|
sptr += 4;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void load_big_endian_unsigned_samples (int32_t *dst, void *src, int bps, int count)
|
|
{
|
|
unsigned char *sptr = src;
|
|
|
|
switch (bps) {
|
|
|
|
case 1:
|
|
while (count--)
|
|
*dst++ = *sptr++ - 0x80;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (count--) {
|
|
*dst++ = (sptr [1] | ((int32_t) sptr [0] << 8)) - 0x8000;
|
|
sptr += 2;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (count--) {
|
|
*dst++ = (sptr [2] | ((int32_t) sptr [1] << 8) | ((int32_t) sptr [0] << 16)) - 0x800000;
|
|
sptr += 3;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (count--) {
|
|
*dst++ = (sptr [3] | sptr [2] << 8 | sptr [1] << 16 | (uint32_t) sptr [0] << 24) ^ 0x80000000;
|
|
sptr += 4;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void load_big_endian_signed_samples (int32_t *dst, void *src, int bps, int count)
|
|
{
|
|
unsigned char *sptr = src;
|
|
|
|
switch (bps) {
|
|
|
|
case 1:
|
|
while (count--)
|
|
*dst++ = (signed char) *sptr++;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (count--) {
|
|
*dst++ = (int16_t)(sptr [1] | sptr [0] << 8);
|
|
sptr += 2;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (count--) {
|
|
*dst++ = (int32_t)((uint32_t)(sptr [2] | sptr [1] << 8 | sptr [0] << 16) << 8) >> 8;
|
|
sptr += 3;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (count--) {
|
|
*dst++ = sptr [3] | sptr [2] << 8 | sptr [1] << 16 | (uint32_t) sptr [0] << 24;
|
|
sptr += 4;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Code to store samples. Source is an array of int32_t data (which is what WavPack uses
|
|
// internally), but the destination can have from 1 to 4 bytes per sample. Also, the destination
|
|
// data is assumed to be little-endian and signed, except for byte data which is unsigned (these
|
|
// are WAV file defaults). The endian and signedness can be overridden with the qmode flags
|
|
// to support other formats.
|
|
|
|
static void *store_little_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count);
|
|
static void *store_little_endian_signed_samples (void *dst, int32_t *src, int bps, int count);
|
|
static void *store_big_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count);
|
|
static void *store_big_endian_signed_samples (void *dst, int32_t *src, int bps, int count);
|
|
|
|
static void *store_samples (void *dst, int32_t *src, int qmode, int bps, int count)
|
|
{
|
|
if (qmode & QMODE_BIG_ENDIAN) {
|
|
if ((qmode & QMODE_UNSIGNED_WORDS) || (bps == 1 && !(qmode & QMODE_SIGNED_BYTES)))
|
|
return store_big_endian_unsigned_samples (dst, src, bps, count);
|
|
else
|
|
return store_big_endian_signed_samples (dst, src, bps, count);
|
|
}
|
|
else if ((qmode & QMODE_UNSIGNED_WORDS) || (bps == 1 && !(qmode & QMODE_SIGNED_BYTES)))
|
|
return store_little_endian_unsigned_samples (dst, src, bps, count);
|
|
else
|
|
return store_little_endian_signed_samples (dst, src, bps, count);
|
|
}
|
|
|
|
static void *store_little_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count)
|
|
{
|
|
unsigned char *dptr = dst;
|
|
int32_t temp;
|
|
|
|
switch (bps) {
|
|
|
|
case 1:
|
|
while (count--)
|
|
*dptr++ = *src++ + 0x80;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) (temp = *src++ + 0x8000);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) (temp = *src++ + 0x800000);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
*dptr++ = (unsigned char) (temp >> 16);
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) (temp = *src++ + 0x80000000);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
*dptr++ = (unsigned char) (temp >> 16);
|
|
*dptr++ = (unsigned char) (temp >> 24);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return dptr;
|
|
}
|
|
|
|
static void *store_little_endian_signed_samples (void *dst, int32_t *src, int bps, int count)
|
|
{
|
|
unsigned char *dptr = dst;
|
|
int32_t temp;
|
|
|
|
switch (bps) {
|
|
|
|
case 1:
|
|
while (count--)
|
|
*dptr++ = *src++;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) (temp = *src++);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) (temp = *src++);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
*dptr++ = (unsigned char) (temp >> 16);
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) (temp = *src++);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
*dptr++ = (unsigned char) (temp >> 16);
|
|
*dptr++ = (unsigned char) (temp >> 24);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return dptr;
|
|
}
|
|
|
|
static void *store_big_endian_unsigned_samples (void *dst, int32_t *src, int bps, int count)
|
|
{
|
|
unsigned char *dptr = dst;
|
|
int32_t temp;
|
|
|
|
switch (bps) {
|
|
|
|
case 1:
|
|
while (count--)
|
|
*dptr++ = *src++ + 0x80;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) ((temp = *src++ + 0x8000) >> 8);
|
|
*dptr++ = (unsigned char) temp;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) ((temp = *src++ + 0x800000) >> 16);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
*dptr++ = (unsigned char) temp;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) ((temp = *src++ + 0x80000000) >> 24);
|
|
*dptr++ = (unsigned char) (temp >> 16);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
*dptr++ = (unsigned char) temp;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return dptr;
|
|
}
|
|
|
|
static void *store_big_endian_signed_samples (void *dst, int32_t *src, int bps, int count)
|
|
{
|
|
unsigned char *dptr = dst;
|
|
int32_t temp;
|
|
|
|
switch (bps) {
|
|
|
|
case 1:
|
|
while (count--)
|
|
*dptr++ = *src++;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) ((temp = *src++) >> 8);
|
|
*dptr++ = (unsigned char) temp;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) ((temp = *src++) >> 16);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
*dptr++ = (unsigned char) temp;
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
while (count--) {
|
|
*dptr++ = (unsigned char) ((temp = *src++) >> 24);
|
|
*dptr++ = (unsigned char) (temp >> 16);
|
|
*dptr++ = (unsigned char) (temp >> 8);
|
|
*dptr++ = (unsigned char) temp;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return dptr;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
|
|
// 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 wchar_t *Wide, unsigned char *pUTF8, int len)
|
|
{
|
|
const wchar_t *pWide = Wide;
|
|
int outndx = 0;
|
|
|
|
while (*pWide) {
|
|
if (*pWide < 0x80 && outndx + 1 < len)
|
|
pUTF8 [outndx++] = (unsigned char) *pWide++;
|
|
else if (*pWide < 0x800 && outndx + 2 < len) {
|
|
pUTF8 [outndx++] = (unsigned char) (0xc0 | ((*pWide >> 6) & 0x1f));
|
|
pUTF8 [outndx++] = (unsigned char) (0x80 | (*pWide++ & 0x3f));
|
|
}
|
|
else if (outndx + 3 < len) {
|
|
pUTF8 [outndx++] = (unsigned char) (0xe0 | ((*pWide >> 12) & 0xf));
|
|
pUTF8 [outndx++] = (unsigned char) (0x80 | ((*pWide >> 6) & 0x3f));
|
|
pUTF8 [outndx++] = (unsigned char) (0x80 | (*pWide++ & 0x3f));
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
pUTF8 [outndx] = 0;
|
|
return (int)(pWide - Wide);
|
|
}
|
|
|
|
// Convert a text 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 TextToUTF8 (void *string, int len)
|
|
{
|
|
unsigned char *inp = string;
|
|
|
|
// simple case: test for UTF8 BOM and if so, simply delete the BOM
|
|
|
|
if (len > 3 && inp [0] == 0xEF && inp [1] == 0xBB && inp [2] == 0xBF) {
|
|
memmove (inp, inp + 3, len - 3);
|
|
inp [len - 3] = 0;
|
|
}
|
|
else if (* (wchar_t *) string == 0xFEFF) {
|
|
wchar_t *temp = _wcsdup (string);
|
|
|
|
WideCharToUTF8 (temp + 1, (unsigned char *) string, len);
|
|
free (temp);
|
|
}
|
|
else {
|
|
int max_chars = (int) strlen (string);
|
|
wchar_t *temp = (wchar_t *) malloc ((max_chars + 1) * 2);
|
|
|
|
MultiByteToWideChar (CP_ACP, 0, string, -1, temp, max_chars + 1);
|
|
WideCharToUTF8 (temp, (unsigned char *) string, len);
|
|
free (temp);
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
static void TextToUTF8 (void *string, int len)
|
|
{
|
|
char *temp = malloc (len);
|
|
char *outp = temp;
|
|
char *inp = string;
|
|
size_t insize = 0;
|
|
size_t outsize = len - 1;
|
|
int err = 0;
|
|
char *old_locale;
|
|
iconv_t converter;
|
|
|
|
// simple case: test for UTF8 BOM and if so, simply delete the BOM and return
|
|
|
|
if (len > 3 && (unsigned char) inp [0] == 0xEF && (unsigned char) inp [1] == 0xBB &&
|
|
(unsigned char) inp [2] == 0xBF) {
|
|
memmove (inp, inp + 3, len - 3);
|
|
inp [len - 3] = 0;
|
|
return;
|
|
}
|
|
|
|
memset(temp, 0, len);
|
|
old_locale = setlocale (LC_CTYPE, "");
|
|
|
|
if ((unsigned char) inp [0] == 0xFF && (unsigned char) inp [1] == 0xFE) {
|
|
uint16_t *utf16p = (uint16_t *) (inp += 2);
|
|
|
|
while (*utf16p++)
|
|
insize += 2;
|
|
|
|
converter = iconv_open ("UTF-8", "UTF-16LE");
|
|
}
|
|
else {
|
|
insize = strlen (string);
|
|
converter = iconv_open ("UTF-8", "");
|
|
}
|
|
|
|
if (converter != (iconv_t) -1) {
|
|
err = iconv (converter, &inp, &insize, &outp, &outsize);
|
|
iconv_close (converter);
|
|
}
|
|
else
|
|
err = -1;
|
|
|
|
setlocale (LC_CTYPE, old_locale);
|
|
|
|
if (err == -1) {
|
|
free(temp);
|
|
return;
|
|
}
|
|
|
|
memmove (string, temp, len);
|
|
free (temp);
|
|
}
|
|
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// 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];
|
|
|
|
if (set_console_title) {
|
|
file_progress = (file_index + file_progress) / num_files;
|
|
sprintf (title, "%d%% (WavPack)", (int) ((file_progress * 100.0) + 0.5));
|
|
DoSetConsoleTitle (title);
|
|
}
|
|
}
|