diff --git a/README.md b/README.md index c8e8fbd..2146988 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ # CaesiumCLT Caesium Command Line Tools -This is a WIP README. To be updated. +Usage: cclt [OPTION] INPUT... +Compress your pictures up to 90% without visible quality loss. + +Options: + -q set output file quality between [1-100], ignored for non-JPEGs + -e keeps EXIF info during compression + -o output to set folder + -l use lossless optimization + -s scale to value expressed as percentage (e.g. 20%) + -R if input is a folder scans subfolders too + -h display this help and exit + -v output version information and exit + diff --git a/bin/cclt b/bin/cclt index f118651..b33f737 100755 Binary files a/bin/cclt and b/bin/cclt differ diff --git a/compress.h b/compress.h index ced18e7..5ded694 100755 --- a/compress.h +++ b/compress.h @@ -1,18 +1,6 @@ #ifndef CCTL_COMPRESS #define CCTL_COMPRESS -#include - -typedef struct cclt_compress_parameters { - int quality; - int width; - int height; - int smoothing_factor; - J_COLOR_SPACE color_space; - J_DCT_METHOD dct_method; - int exif_copy; -} cclt_compress_parameters; - void cclt_compress(char* output_file, unsigned char* image_buffer); #endif diff --git a/lossless.c b/lossless.c index eff3194..9cb0e83 100755 --- a/lossless.c +++ b/lossless.c @@ -6,10 +6,9 @@ #include "lossless.h" int cclt_optimize(char* input_file, char* output_file) { - //File pointer for both input and output FILE* fp; - + //Those will hold the input/output structs struct jpeg_decompress_struct srcinfo; struct jpeg_compress_struct dstinfo; @@ -33,7 +32,7 @@ int cclt_optimize(char* input_file, char* output_file) { //Check for errors //TODO Use UNIX error messages if (fp == NULL) { - printf("Failed to open file \"%s\"\n", input_file); + printf("INPUT: Failed to open file \"%s\"\n", input_file); return -1; } @@ -56,11 +55,11 @@ int cclt_optimize(char* input_file, char* output_file) { fclose(fp); //Open the output one instead - fp = fopen(output_file, "w"); + fp = fopen(output_file, "w+"); //Check for errors //TODO Use UNIX error messages if (fp == NULL) { - printf("Failed to open file \"%s\"\n", output_file); + printf("OUTPUT: Failed to open file \"%s\"\n", output_file); return -2; } diff --git a/main.c b/main.c index de7550c..a07a55f 100755 --- a/main.c +++ b/main.c @@ -2,59 +2,88 @@ #include #include #include +#include +#include +#include #include "lossless.h" #include "compress.h" +#include "utils.h" -#define VERSION "1.9.9" -#define BUILD 20150508 +#define VERSION "1.9.9 BETA" +#define BUILD 20150509 /* PARAMETERS: -q quality -e exif - -o output file + -o output folder -v version -l lossless -s scale -h help -R recursive - */ cclt_compress_parameters parse_arguments(int argc, char* argv[]) { - cclt_compress_parameters parameters; + + //Initialize default params + cclt_compress_parameters parameters = initialize_compression_parameters(); int c; - char *qvalue = NULL; - char *evalue = NULL; - char *ovalue = NULL; - char *svalue = NULL; - while ((c = getopt (argc, argv, "q:ve:lo:s:hR")) != -1) { - switch (c) { - case 'v': - printf("CCLT - Caesium Command Line Tool - Version %s (Build: %d)\n", VERSION, BUILD); - break; - case '?': - if (optopt == 'q' || optopt == 'e' || optopt == 'o' || optopt == 's') { - //fprintf (stderr, "Option -%c requires an argument.\n", optopt); - //Arguments without values - exit(-1); - } - else if (isprint(optopt)) { - fprintf (stderr, "Unknown option `-%c'.\n", optopt); - } - else { - fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt); - } - break; - case 'q': - qvalue = optarg; - break; - case 'e': - evalue = optarg; - break; - default: - abort(); + while (optind < argc) { + if ((c = getopt (argc, argv, "q:velo:s:hR")) != -1) { + switch (c) { + case 'v': + printf("CCLT - Caesium Command Line Tool - Version %s (Build: %d)\n", VERSION, BUILD); + exit(0); + break; + case '?': + if (optopt == 'q' || optopt == 'o' || optopt == 's') { + fprintf (stderr, "Option -%c requires an argument.\n", optopt); + //Arguments without values + exit(-1); + } + else if (isprint(optopt)) { + fprintf (stderr, "Unknown option `-%c'.\n", optopt); + } + else { + fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt); + } + break; + case ':': + fprintf(stderr, "Parameter expected.\n"); + break; + case 'q': + parameters.quality = string_to_int(optarg); + break; + case 'e': + parameters.exif_copy = 1; + break; + case 'l': + parameters.lossless = 1; + break; + case 'o': + parameters.output_folder = optarg; + break; + case 's': + parameters.scaling_factor = string_to_int(optarg); + break; + case 'h': + print_help(); + break; + default: + abort(); + } + } else { + int i = 0; + parameters.input_files = (char**) malloc ((argc - optind) * sizeof (char*)); + while (optind < argc) { + parameters.input_files[i] = (char*) malloc (strlen(argv[optind]) * sizeof(char)); //TODO Necessary?? + parameters.input_files[i] = argv[optind]; + parameters.input_files_count = i + 1; + optind++; + i++; + } } } @@ -62,16 +91,70 @@ cclt_compress_parameters parse_arguments(int argc, char* argv[]) { } int main (int argc, char *argv[]) { - - cclt_compress_parameters parameters; - - //Check if there's at least one argument - if (argc <= 1) { - printf("CCLT requires at least one argument. Aborting.\n"); - return -1; + errno = 0; + //Parse arguments + cclt_compress_parameters pars = parse_arguments(argc, argv); + + + + //Either -l or -q must be set but not together + if ((pars.lossless == 1) ^ (pars.quality > 0) == 0) { + //Both or none are set + if (pars.lossless == 1 && pars.quality != -1) { + fprintf(stderr, "-l option can't be used with -q. Either use one or the other. Aborting.\n"); + exit(-1); + } else if (pars.lossless == 0 && pars.quality == -1) { + fprintf(stderr, "Either -l or -q must be set. Aborting.\n"); + exit(-2); + } + } else { + //One of them is set + //If -q is set check it is within the 1-100 range + if (!(pars.quality >= 1 && pars.quality <= 100) && pars.lossless == 0) { + fprintf(stderr, "Quality must be within a [1-100] range. Aborting.\n"); + exit(-3); + } } - - parameters = parse_arguments(argc, argv); - - return 0; + + //Check if you set the input files + if (pars.input_files_count == 0) { + fprintf(stderr, "No input files. Aborting.\n"); + exit(-9); + } + + //Check if there's a valid scaling factor + if (pars.scaling_factor <= 0) { + fprintf(stderr, "Scaling factor must be > 0. Aborting.\n"); + exit(-6); + } + + //Check if the output folder exists, otherwise create it + if (pars.output_folder == NULL) { + fprintf(stderr, "No -o option pointing to the destination folder. Aborting.\n"); + exit(-4); + } else { + if (mkdir(pars.output_folder, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) { + if (errno != EEXIST) { + perror("mkdir"); + exit(-5); + } + } + } + + + + for (int i = 0; i < pars.input_files_count; i++) { + + char* output_filename = pars.output_folder; + char* i_tmp = (char*) malloc (strlen(pars.input_files[i]) * sizeof(char)); + strcpy(i_tmp, pars.input_files[i]); + if (output_filename[strlen(pars.output_folder -1)] != '/') { + strcat(pars.output_folder, "/"); + } + + output_filename = strcat(pars.output_folder, i_tmp); + cclt_optimize(pars.input_files[i], output_filename); + } + + exit(0); } diff --git a/samples/in.jpg b/samples/in.jpg new file mode 100644 index 0000000..21c31bf Binary files /dev/null and b/samples/in.jpg differ diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..bf62c13 --- /dev/null +++ b/utils.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +cclt_compress_parameters initialize_compression_parameters() { + cclt_compress_parameters par; + + par.quality = 0; + par.width = 0; + par.height = 0; + par.smoothing_factor = 50; + par.scaling_factor = 100; + //par.color_space = NULL; TODO Must set? + //par.dct_method = NULL; TODO Must set? + par.output_folder = NULL; + par.exif_copy = 0; + par.lossless = 0; + par.input_files_count = 0; + //par.input_files = (char**) malloc (55 * sizeof(char)); + return par; +} + +int string_to_int(char* in_string) { + int value = 0; + char* endptr; + errno = 0; //Error checking + + value = strtol(in_string, &endptr, 0); //Convert the string + + //Check errors + if ((errno == ERANGE && (value == LONG_MAX || value == LONG_MIN)) + || (errno != 0 && value == 0)) { + perror("strtol"); + exit(-8); + } + + if (endptr == in_string) { + fprintf(stderr, "Parse error: No digits were found for -q option. Aborting.\n"); + exit(-7); + } + + return value; +} + +void print_help() { + fprintf(stdout, + "Usage: cclt [OPTION] INPUT...\n" + "Compress your pictures up to 90% without visible quality loss.\n\n" + + "Options:\n" + "\t-q\tset output file quality between [1-100], ignored for non-JPEGs\n" + "\t-e\tkeeps EXIF info during compression\n" + "\t-o\tcompress to custom folder\n" + "\t-l\tuse lossless optimization\n" + "\t-s\tscale to value, expressed as percentage (e.g. 20%)\n" + "\t-R\tif input is a folder, scan subfolders too\n" + "\t-h\tdisplay this help and exit\n" + "\t-v\toutput version information and exit\n"); + exit(0); +} + +void print_progress(int current, int max, char* message) { + fprintf(stdout, "\e[?25l"); + fprintf(stdout, "\r%s[%d\%]", message, current * 100 / max); + if (current == max) { + fprintf(stdout, "\e[?25h\n"); + } +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..478d371 --- /dev/null +++ b/utils.h @@ -0,0 +1,27 @@ +#ifndef CCTL_UTILS +#define CCTL_UTILS + +#include + +typedef struct cclt_compress_parameters { + int quality; + int width; + int height; + int smoothing_factor; + int scaling_factor; + char* output_folder; + J_COLOR_SPACE color_space; + J_DCT_METHOD dct_method; + int exif_copy; + int lossless; + char** input_files; + int input_files_count; +} cclt_compress_parameters; + +cclt_compress_parameters initialize_compression_parameters(); + +int string_to_int(char* in_string); +void print_help(); +void print_progress(int current, int max, char* message); + +#endif