/* hb_dict.c Copyright (c) 2003-2021 HandBrake Team This file is part of the HandBrake source code Homepage: . It may be used under the terms of the GNU General Public License v2. For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html */ #include #include #include "handbrake/handbrake.h" #include "handbrake/hb_dict.h" hb_value_type_t hb_value_type(const hb_value_t *value) { if (value == NULL) return HB_VALUE_TYPE_NULL; hb_value_type_t type = json_typeof(value); if (type == JSON_TRUE || type == JSON_FALSE) return HB_VALUE_TYPE_BOOL; return type; } int hb_value_is_number(const hb_value_t *value) { return json_is_number(value); } hb_value_t * hb_value_dup(const hb_value_t *value) { if (value == NULL) return NULL; return json_deep_copy(value); } hb_value_t* hb_value_incref(hb_value_t *value) { return json_incref(value); } void hb_value_decref(hb_value_t *value) { if (value == NULL) return; json_decref(value); } void hb_value_free(hb_value_t **_value) { hb_value_decref(*_value); *_value = NULL; } hb_value_t * hb_value_null() { return json_null(); } hb_value_t * hb_value_string(const char * value) { // json_string does not create a value for NULL strings. // So create JSON_NULL in this case if (value == NULL) return json_null(); return json_string(value); } hb_value_t * hb_value_int(json_int_t value) { return json_integer(value); } hb_value_t * hb_value_double(double value) { return json_real(value); } hb_value_t * hb_value_bool(int value) { return json_boolean(value); } hb_value_t * hb_value_json(const char *json) { json_error_t error; hb_value_t *val = json_loads(json, 0, &error); if (val == NULL) { hb_error("hb_value_json: Failed, error %s", error.text); } return val; } hb_value_t * hb_value_read_json(const char *path) { FILE * fp; json_error_t error; fp = hb_fopen(path, "r"); if (fp == NULL) { return NULL; } hb_value_t *val = json_loadf(fp, 0, &error); fclose(fp); return val; } static hb_value_t* xform_null(hb_value_type_t type) { switch (type) { default: case HB_VALUE_TYPE_NULL: return json_null(); case HB_VALUE_TYPE_BOOL: return json_false(); case HB_VALUE_TYPE_INT: return json_integer(0); case HB_VALUE_TYPE_DOUBLE: return json_real(0.0); case HB_VALUE_TYPE_STRING: return json_string(""); } } static hb_value_t* xform_bool(const hb_value_t *value, hb_value_type_t type) { json_int_t b = json_is_true(value); switch (type) { default: case HB_VALUE_TYPE_NULL: return json_null(); case HB_VALUE_TYPE_BOOL: return json_boolean(b); case HB_VALUE_TYPE_INT: return json_integer(b); case HB_VALUE_TYPE_DOUBLE: return json_real(b); case HB_VALUE_TYPE_STRING: { char *s = hb_strdup_printf("%"JSON_INTEGER_FORMAT, b); hb_value_t *v = json_string(s); free(s); return v; } } } static hb_value_t* xform_int(const hb_value_t *value, hb_value_type_t type) { json_int_t i = json_integer_value(value); switch (type) { default: case HB_VALUE_TYPE_NULL: return json_null(); case HB_VALUE_TYPE_BOOL: return json_boolean(i); case HB_VALUE_TYPE_INT: return json_integer(i); case HB_VALUE_TYPE_DOUBLE: return json_real(i); case HB_VALUE_TYPE_STRING: { char *s = hb_strdup_printf("%"JSON_INTEGER_FORMAT, i); hb_value_t *v = json_string(s); free(s); return v; } } } static hb_value_t* xform_double(const hb_value_t *value, hb_value_type_t type) { double d = json_real_value(value); switch (type) { default: case HB_VALUE_TYPE_NULL: return json_null(); case HB_VALUE_TYPE_BOOL: return json_boolean((int)d != 0); case HB_VALUE_TYPE_INT: return json_integer(d); case HB_VALUE_TYPE_DOUBLE: return json_real(d); case HB_VALUE_TYPE_STRING: { char *s = hb_strdup_printf("%g", d); hb_value_t *v = json_string(s); free(s); return v; } } } static hb_value_t* xform_string(const hb_value_t *value, hb_value_type_t type) { const char *s = json_string_value(value); switch (type) { default: case HB_VALUE_TYPE_NULL: { return json_null(); } case HB_VALUE_TYPE_BOOL: { if (!strcasecmp(s, "true") || !strcasecmp(s, "yes") || !strcasecmp(s, "1")) { return json_true(); } return json_false(); } case HB_VALUE_TYPE_INT: { return json_integer(strtoll(s, NULL, 0)); } case HB_VALUE_TYPE_DOUBLE: { return json_real(strtod(s, NULL)); } case HB_VALUE_TYPE_STRING: { return json_string(s); } } } static hb_value_t* xform_array(const hb_value_t *value, hb_value_type_t type) { hb_value_t *first = NULL; int count = hb_value_array_len(value); if (count > 0) first = hb_value_array_get(value, 0); switch (type) { default: case HB_VALUE_TYPE_NULL: case HB_VALUE_TYPE_BOOL: case HB_VALUE_TYPE_INT: case HB_VALUE_TYPE_DOUBLE: return hb_value_xform(first, type); case HB_VALUE_TYPE_STRING: { char *r = strdup(""); int ii; for (ii = 0; ii < count; ii++) { hb_value_t *v = hb_value_array_get(value, ii); hb_value_t *x = hb_value_xform(v, type); const char *s = hb_value_get_string(x); if (s != NULL) { char *tmp = r; r = hb_strdup_printf("%s%s,", tmp, s); free(tmp); } hb_value_free(&x); } int len = strlen(r); hb_value_t *v; if (len > 0) { // Removing trailing ',' r[len - 1] = 0; v = json_string(r); } else { free(r); r = NULL; v = json_null(); } free(r); return v; } } } static hb_value_t* xform_dict(const hb_value_t *dict, hb_value_type_t type) { hb_value_t *first = NULL; hb_dict_iter_t iter = hb_dict_iter_init(dict); if (iter != HB_DICT_ITER_DONE) first = hb_dict_iter_value(iter); switch (type) { default: case HB_VALUE_TYPE_NULL: case HB_VALUE_TYPE_BOOL: case HB_VALUE_TYPE_INT: case HB_VALUE_TYPE_DOUBLE: return hb_value_xform(first, type); case HB_VALUE_TYPE_STRING: { char *r = strdup(""); hb_dict_iter_t iter; for (iter = hb_dict_iter_init(dict); iter != HB_DICT_ITER_DONE; iter = hb_dict_iter_next(dict, iter)) { const char *k = hb_dict_iter_key(iter); hb_value_t *v = hb_dict_iter_value(iter); hb_value_t *x = hb_value_xform(v, type); const char *s = hb_value_get_string(x); char *tmp = r; r = hb_strdup_printf("%s%s%s%s:", r, k, s ? "=" : "", s ? s : ""); free(tmp); hb_value_free(&x); } int len = strlen(r); hb_value_t *v; if (len > 0) { // Removing trailing ':' r[len - 1] = 0; v = json_string(r); } else { free(r); r = NULL; v = json_null(); } free(r); return v; } } } hb_value_t* hb_value_xform(const hb_value_t *value, hb_value_type_t type) { hb_value_type_t src_type = hb_value_type(value); if (src_type == type && value != NULL) { json_incref((hb_value_t*)value); return (hb_value_t*)value; } switch (src_type) { default: case HB_VALUE_TYPE_NULL: { return xform_null(type); } case HB_VALUE_TYPE_BOOL: { return xform_bool(value, type); } case HB_VALUE_TYPE_INT: { return xform_int(value, type); } case HB_VALUE_TYPE_DOUBLE: { return xform_double(value, type); } case HB_VALUE_TYPE_STRING: { return xform_string(value, type); } case HB_VALUE_TYPE_ARRAY: { return xform_array(value, type); } case HB_VALUE_TYPE_DICT: { return xform_dict(value, type); } } } const char * hb_value_get_string(const hb_value_t *value) { if (hb_value_type(value) != HB_VALUE_TYPE_STRING) return NULL; return json_string_value(value); } json_int_t hb_value_get_int(const hb_value_t *value) { json_int_t result; hb_value_t *v = hb_value_xform(value, HB_VALUE_TYPE_INT); result = json_integer_value(v); json_decref(v); return result; } double hb_value_get_double(const hb_value_t *value) { double result; hb_value_t *v = hb_value_xform(value, HB_VALUE_TYPE_DOUBLE); result = json_real_value(v); json_decref(v); return result; } int hb_value_get_bool(const hb_value_t *value) { int result; hb_value_t *v = hb_value_xform(value, HB_VALUE_TYPE_BOOL); result = json_is_true(v); json_decref(v); return result; } char* hb_value_get_string_xform(const hb_value_t *value) { char *result; if (hb_value_type(value) == HB_VALUE_TYPE_NULL) return NULL; hb_value_t *v = hb_value_xform(value, HB_VALUE_TYPE_STRING); if (hb_value_type(v) == HB_VALUE_TYPE_NULL) return NULL; result = strdup(json_string_value(v)); json_decref(v); return result; } char * hb_value_get_json(const hb_value_t *value) { return json_dumps(value, JSON_INDENT(4) | JSON_SORT_KEYS); } int hb_value_write_file_json(hb_value_t *value, FILE *file) { return json_dumpf(value, file, JSON_INDENT(4) | JSON_SORT_KEYS); } int hb_value_write_json(hb_value_t *value, const char *path) { return json_dump_file(value, path, JSON_INDENT(4) | JSON_SORT_KEYS); } void hb_dict_free(hb_dict_t **_dict) { hb_value_free(_dict); } hb_dict_t * hb_dict_init() { return json_object(); } int hb_dict_elements(hb_dict_t * dict) { return json_object_size(dict); } static char * makelower(const char *key) { int ii, len = strlen(key); char * lower = malloc(len + 1); for (ii = 0; ii < len; ii++) { lower[ii] = tolower(key[ii]); } lower[ii] = '\0'; return lower; } void hb_dict_set(hb_dict_t * dict, const char *key, hb_value_t *value) { json_object_set_new(dict, key, value); } void hb_dict_merge(hb_dict_t * dict, hb_dict_t *value) { json_object_update(dict, value); } void hb_dict_case_set(hb_dict_t * dict, const char *key, hb_value_t *value) { char * lower = makelower(key); json_object_set_new(dict, lower, value); free(lower); } int hb_dict_remove(hb_dict_t * dict, const char * key) { int result; // First try case sensitive lookup result = json_object_del(dict, key) == 0; if (!result) { // If not found, try case insensitive lookup char * lower = makelower(key); result = json_object_del(dict, lower) == 0; free(lower); } return result; } hb_value_t * hb_dict_get(const hb_dict_t * dict, const char * key) { hb_value_t * result; // First try case sensitive lookup result = json_object_get(dict, key); if (result == NULL) { // If not found, try case insensitive lookup char * lower = makelower(key); result = json_object_get(dict, lower); free(lower); } return result; } // Dictionary extraction helpers // // Extract the given key from the dict and assign to dst *only* // if key is found in dict. Values are converted to the requested // data type. // // return: 1 - key is in dict // 0 - key is not in dict int hb_dict_extract_int(int *dst, const hb_dict_t * dict, const char * key) { if (dict == NULL || key == NULL || dst == NULL) { return 0; } hb_value_t *val = hb_dict_get(dict, key); if (val == NULL) { return 0; } *dst = hb_value_get_int(val); return 1; } int hb_dict_extract_double(double *dst, const hb_dict_t * dict, const char * key) { if (dict == NULL || key == NULL || dst == NULL) { return 0; } hb_value_t *val = hb_dict_get(dict, key); if (val == NULL) { return 0; } *dst = hb_value_get_double(val); return 1; } int hb_dict_extract_bool(int *dst, const hb_dict_t * dict, const char * key) { if (dict == NULL || key == NULL || dst == NULL) { return 0; } hb_value_t *val = hb_dict_get(dict, key); if (val == NULL) { return 0; } *dst = hb_value_get_bool(val); return 1; } int hb_dict_extract_string(char **dst, const hb_dict_t * dict, const char * key) { if (dict == NULL || key == NULL || dst == NULL) { return 0; } hb_value_t *val = hb_dict_get(dict, key); if (val == NULL) { return 0; } *dst = hb_value_get_string_xform(val); return 1; } int hb_dict_extract_rational(hb_rational_t *dst, const hb_dict_t * dict, const char * key) { if (dict == NULL || key == NULL || dst == NULL) { return 0; } hb_value_t *val = hb_dict_get(dict, key); if (val == NULL) { return 0; } if (hb_value_type(val) == HB_VALUE_TYPE_DICT) { hb_value_t * num_val = hb_dict_get(val, "Num"); if (num_val == NULL) { return 0; } hb_value_t * den_val = hb_dict_get(val, "Den"); if (den_val == NULL) { return 0; } dst->num = hb_value_get_int(num_val); dst->den = hb_value_get_int(den_val); return 1; } else if (hb_value_type(val) == HB_VALUE_TYPE_STRING) { const char * str = hb_value_get_string(val); char ** rational = hb_str_vsplit(str, '/'); if (rational[0] != NULL && rational[1] != NULL && isdigit(rational[0][0]) && isdigit(rational[1][0])) { char *num_end, *den_end; // found rational format value int num = strtol(rational[0], &num_end, 0); int den = strtol(rational[1], &den_end, 0); // confirm that the 2 components were entirely numbers if (num_end[0] == 0 && den_end[0] == 0) { dst->num = num; dst->den = den; hb_str_vfree(rational); return 1; } } hb_str_vfree(rational); } return 0; } int hb_dict_extract_int_array(int *dst, int count, const hb_dict_t * dict, const char * key) { if (dict == NULL || key == NULL || dst == NULL) { return 0; } hb_value_t *val = hb_dict_get(dict, key); if (hb_value_type(val) != HB_VALUE_TYPE_ARRAY) { return 0; } int len = hb_value_array_len(val); count = count < len ? count : len; int ii; for (ii = 0; ii < count; ii++) { dst[ii] = hb_value_get_int(hb_value_array_get(val, ii)); } return 1; } hb_dict_iter_t hb_dict_iter_init(const hb_dict_t *dict) { if (dict == NULL) return HB_DICT_ITER_DONE; return json_object_iter((hb_dict_t*)dict); } hb_dict_iter_t hb_dict_iter_next(const hb_dict_t *dict, hb_dict_iter_t iter) { return json_object_iter_next((hb_dict_t*)dict, iter); } const char * hb_dict_iter_key(const hb_dict_iter_t iter) { return json_object_iter_key(iter); } hb_value_t * hb_dict_iter_value(const hb_dict_iter_t iter) { return json_object_iter_value(iter); } int hb_dict_iter_next_ex(const hb_dict_t *dict, hb_dict_iter_t *iter, const char **key, hb_value_t **val) { if (*iter == NULL) return 0; if (key != NULL) *key = json_object_iter_key(*iter); if (val != NULL) *val = json_object_iter_value(*iter); *iter = json_object_iter_next((hb_dict_t*)dict, *iter); return 1; } hb_value_array_t* hb_value_array_init() { return json_array(); } void hb_value_array_clear(hb_value_array_t *array) { json_array_clear(array); } hb_value_t* hb_value_array_get(const hb_value_array_t *array, int index) { return json_array_get(array, index); } void hb_value_array_set(hb_value_array_t *array, int index, hb_value_t *value) { if (index < 0 || index >= json_array_size(array)) { hb_error("hb_value_array_set: invalid index %d size %zu", index, json_array_size(array)); return; } json_array_set_new(array, index, value); } void hb_value_array_insert(hb_value_array_t *array, int index, hb_value_t *value) { json_array_insert_new(array, index, value); } void hb_value_array_append(hb_value_array_t *array, hb_value_t *value) { json_array_append_new(array, value); } void hb_value_array_concat(hb_value_array_t *array, hb_value_t *value) { if (hb_value_type(value) == HB_VALUE_TYPE_ARRAY) { int ii; int len = hb_value_array_len(value); for (ii = 0; ii < len; ii++) { hb_value_t * val = hb_value_array_get(value, ii); json_array_append_new(array, hb_value_dup(val)); } } else { json_array_append_new(array, hb_value_dup(value)); } } void hb_value_array_remove(hb_value_array_t *array, int index) { json_array_remove(array, index); } void hb_value_array_copy(hb_value_array_t *dst, const hb_value_array_t *src, int count) { size_t len; int ii; // empty the first array if it is not already empty json_array_clear(dst); len = hb_value_array_len(src); count = MIN(count, len); for (ii = 0; ii < count; ii++) hb_value_array_append(dst, hb_value_dup(hb_value_array_get(src, ii))); } size_t hb_value_array_len(const hb_value_array_t *array) { return json_array_size(array); } hb_dict_t * hb_encopts_to_dict(const char * encopts, int encoder) { hb_dict_t * dict = NULL; if (encopts && *encopts) { char *cur_opt, *opts_start, *value; const char *name; dict = hb_dict_init(); if( !dict ) return NULL; cur_opt = opts_start = strdup(encopts); if (opts_start) { while (*cur_opt) { name = cur_opt; cur_opt += strcspn(cur_opt, ":"); if (*cur_opt) { *cur_opt = 0; cur_opt++; } value = strchr(name, '='); if (value) { *value = 0; value++; } // x264 has multiple names for some options if (encoder & HB_VCODEC_X264_MASK) name = hb_x264_encopt_name(name); #if HB_PROJECT_FEATURE_X265 // x265 has multiple names for some options if (encoder & HB_VCODEC_X265_MASK) name = hb_x265_encopt_name(name); #endif if (name != NULL) { hb_dict_set(dict, name, hb_value_string(value)); } } } free(opts_start); } return dict; } char * hb_dict_to_encopts(const hb_dict_t * dict) { return hb_value_get_string_xform(dict); }