diff --git a/CMakeLists.txt b/CMakeLists.txt index ae6997f..f24b9d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.0.0) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") project(aptdec) -file(GLOB_RECURSE C_SOURCE_FILES src/*.c) +file(GLOB_RECURSE C_SOURCE_FILES src/*.c src/libs/*.c) add_executable(aptdec ${C_SOURCE_FILES}) add_compile_definitions(PALETTE_DIR="../palettes") diff --git a/src/image.c b/src/image.c index 3f14cd2..5c5f5bf 100644 --- a/src/image.c +++ b/src/image.c @@ -24,7 +24,7 @@ #include #include "offsets.h" -#include "reg.h" +#include "libs/reg.h" #include "image.h" #define REGORDER 3 diff --git a/src/libs/argparse.c b/src/libs/argparse.c new file mode 100644 index 0000000..ec86bba --- /dev/null +++ b/src/libs/argparse.c @@ -0,0 +1,384 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#include +#include +#include +#include +#include +#include "argparse.h" + +#define OPT_UNSET 1 +#define OPT_LONG (1 << 1) + +static const char * +prefix_skip(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) ? NULL : str + len; +} + +static int +prefix_cmp(const char *str, const char *prefix) +{ + for (;; str++, prefix++) + if (!*prefix) { + return 0; + } else if (*str != *prefix) { + return (unsigned char)*prefix - (unsigned char)*str; + } +} + +static void +argparse_error(struct argparse *self, const struct argparse_option *opt, + const char *reason, int flags) +{ + (void)self; + if (flags & OPT_LONG) { + fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason); + } else { + fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason); + } + exit(1); +} + +static int +argparse_getvalue(struct argparse *self, const struct argparse_option *opt, + int flags) +{ + const char *s = NULL; + if (!opt->value) + goto skipped; + switch (opt->type) { + case ARGPARSE_OPT_BOOLEAN: + if (flags & OPT_UNSET) { + *(int *)opt->value = *(int *)opt->value - 1; + } else { + *(int *)opt->value = *(int *)opt->value + 1; + } + if (*(int *)opt->value < 0) { + *(int *)opt->value = 0; + } + break; + case ARGPARSE_OPT_BIT: + if (flags & OPT_UNSET) { + *(int *)opt->value &= ~opt->data; + } else { + *(int *)opt->value |= opt->data; + } + break; + case ARGPARSE_OPT_STRING: + if (self->optvalue) { + *(const char **)opt->value = self->optvalue; + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(const char **)opt->value = *++self->argv; + } else { + argparse_error(self, opt, "requires a value", flags); + } + break; + case ARGPARSE_OPT_INTEGER: + errno = 0; + if (self->optvalue) { + *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno) + argparse_error(self, opt, strerror(errno), flags); + if (s[0] != '\0') + argparse_error(self, opt, "expects an integer value", flags); + break; + case ARGPARSE_OPT_FLOAT: + errno = 0; + if (self->optvalue) { + *(float *)opt->value = strtof(self->optvalue, (char **)&s); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(float *)opt->value = strtof(*++self->argv, (char **)&s); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno) + argparse_error(self, opt, strerror(errno), flags); + if (s[0] != '\0') + argparse_error(self, opt, "expects a numerical value", flags); + break; + default: + assert(0); + } + +skipped: + if (opt->callback) { + return opt->callback(self, opt); + } + + return 0; +} + +static void +argparse_options_check(const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + switch (options->type) { + case ARGPARSE_OPT_END: + case ARGPARSE_OPT_BOOLEAN: + case ARGPARSE_OPT_BIT: + case ARGPARSE_OPT_INTEGER: + case ARGPARSE_OPT_FLOAT: + case ARGPARSE_OPT_STRING: + case ARGPARSE_OPT_GROUP: + continue; + default: + fprintf(stderr, "wrong option type: %d", options->type); + break; + } + } +} + +static int +argparse_short_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + if (options->short_name == *self->optvalue) { + self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL; + return argparse_getvalue(self, options, 0); + } + } + return -2; +} + +static int +argparse_long_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + const char *rest; + int opt_flags = 0; + if (!options->long_name) + continue; + + rest = prefix_skip(self->argv[0] + 2, options->long_name); + if (!rest) { + // negation disabled? + if (options->flags & OPT_NONEG) { + continue; + } + // only OPT_BOOLEAN/OPT_BIT supports negation + if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != + ARGPARSE_OPT_BIT) { + continue; + } + + if (prefix_cmp(self->argv[0] + 2, "no-")) { + continue; + } + rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name); + if (!rest) + continue; + opt_flags |= OPT_UNSET; + } + if (*rest) { + if (*rest != '=') + continue; + self->optvalue = rest + 1; + } + return argparse_getvalue(self, options, opt_flags | OPT_LONG); + } + return -2; +} + +int +argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags) +{ + memset(self, 0, sizeof(*self)); + self->options = options; + self->usages = usages; + self->flags = flags; + self->description = NULL; + self->epilog = NULL; + return 0; +} + +void +argparse_describe(struct argparse *self, const char *description, + const char *epilog) +{ + self->description = description; + self->epilog = epilog; +} + +int +argparse_parse(struct argparse *self, int argc, const char **argv) +{ + self->argc = argc - 1; + self->argv = argv + 1; + self->out = argv; + + argparse_options_check(self->options); + + for (; self->argc; self->argc--, self->argv++) { + const char *arg = self->argv[0]; + if (arg[0] != '-' || !arg[1]) { + if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) { + goto end; + } + // if it's not option or is a single char '-', copy verbatim + self->out[self->cpidx++] = self->argv[0]; + continue; + } + // short option + if (arg[1] != '-') { + self->optvalue = arg + 1; + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + while (self->optvalue) { + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + } + continue; + } + // if '--' presents + if (!arg[2]) { + self->argc--; + self->argv++; + break; + } + // long option + switch (argparse_long_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + continue; + +unknown: + fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]); + argparse_usage(self); + exit(1); + } + +end: + memmove(self->out + self->cpidx, self->argv, + self->argc * sizeof(*self->out)); + self->out[self->cpidx + self->argc] = NULL; + + return self->cpidx + self->argc; +} + +void +argparse_usage(struct argparse *self) +{ + if (self->usages) { + fprintf(stdout, "Usage: %s\n", *self->usages++); + while (*self->usages && **self->usages) + fprintf(stdout, " or: %s\n", *self->usages++); + } else { + fprintf(stdout, "Usage:\n"); + } + + // print description + if (self->description) + fprintf(stdout, "%s\n", self->description); + + fputc('\n', stdout); + + const struct argparse_option *options; + + // figure out best width + size_t usage_opts_width = 0; + size_t len; + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + len = 0; + if ((options)->short_name) { + len += 2; + } + if ((options)->short_name && (options)->long_name) { + len += 2; // separator ", " + } + if ((options)->long_name) { + len += strlen((options)->long_name) + 2; + } + if (options->type == ARGPARSE_OPT_INTEGER) { + len += strlen("="); + } + if (options->type == ARGPARSE_OPT_FLOAT) { + len += strlen("="); + } else if (options->type == ARGPARSE_OPT_STRING) { + len += strlen("="); + } + len = (len + 3) - ((len + 3) & 3); + if (usage_opts_width < len) { + usage_opts_width = len; + } + } + usage_opts_width += 4; // 4 spaces prefix + + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + size_t pos = 0; + int pad = 0; + if (options->type == ARGPARSE_OPT_GROUP) { + fputc('\n', stdout); + fprintf(stdout, "%s", options->help); + fputc('\n', stdout); + continue; + } + pos = fprintf(stdout, " "); + if (options->short_name) { + pos += fprintf(stdout, "-%c", options->short_name); + } + if (options->long_name && options->short_name) { + pos += fprintf(stdout, ", "); + } + if (options->long_name) { + pos += fprintf(stdout, "--%s", options->long_name); + } + if (options->type == ARGPARSE_OPT_INTEGER) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_FLOAT) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_STRING) { + pos += fprintf(stdout, "="); + } + if (pos <= usage_opts_width) { + pad = usage_opts_width - pos; + } else { + fputc('\n', stdout); + pad = usage_opts_width; + } + fprintf(stdout, "%*s%s\n", pad + 2, "", options->help); + } + + // print epilog + if (self->epilog) + fprintf(stdout, "%s\n", self->epilog); +} + +int +argparse_help_cb(struct argparse *self, const struct argparse_option *option) +{ + (void)option; + argparse_usage(self); + exit(0); +} diff --git a/src/libs/argparse.h b/src/libs/argparse.h new file mode 100644 index 0000000..44ce835 --- /dev/null +++ b/src/libs/argparse.h @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#ifndef ARGPARSE_H +#define ARGPARSE_H + +/* For c++ compatibility */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct argparse; +struct argparse_option; + +typedef int argparse_callback (struct argparse *self, + const struct argparse_option *option); + +enum argparse_flag { + ARGPARSE_STOP_AT_NON_OPTION = 1, +}; + +enum argparse_option_type { + /* special */ + ARGPARSE_OPT_END, + ARGPARSE_OPT_GROUP, + /* options with no arguments */ + ARGPARSE_OPT_BOOLEAN, + ARGPARSE_OPT_BIT, + /* options with arguments (optional or required) */ + ARGPARSE_OPT_INTEGER, + ARGPARSE_OPT_FLOAT, + ARGPARSE_OPT_STRING, +}; + +enum argparse_option_flags { + OPT_NONEG = 1, /* disable negation */ +}; + +/** + * argparse option + * + * `type`: + * holds the type of the option, you must have an ARGPARSE_OPT_END last in your + * array. + * + * `short_name`: + * the character to use as a short option name, '\0' if none. + * + * `long_name`: + * the long option name, without the leading dash, NULL if none. + * + * `value`: + * stores pointer to the value to be filled. + * + * `help`: + * the short help message associated to what the option does. + * Must never be NULL (except for ARGPARSE_OPT_END). + * + * `callback`: + * function is called when corresponding argument is parsed. + * + * `data`: + * associated data. Callbacks can use it like they want. + * + * `flags`: + * option flags. + */ +struct argparse_option { + enum argparse_option_type type; + const char short_name; + const char *long_name; + void *value; + const char *help; + argparse_callback *callback; + intptr_t data; + int flags; +}; + +/** + * argpparse + */ +struct argparse { + // user supplied + const struct argparse_option *options; + const char *const *usages; + int flags; + const char *description; // a description after usage + const char *epilog; // a description at the end + // internal context + int argc; + const char **argv; + const char **out; + int cpidx; + const char *optvalue; // current option value +}; + +// built-in callbacks +int argparse_help_cb(struct argparse *self, + const struct argparse_option *option); + +// built-in option macros +#define OPT_END() { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 } +#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ } +#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ } +#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ } +#define OPT_FLOAT(...) { ARGPARSE_OPT_FLOAT, __VA_ARGS__ } +#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ } +#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 } +#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \ + "show this help message and exit", \ + argparse_help_cb, 0, OPT_NONEG) + +int argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags); +void argparse_describe(struct argparse *self, const char *description, + const char *epilog); +int argparse_parse(struct argparse *self, int argc, const char **argv); +void argparse_usage(struct argparse *self); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/median.c b/src/libs/median.c similarity index 100% rename from src/median.c rename to src/libs/median.c diff --git a/src/reg.c b/src/libs/reg.c similarity index 100% rename from src/reg.c rename to src/libs/reg.c diff --git a/src/reg.h b/src/libs/reg.h similarity index 100% rename from src/reg.h rename to src/libs/reg.h diff --git a/src/main.c b/src/main.c index 9a2f1e1..770770e 100644 --- a/src/main.c +++ b/src/main.c @@ -20,12 +20,12 @@ #include #include #include -#include #include #include #include #include #include +#include "libs/argparse.h" #include "offsets.h" @@ -46,68 +46,61 @@ int channels = 1; static int initsnd(char *filename); int getsample(float *sample, int nb); static int processAudio(char *filename, options_t *opts); -static void usage(void); int main(int argc, char **argv) { - fprintf(stderr, VERSION"\n"); + //fprintf(stderr, VERSION"\n"); // Check if there are actually any input files - if(argc == optind || argc == 1){ + /*if(argc == optind || argc == 1){ fprintf(stderr, "No input files provided.\n"); usage(); - } + }*/ options_t opts = { "r", "", 19, "", ".", 0, "", "", 1.0, 0 }; - // Parse arguments - int opt; - while ((opt = getopt(argc, argv, "o:m:d:i:s:e:p:g:k:r")) != EOF) { - switch (opt) { - case 'd': - opts.path = optarg; - break; - case 'm': - opts.map = optarg; - break; - case 'i': - opts.type = optarg; - break; - case 's': - opts.satnum = atoi(optarg); - if(opts.satnum < 15 || opts.satnum > 19){ - fprintf(stderr, "Invalid satellite number, it must be the range 15-19\n"); - exit(EPERM); - } - break; - case 'e': - opts.effects = optarg; - break; - case 'r': - opts.realtime = 1; - break; - case 'o': - opts.filename = optarg; - break; - case 'p': - opts.palette = optarg; - break; - case 'g': - opts.gamma = atof(optarg); - break; - case 'k': - opts.mapOffset = atoi(optarg); - break; - default: - usage(); - } - } + static const char *const usages[] = { + "aptdec [options] [[--] sources]", + "aptdec [sources]", + NULL, + }; - // Process the files - for (; optind < argc; optind++) { - processAudio(argv[optind], &opts); + struct argparse_option options[] = { + OPT_HELP(), + OPT_GROUP("Image options"), + OPT_STRING('i', "image", &opts.type, "set output image type (see the README for a list)", NULL, 0, 0), + OPT_STRING('e', "effect", &opts.effects, "add an effect (see the README for a list)", NULL, 0, 0), + OPT_FLOAT('g', "gamma", &opts.gamma, "gamma adjustment (1.0 = off)", NULL, 0, 0), + + OPT_GROUP("Satellite options"), + OPT_INTEGER('s', "satellite", &opts.satnum, "satellite ID, must be between 15 and 19", NULL, 0, 0), + + OPT_GROUP("Paths"), + OPT_STRING('p', "palette", &opts.palette, "path to a palette", NULL, 0, 0), + OPT_STRING('m', "map", &opts.map, "path to a WXtoImg map", NULL, 0, 0), + OPT_STRING('o', "filename", &opts.filename, "filename of the output image", NULL, 0, 0), + OPT_STRING('d', "output", &opts.path, "output directory (must exist first)", NULL, 0, 0), + + OPT_GROUP("Misc"), + OPT_BOOLEAN('r', "realtime", &opts.realtime, "decode in realtime", NULL, 0, 0), + OPT_INTEGER('k', "map-offset", &opts.mapOffset, "Map offset (in px, default 0)", NULL, 0, 0), + OPT_END(), + }; + + struct argparse argparse; + argparse_init(&argparse, options, usages, 0); + argparse_describe(&argparse, "\nA lightweight FOSS NOAA APT satellite imagery decoder.", "\nSee `README.md` for a full description of command line arguments and `LICENSE` for licensing conditions."); + argc = argparse_parse(&argparse, argc, argv); + + if(argc == 0){ + argparse_usage(&argparse); } - exit(0); + // Actually decode the files + for (int i = 0; i < argc; i++) { + processAudio(argv[i], &opts); + } + + return 0; } static int processAudio(char *filename, options_t *opts){ @@ -307,35 +300,3 @@ int getsample(float *sample, int nb) { return samples / channels; } } - -static void usage(void) { - fprintf(stderr, - "Aptdec [options] audio files ...\n" - "Options:\n" - " -i [r|a|b|t|m|p] Output image\n" - " r: Raw\n" - " a: Channel A\n" - " b: Channel B\n" - " t: Temperature\n" - " m: MCIR\n" - " p: Paletted image\n" - " -e [t|h|d|p|f|l] Effects\n" - " t: Crop telemetry\n" - " h: Histogram equalise\n" - " d: Denoise\n" - " p: Precipitation\n" - " f: Flip image\n" - " l: Linear equalise\n" - " c: Crop noise\n" - " -o Output filename\n" - " -d Image destination directory.\n" - " -s [15-19] Satellite number\n" - " -m Map file\n" - " -p Path to palette\n" - " -r Realtime decode\n" - " -g Gamma adjustment (1.0 = off)\n" - " -k Map offset (in px, default: 0)" - "\nRefer to the README for more infomation\n"); - - exit(EINVAL); -}