Working Wavpack filter for compressing WAV filies.

Improved error handling of filter routines.
Improved verbose logging.
This commit is contained in:
Moinak Ghosh 2014-09-17 20:34:38 +05:30
parent fd087a8949
commit af39994a59
9 changed files with 382 additions and 57 deletions

View file

@ -64,15 +64,17 @@ ssize_t packpnm_filter(struct filter_info *fi, void *filter_private);
#ifdef _ENABLE_WAVPACK_
extern size_t wavpack_filter_encode(uchar_t *in_buf, size_t len, uchar_t **out_buf);
extern size_t wavpack_filter_decode(uchar_t *in_buf, size_t len, uchar_t **out_buf,
ssize_t out_len);
ssize_t wavpack_filter(struct filter_info *fi, void *filter_private);
#endif
void
add_filters_by_type(struct type_data *typetab, struct filter_flags *ff)
{
#ifndef _MPLV2_LICENSE_
struct scratch_buffer *sdat;
struct scratch_buffer *sdat = NULL;
int slot;
#ifndef _MPLV2_LICENSE_
if (ff->enable_packjpg) {
sdat = (struct scratch_buffer *)malloc(sizeof (struct scratch_buffer));
@ -95,6 +97,21 @@ add_filters_by_type(struct type_data *typetab, struct filter_flags *ff)
typetab[slot].filter_name = "packPNM";
}
#endif
#ifdef _ENABLE_WAVPACK_
if (ff->enable_wavpack) {
if (!sdat) {
sdat = (struct scratch_buffer *)malloc(sizeof (struct scratch_buffer));
sdat->in_buff = NULL;
sdat->in_bufflen = 0;
}
slot = TYPE_WAV >> 3;
typetab[slot].filter_private = sdat;
typetab[slot].filter_func = wavpack_filter;
typetab[slot].filter_name = "WavPack";
}
#endif
}
int
@ -279,13 +296,13 @@ packjpg_filter(struct filter_info *fi, void *filter_private)
out = NULL;
if ((len = packjpg_filter_process(mapbuf, in_size, &out)) == 0) {
/*
* If filter failed we write out the original data and indicate skip
* to continue the archive extraction.
* If filter failed we write out the original data and indicate a
* soft error to continue the archive extraction.
*/
free(out);
if (write_archive_data(fi->target_arc, mapbuf, len1, fi->block_size) < len1)
return (FILTER_RETURN_ERROR);
return (FILTER_RETURN_SKIP);
return (FILTER_RETURN_SOFT_ERROR);
}
rv = write_archive_data(fi->target_arc, out, len, fi->block_size);
free(out);
@ -382,13 +399,13 @@ packpnm_filter(struct filter_info *fi, void *filter_private)
out = NULL;
if ((len = packpnm_filter_process(mapbuf, in_size, &out)) == 0) {
/*
* If filter failed we write out the original data and indicate skip
* to continue the archive extraction.
* If filter failed we write out the original data and indicate a
* soft error to continue the archive extraction.
*/
free(out);
if (write_archive_data(fi->target_arc, mapbuf, len1, fi->block_size) < len1)
return (FILTER_RETURN_ERROR);
return (FILTER_RETURN_SKIP);
return (FILTER_RETURN_SOFT_ERROR);
}
rv = write_archive_data(fi->target_arc, out, len, fi->block_size);
free(out);
@ -413,7 +430,7 @@ wavpack_filter(struct filter_info *fi, void *filter_private)
if (fi->compressing) {
mapbuf = mmap(NULL, len, PROT_READ, MAP_SHARED, fi->fd, 0);
if (mapbuf == NULL) {
log_msg(LOG_ERR, 1, "Mmap failed in packPNM filter.");
log_msg(LOG_ERR, 1, "Mmap failed in WavPack filter.");
return (FILTER_RETURN_ERROR);
}
@ -475,7 +492,7 @@ wavpack_filter(struct filter_info *fi, void *filter_private)
}
munmap(mapbuf, len1);
in_size = LE64(len);
in_size = LE64(len1);
rv = archive_write_data(fi->target_arc, &in_size, 8);
if (rv != 8)
return (rv);
@ -488,15 +505,15 @@ wavpack_filter(struct filter_info *fi, void *filter_private)
* Decompression case.
*/
out = NULL;
if ((len = wavpack_filter_encode(mapbuf, in_size, &out)) == 0) {
if ((len = wavpack_filter_decode(mapbuf, len, &out, in_size)) == 0) {
/*
* If filter failed we write out the original data and indicate skip
* to continue the archive extraction.
* If filter failed we write out the original data and indicate a
* soft error to continue the archive extraction.
*/
free(out);
if (write_archive_data(fi->target_arc, mapbuf, len1, fi->block_size) < len1)
return (FILTER_RETURN_ERROR);
return (FILTER_RETURN_SKIP);
return (FILTER_RETURN_SOFT_ERROR);
}
rv = write_archive_data(fi->target_arc, out, len, fi->block_size);
free(out);

View file

@ -39,6 +39,7 @@ extern "C" {
#define FILTER_RETURN_SKIP (1)
#define FILTER_RETURN_ERROR (-1)
#define FILTER_RETURN_SOFT_ERROR (-2)
#define FILTER_XATTR_ENTRY "_._pc_filter_xattr"
struct filter_info {
@ -52,6 +53,7 @@ struct filter_info {
struct filter_flags {
int enable_packjpg;
int enable_wavpack;
};
typedef ssize_t (*filter_func_ptr)(struct filter_info *fi, void *filter_private);

View file

@ -1156,8 +1156,7 @@ archiver_thread_func(void *dat) {
} else {
archive_entry_set_size(entry, archive_entry_size(entry));
}
if (pctx->verbose)
log_msg(LOG_INFO, 0, "%5d/%5d %8" PRIu64 " %s", ctr, pctx->archive_members_count,
log_msg(LOG_VERBOSE, 0, "%5d/%5d %8" PRIu64 " %s", ctr, pctx->archive_members_count,
archive_entry_size(entry), name);
archive_entry_linkify(resolver, &entry, &spare_entry);
@ -1201,7 +1200,7 @@ start_archiver(pc_ctx_t *pctx) {
*/
static int
copy_data_out(struct archive *ar, struct archive *aw, struct archive_entry *entry,
int typ)
int typ, pc_ctx_t *pctx)
{
int64_t offset;
const void *buff;
@ -1219,7 +1218,18 @@ copy_data_out(struct archive *ar, struct archive *aw, struct archive_entry *entr
return (ARCHIVE_FATAL);
} else if (rv == FILTER_RETURN_SKIP) {
log_msg(LOG_WARN, 0, "Filter function failed for entry.");
log_msg(LOG_WARN, 0, "Filter function skipped.");
return (ARCHIVE_WARN);
} else if (rv == FILTER_RETURN_SOFT_ERROR) {
log_msg(LOG_WARN, 0, "Filter function failed for entry: %s.",
archive_entry_pathname(entry));
pctx->errored_count++;
if (pctx->err_paths_fd) {
fprintf(pctx->err_paths_fd, "%s,%s",
archive_entry_pathname(entry),
typetab[(typ >> 3)].filter_name);
}
return (ARCHIVE_WARN);
} else {
return (ARCHIVE_OK);
@ -1247,7 +1257,7 @@ copy_data_out(struct archive *ar, struct archive *aw, struct archive_entry *entr
static int
archive_extract_entry(struct archive *a, struct archive_entry *entry,
struct archive *ad, int typ)
struct archive *ad, int typ, pc_ctx_t *pctx)
{
int r, r2;
char *filter_name;
@ -1271,7 +1281,7 @@ archive_extract_entry(struct archive *a, struct archive_entry *entry,
archive_copy_error(a, ad);
} else if (!archive_entry_size_is_set(entry) || archive_entry_size(entry) > 0) {
/* Otherwise, pour data into the entry. */
r = copy_data_out(a, ad, entry, typ);
r = copy_data_out(a, ad, entry, typ, pctx);
}
r2 = archive_write_finish_entry(ad);
if (r2 < ARCHIVE_WARN)
@ -1375,6 +1385,11 @@ extractor_thread_func(void *dat) {
log_msg(LOG_ERR, 1, "Cannot change to dir: %s", pctx->to_filename);
goto done;
}
/*
* Open list file for pathnames that had filter errors (if any).
*/
pctx->err_paths_fd = fopen("filter_failures.txt", "w");
}
/*
@ -1431,7 +1446,7 @@ extractor_thread_func(void *dat) {
#endif
if (!pctx->list_mode) {
rv = archive_extract_entry(arc, entry, awd, typ);
rv = archive_extract_entry(arc, entry, awd, typ, pctx);
} else {
rv = archive_list_entry(arc, entry, typ);
}
@ -1439,8 +1454,8 @@ extractor_thread_func(void *dat) {
log_msg(LOG_WARN, 0, "%s: %s", archive_entry_pathname(entry),
archive_error_string(arc));
} else if (pctx->verbose) {
log_msg(LOG_INFO, 0, "%5d %8" PRIu64 " %s", ctr, archive_entry_size(entry),
} else {
log_msg(LOG_VERBOSE, 0, "%5d %8" PRIu64 " %s", ctr, archive_entry_size(entry),
archive_entry_pathname(entry));
}
@ -1452,12 +1467,26 @@ extractor_thread_func(void *dat) {
}
if (!pctx->list_mode) {
if (pctx->errored_count > 0) {
log_msg(LOG_WARN, 0, "WARN: %d pathnames failed filter decoding.");
if (pctx->err_paths_fd) {
fclose(pctx->err_paths_fd);
log_msg(LOG_WARN, 0, "Please see file filter_failures.txt.");
}
} else {
if (pctx->err_paths_fd) {
fclose(pctx->err_paths_fd);
(void) unlink("filter_failures.txt");
}
}
if (got_cwd) {
rv = chdir(cwd);
}
}
archive_read_free(arc);
archive_write_free(awd);
done:
return (NULL);
}

View file

@ -93,11 +93,11 @@ read_block(read_data *rdat, uchar_t *tgt, uint32_t len, uint32_t *numread)
numcopy = rdat->bufsize - rdat->bytes_read;
if (numcopy == 0)
return (1);
return (FALSE);
memcpy(tgt, rdat->buf + rdat->bytes_read, numcopy);
rdat->bytes_read += numcopy;
*numread = numcopy;
return (0);
return (TRUE);
}
/*
@ -116,12 +116,105 @@ read_block_ref(read_data *rdat, uchar_t **ref, uint32_t len, uint32_t *numread)
numcopy = rdat->bufsize - rdat->bytes_read;
if (numcopy == 0)
return (1);
return (FALSE);
*ref = rdat->buf + rdat->bytes_read;
rdat->bytes_read += numcopy;
*numread = numcopy;
return (TRUE);
}
/*
* Memory buffer I/O functions mirroring semantics of stdio, for Wavpack
* I/O ops structure.
*/
static int32_t
read_bytes(void *id, void *data, int32_t bcount)
{
read_data *rdat = (read_data *)id;
uint32_t numread;
if (!read_block(rdat, (uchar_t *)data, bcount, &numread)) {
return (0);
}
return (numread);
}
static uint32_t
get_pos(void *id)
{
read_data *rdat = (read_data *)id;
return (rdat->bytes_read);
}
static int
set_pos_abs(void *id, uint32_t pos)
{
read_data *rdat = (read_data *)id;
if (pos > rdat->bufsize)
pos = rdat->bufsize;
rdat->bytes_read = pos;
return (0);
}
static int
set_pos_rel(void *id, int32_t delta, int mode)
{
read_data *rdat = (read_data *)id;
int64_t br;
br = rdat->bytes_read;
if (mode == SEEK_SET) {
br = delta;
} else if (mode == SEEK_CUR) {
br += delta;
} else if (mode == SEEK_END) {
br = rdat->bufsize - delta;
} else {
errno = EINVAL;
return (-1);
}
if (br < 0) {
errno = EINVAL;
return (-1);
}
if (br > rdat->bufsize)
br = rdat->bufsize;
rdat->bytes_read = br;
return (0);
}
static int
push_back_byte(void *id, int c)
{
read_data *rdat = (read_data *)id;
if (rdat->bytes_read > 0) {
rdat->bytes_read--;
rdat->buf[rdat->bytes_read] = c;
return (c);
} else {
return (EOF);
}
}
static uint32_t
get_length(void *id)
{
read_data *rdat = (read_data *)id;
return (rdat->bufsize);
}
static int
can_seek(void *id)
{
return (TRUE);
}
static WavpackStreamReader memreader = {
read_bytes, get_pos, set_pos_abs, set_pos_rel, push_back_byte,
get_length, can_seek, write_block
};
#define INPUT_SAMPLES 65536
@ -140,7 +233,6 @@ pack_audio(WavpackContext *wpc, read_data *rdat)
WavpackPackInit(wpc);
bytes_per_sample = WavpackGetBytesPerSample (wpc) * WavpackGetNumChannels (wpc);
//input_buffer = malloc(input_samples * bytes_per_sample);
sample_buffer = malloc(input_samples * sizeof (int32_t) * WavpackGetNumChannels (wpc));
samples_remaining = WavpackGetNumSamples (wpc);
@ -212,7 +304,7 @@ pack_audio(WavpackContext *wpc, read_data *rdat)
/*
* Helper routine for wavpack. Higher level encoding interface adapted from
* pack_file() in cli/wavpack.c
* pack_file() in cli/wavpack.c and unpack_file() in cli/wvunpack.c
*/
size_t
wavpack_filter_encode(uchar_t *in_buf, size_t len, uchar_t **out_buf)
@ -233,14 +325,11 @@ wavpack_filter_encode(uchar_t *in_buf, size_t len, uchar_t **out_buf)
memset(&loc_config, 0, sizeof (loc_config));
adobe_mode = 0;
/*
* Default WavPack config.
*/
loc_config.flags |= CONFIG_FAST_FLAG;
*out_buf = (uchar_t *)malloc(len);
if (*out_buf == NULL)
if (*out_buf == NULL) {
log_msg(LOG_ERR, 1, "malloc failed.");
return (0);
}
wv_dat.buf = *out_buf;
wv_dat.bufsize = len;
@ -249,24 +338,34 @@ wavpack_filter_encode(uchar_t *in_buf, size_t len, uchar_t **out_buf)
rd_dat.buf = in_buf;
rd_dat.bufsize = len;
// read (and copy to output) initial RIFF form header
if (read_block(&rd_dat, (uchar_t *)&riff_chunk_header, sizeof (RiffChunkHeader), &bcount) ||
// Read (and copy to output) initial RIFF form header
if (!read_block(&rd_dat, (uchar_t *)&riff_chunk_header, sizeof (RiffChunkHeader), &bcount) ||
bcount != sizeof (RiffChunkHeader) || strncmp(riff_chunk_header.ckID, "RIFF", 4) ||
strncmp(riff_chunk_header.formType, "WAVE", 4)) {
WavpackCloseFile(wpc);
return (0);
}
if (!WavpackAddWrapper(wpc, &riff_chunk_header, sizeof (RiffChunkHeader))) {
WavpackCloseFile(wpc);
return (0);
}
// loop through all elements of the RIFF wav header
// (until the data chunk) and copy them to the output
//
while (1) {
if (read_block(&rd_dat, (uchar_t *)&chunk_header, sizeof (ChunkHeader), &bcount) ||
if (!read_block(&rd_dat, (uchar_t *)&chunk_header, sizeof (ChunkHeader), &bcount) ||
bcount != sizeof (ChunkHeader)) {
WavpackCloseFile(wpc);
return (0);
}
if (!WavpackAddWrapper(wpc, &chunk_header, sizeof (ChunkHeader))) {
WavpackCloseFile(wpc);
return (0);
}
WavpackLittleEndianToNative (&chunk_header, ChunkHeaderFormat);
// if it's the format chunk, we want to get some info out of there and
@ -281,6 +380,11 @@ wavpack_filter_encode(uchar_t *in_buf, size_t len, uchar_t **out_buf)
WavpackCloseFile(wpc);
return (0);
}
if (!WavpackAddWrapper(wpc, &WaveHeader, chunk_header.ckSize)) {
WavpackCloseFile(wpc);
return (0);
}
WavpackLittleEndianToNative (&WaveHeader, WaveHeaderFormat);
if (chunk_header.ckSize > 16 && WaveHeader.cbSize == 2)
@ -401,6 +505,167 @@ wavpack_filter_encode(uchar_t *in_buf, size_t len, uchar_t **out_buf)
return (wv_dat.bytes_written);
}
/*
* Reformat samples from longs in processor's native endian mode to
* little-endian data with (possibly) less than 4 bytes / sample.
*/
static unsigned char *
format_samples(int bps, unsigned char *dst, int32_t *src, uint32_t samcnt)
{
int32_t temp;
switch (bps) {
case 1:
while (samcnt--)
*dst++ = *src++ + 128;
break;
case 2:
while (samcnt--) {
*dst++ = (unsigned char) (temp = *src++);
*dst++ = (unsigned char) (temp >> 8);
}
break;
case 3:
while (samcnt--) {
*dst++ = (unsigned char) (temp = *src++);
*dst++ = (unsigned char) (temp >> 8);
*dst++ = (unsigned char) (temp >> 16);
}
break;
case 4:
while (samcnt--) {
*dst++ = (unsigned char) (temp = *src++);
*dst++ = (unsigned char) (temp >> 8);
*dst++ = (unsigned char) (temp >> 16);
*dst++ = (unsigned char) (temp >> 24);
}
break;
}
return dst;
}
size_t
wavpack_filter_decode(uchar_t *in_buf, size_t len, uchar_t **out_buf, ssize_t out_len)
{
write_data wr_dat;
read_data rd_dat;
WavpackContext *wpc;
int bytes_per_sample, num_channels, bps, result;
int32_t *temp_buffer;
uchar_t *output_ptr = NULL, *output_buffer = NULL;
uint32_t total_unpacked_samples = 0, output_buffer_size = 0;
char error[80];
rd_dat.buf = in_buf;
rd_dat.bufsize = len;
rd_dat.bytes_read = 0;
wpc = WavpackOpenFileInputEx(&memreader, &rd_dat, NULL, error, OPEN_WRAPPER, 0);
if (!wpc) {
log_msg(LOG_ERR, 0, error);
return (0);
}
num_channels = WavpackGetNumChannels(wpc);
bps = WavpackGetBytesPerSample(wpc);
bytes_per_sample = num_channels * bps;
*out_buf = (uchar_t *)malloc(out_len);
if (*out_buf == NULL) {
log_msg(LOG_ERR, 1, "malloc failed.");
return (0);
}
/*
* Must start with a wrapper.
*/
wr_dat.buf = *out_buf;
wr_dat.bufsize = len;
wr_dat.bytes_written = 0;
wr_dat.first_block_size = 0;
wr_dat.error = 0;
if (WavpackGetWrapperBytes(wpc)) {
if (!write_block(&wr_dat, WavpackGetWrapperData(wpc),
WavpackGetWrapperBytes(wpc))) {
WavpackFreeWrapper(wpc);
WavpackCloseFile(wpc);
log_msg(LOG_ERR, 0, "Wavpack: Header write failed.");
return (0);
}
WavpackFreeWrapper(wpc);
} else {
log_msg(LOG_ERR, 0, "Wavpack: RIFF wrapper size if zero. File corrupt?");
WavpackCloseFile(wpc);
return (0);
}
result = TRUE;
temp_buffer = malloc (4096L * num_channels * 4);
output_buffer_size = 1024 * 256;
output_buffer = malloc(output_buffer_size);
output_ptr = output_buffer;
while (result) {
uint32_t samples_to_unpack, samples_unpacked;
samples_to_unpack = (output_buffer_size -
(uint32_t)(output_ptr - output_buffer)) / bytes_per_sample;
if (samples_to_unpack > 4096)
samples_to_unpack = 4096;
samples_unpacked = WavpackUnpackSamples(wpc, temp_buffer, samples_to_unpack);
total_unpacked_samples += samples_unpacked;
if (samples_unpacked) {
output_ptr = format_samples(bps, output_ptr, temp_buffer,
samples_unpacked * num_channels);
}
if (!samples_unpacked || (output_buffer_size -
(output_ptr - output_buffer)) < (uint32_t)bytes_per_sample) {
if (!write_block(&wr_dat, output_buffer,
(uint32_t)(output_ptr - output_buffer))) {
if (temp_buffer)
free(temp_buffer);
WavpackCloseFile(wpc);
log_msg(LOG_ERR, 0, "Wavpack: Writing samples failed.");
return (0);
}
output_ptr = output_buffer;
}
if (!samples_unpacked)
break;
}
if (output_buffer)
free(output_buffer);
while (WavpackGetWrapperBytes(wpc)) {
if (!write_block(&wr_dat, WavpackGetWrapperData(wpc),
WavpackGetWrapperBytes(wpc))) {
if (temp_buffer)
free(temp_buffer);
WavpackCloseFile(wpc);
log_msg(LOG_ERR, 0, "Wavpack: Writing trailing data failed.");
return (0);
}
WavpackFreeWrapper(wpc);
WavpackUnpackSamples(wpc, temp_buffer, 1); // perhaps there's more RIFF info...
}
free(temp_buffer);
WavpackCloseFile(wpc);
return (wr_dat.bytes_written);
}
#ifdef __cplusplus
}
#endif

View file

@ -2854,6 +2854,7 @@ init_pc_context(pc_ctx_t *pctx, int argc, char *argv[])
strcpy(pctx->exec_name, pos);
pctx->advanced_opts = 0;
ff.enable_packjpg = 0;
ff.enable_wavpack = 0;
pthread_mutex_lock(&opt_parse);
while ((opt = getopt(argc, argv, "dc:s:l:pt:MCDGEe:w:LPS:B:Fk:avmKjxi")) != -1) {
@ -3010,7 +3011,7 @@ init_pc_context(pc_ctx_t *pctx, int argc, char *argv[])
break;
case 'v':
pctx->verbose = 1;
set_log_level(LOG_VERBOSE);
break;
case 'm':
@ -3024,6 +3025,7 @@ init_pc_context(pc_ctx_t *pctx, int argc, char *argv[])
case 'j':
pctx->advanced_opts = 1;
ff.enable_packjpg = 1;
ff.enable_wavpack = 1;
break;
case 'x':
@ -3142,9 +3144,10 @@ init_pc_context(pc_ctx_t *pctx, int argc, char *argv[])
}
/*
* Dispack and PackJPG are only valid when archiving files.
* Dispack, PackJPG and WavPack are only valid when archiving files.
*/
if ((pctx->dispack_preprocess || ff.enable_packjpg) && !pctx->archive_mode) {
if ((pctx->dispack_preprocess || ff.enable_packjpg || ff.enable_wavpack)
&& !pctx->archive_mode) {
log_msg(LOG_ERR, 0, "Dispack Executable Preprocessor and PackJPG are only valid when archiving.");
return (1);
}
@ -3305,9 +3308,13 @@ init_pc_context(pc_ctx_t *pctx, int argc, char *argv[])
* Selectively enable filters while archiving, depending on compression level.
*/
if (pctx->archive_mode) {
if (pctx->level > 10) ff.enable_packjpg = 1;
if (pctx->level > 10) {
ff.enable_packjpg = 1;
ff.enable_wavpack = 1;
}
init_filters(&ff);
pctx->enable_packjpg = ff.enable_packjpg;
pctx->enable_wavpack = ff.enable_wavpack;
if (pctx->level > 8) pctx->dispack_preprocess = 1;
}
@ -3345,7 +3352,9 @@ init_pc_context(pc_ctx_t *pctx, int argc, char *argv[])
* Enable all filters while decompressing. Obviously!
*/
ff.enable_packjpg = 1;
ff.enable_wavpack = 1;
pctx->enable_packjpg = 1;
pctx->enable_wavpack = 1;
init_filters(&ff);
}
pctx->inited = 1;

View file

@ -217,7 +217,6 @@ typedef struct pc_ctx {
int dispack_preprocess;
int encrypt_type;
int archive_mode;
int verbose;
int enable_archive_sort;
int pagesize;
int force_archive_perms;
@ -246,7 +245,10 @@ typedef struct pc_ctx {
int btype, ctype;
int min_chunk;
int enable_packjpg;
int enable_wavpack;
int list_mode;
FILE *err_paths_fd;
uint32_t errored_count;
unsigned int chunk_num;
uint64_t largest_chunk, smallest_chunk, avg_chunk;

View file

@ -61,7 +61,7 @@ static mach_timebase_info_data_t sTimebaseInfo;
processor_cap_t proc_info;
pthread_mutex_t f_mutex = PTHREAD_MUTEX_INITIALIZER;
static int cur_log_level = 2;
static int cur_log_level = LOG_INFO;
static log_dest_t ldest = {LOG_OUTPUT, LOG_INFO, NULL};
static char *f_name_list[512];
static int f_count = 512, f_inited = 0;

View file

@ -277,7 +277,7 @@ typedef enum {
/*
* Sub-types.
*/
#define NUM_SUB_TYPES 32
#define NUM_SUB_TYPES 33
TYPE_EXE32 = 8,
TYPE_JPEG = 16,
TYPE_MARKUP = 24,
@ -386,7 +386,8 @@ typedef enum {
typedef enum {
LOG_ERR,
LOG_WARN,
LOG_INFO
LOG_INFO,
LOG_VERBOSE
} log_level_t;
typedef void (*log_callback_ptr)(char *msg);