/* json.c Copyright (c) 2003-2025 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 "handbrake/handbrake.h" #include "handbrake/hb_json.h" #include "libavutil/base64.h" /** * Convert an hb_state_t to a jansson dict * @param state - Pointer to hb_state_t to convert */ hb_dict_t* hb_state_to_dict( hb_state_t * state) { const char * state_s; hb_dict_t *dict = NULL; json_error_t error; switch (state->state) { case HB_STATE_IDLE: state_s = "IDLE"; break; case HB_STATE_SCANNING: state_s = "SCANNING"; break; case HB_STATE_SCANDONE: state_s = "SCANDONE"; break; case HB_STATE_WORKING: state_s = "WORKING"; break; case HB_STATE_PAUSED: state_s = "PAUSED"; break; case HB_STATE_SEARCHING: state_s = "SEARCHING"; break; case HB_STATE_WORKDONE: state_s = "WORKDONE"; break; case HB_STATE_MUXING: state_s = "MUXING"; break; default: state_s = "UNKNOWN"; break; } switch (state->state) { case HB_STATE_IDLE: dict = json_pack_ex(&error, 0, "{s:o}", "State", hb_value_string(state_s)); break; case HB_STATE_SCANNING: case HB_STATE_SCANDONE: dict = json_pack_ex(&error, 0, "{s:o, s{s:o, s:o, s:o, s:o, s:o, s:o}}", "State", hb_value_string(state_s), "Scanning", "SequenceID", hb_value_int(state->sequence_id), "Progress", hb_value_double(state->param.scanning.progress), "Preview", hb_value_int(state->param.scanning.preview_cur), "PreviewCount", hb_value_int(state->param.scanning.preview_count), "Title", hb_value_int(state->param.scanning.title_cur), "TitleCount", hb_value_int(state->param.scanning.title_count)); break; case HB_STATE_WORKING: case HB_STATE_PAUSED: case HB_STATE_SEARCHING: dict = json_pack_ex(&error, 0, "{s:o, s{s:o, s:o, s:o, s:o, s:o, s:o," " s:o, s:o, s:o, s:o, s:o, s:o}}", "State", hb_value_string(state_s), "Working", "Progress", hb_value_double(state->param.working.progress), "PassID", hb_value_int(state->param.working.pass_id), "Pass", hb_value_int(state->param.working.pass), "PassCount", hb_value_int(state->param.working.pass_count), "Rate", hb_value_double(state->param.working.rate_cur), "RateAvg", hb_value_double(state->param.working.rate_avg), "ETASeconds", hb_value_int(state->param.working.eta_seconds), "Hours", hb_value_int(state->param.working.hours), "Minutes", hb_value_int(state->param.working.minutes), "Paused", hb_value_int(state->param.working.paused), "Seconds", hb_value_int(state->param.working.seconds), "SequenceID", hb_value_int(state->sequence_id)); break; case HB_STATE_WORKDONE: dict = json_pack_ex(&error, 0, "{s:o, s{s:o, s:o}}", "State", hb_value_string(state_s), "WorkDone", "SequenceID", hb_value_int(state->sequence_id), "Error", hb_value_int(state->param.working.error)); break; case HB_STATE_MUXING: dict = json_pack_ex(&error, 0, "{s:o, s{s:o}}", "State", hb_value_string(state_s), "Muxing", "Progress", hb_value_double(state->param.muxing.progress)); break; default: dict = json_pack_ex(&error, 0, "{s:o}", "State", hb_value_string(state_s)); hb_error("hb_state_to_dict: unrecognized state %d", state->state); break; } if (dict == NULL) { hb_error("hb_state_to_dict, json pack failure: %s", error.text); } return dict; } hb_dict_t * hb_version_dict() { hb_dict_t * dict; json_error_t error; dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s{s:o, s:o, s:o}, s:o, s:o, s:o, s:o, s:o}", "Name", hb_value_string(HB_PROJECT_NAME), "Official", hb_value_bool(HB_PROJECT_REPO_OFFICIAL), "Type", hb_value_string(HB_PROJECT_REPO_TYPE), "Version", "Major", hb_value_int(HB_PROJECT_VERSION_MAJOR), "Minor", hb_value_int(HB_PROJECT_VERSION_MINOR), "Point", hb_value_int(HB_PROJECT_VERSION_POINT), "VersionString", hb_value_string(HB_PROJECT_VERSION), "RepoHash", hb_value_string(HB_PROJECT_REPO_HASH), "RepoDate", hb_value_string(HB_PROJECT_REPO_DATE), "System", hb_value_string(HB_PROJECT_HOST_SYSTEMF), "Arch", hb_value_string(HB_PROJECT_HOST_ARCH)); if (dict == NULL) { hb_error("hb_version_dict, json pack failure: %s", error.text); return NULL; } return dict; } /** * Get the current state of an hb instance as a json string * @param h - Pointer to an hb_handle_t hb instance */ char* hb_get_state_json( hb_handle_t * h ) { hb_state_t state; hb_get_state(h, &state); hb_dict_t *dict = hb_state_to_dict(&state); char *json_state = hb_value_get_json(dict); hb_value_free(&dict); return json_state; } hb_dict_t * hb_audio_attributes_to_dict(uint32_t attributes) { json_error_t error; hb_dict_t * dict; dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:o, s:o, s:o}", "Normal", hb_value_bool(attributes & HB_AUDIO_ATTR_NORMAL), "VisuallyImpaired", hb_value_bool(attributes & HB_AUDIO_ATTR_VISUALLY_IMPAIRED), "Commentary", hb_value_bool(attributes & HB_AUDIO_ATTR_COMMENTARY), "AltCommentary", hb_value_bool(attributes & HB_AUDIO_ATTR_ALT_COMMENTARY), "Secondary", hb_value_bool(attributes & HB_AUDIO_ATTR_SECONDARY), "Default", hb_value_bool(attributes & HB_AUDIO_ATTR_DEFAULT)); if (dict == NULL) { hb_error("hb_audio_attributes_to_dict, json pack failure: %s", error.text); } return dict; } hb_dict_t * hb_subtitle_attributes_to_dict(uint32_t attributes) { json_error_t error; hb_dict_t * dict; dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "Normal", hb_value_bool(attributes & HB_SUBTITLE_ATTR_NORMAL), "Large", hb_value_bool(attributes & HB_SUBTITLE_ATTR_LARGE), "Children", hb_value_bool(attributes & HB_SUBTITLE_ATTR_CHILDREN), "ClosedCaption", hb_value_bool(attributes & HB_SUBTITLE_ATTR_CC), "Forced", hb_value_bool(attributes & HB_SUBTITLE_ATTR_FORCED), "Commentary", hb_value_bool(attributes & HB_SUBTITLE_ATTR_COMMENTARY), "4By3", hb_value_bool(attributes & HB_SUBTITLE_ATTR_4_3), "Wide", hb_value_bool(attributes & HB_SUBTITLE_ATTR_WIDE), "Letterbox", hb_value_bool(attributes & HB_SUBTITLE_ATTR_LETTERBOX), "PanScan", hb_value_bool(attributes & HB_SUBTITLE_ATTR_PANSCAN), "Default", hb_value_bool(attributes & HB_SUBTITLE_ATTR_DEFAULT)); if (dict == NULL) { hb_error("hb_subtitle_attributes_to_dict, json pack failure: %s", error.text); } return dict; } static hb_dict_t* hb_title_to_dict_internal( hb_title_t *title ) { hb_dict_t *dict; json_error_t error; int ii; if (title == NULL) return NULL; int h_shift, v_shift; int chroma_available = hb_get_chroma_sub_sample(title->pix_fmt, &h_shift, &v_shift); char *chroma_subsampling = NULL; if (chroma_available == 0) { int h_value = 4 >> h_shift; int v_value = v_shift ? 0 : h_value; chroma_subsampling = hb_strdup_printf("4:%d:%d", h_value, v_value); } dict = json_pack_ex(&error, 0, "{" // Type, Path, Name, Index, KeepDuplicateTitles, Playlist, AngleCount "s:o, s:o, s:o, s:o, s:o, s:o, s:o," // Duration {Ticks, Hours, Minutes, Seconds} "s:{s:o, s:o, s:o, s:o}," // Geometry {Width, Height, PAR {Num, Den}, "s:{s:o, s:o, s:{s:o, s:o}}," // Crop[Top, Bottom, Left, Right]} "s:[oooo]," // LooseCrop[Top, Bottom, Left, Right]} "s:[oooo]," // Color {Format, Range, Primary, Transfer, Matrix, ChromaLocation, ChromaSubsampling, BitDepth} "s:{s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}," // FrameRate {Num, Den} "s:{s:o, s:o}," // InterlaceDetected, VideoCodec "s:o, s:o," // Metadata "s:o" "}", "Type", hb_value_int(title->type), "Path", hb_value_string(title->path), "Name", hb_value_string(title->name), "Index", hb_value_int(title->index), "KeepDuplicateTitles", hb_value_bool(title->keep_duplicate_titles), "Playlist", hb_value_int(title->playlist), "AngleCount", hb_value_int(title->angle_count), "Duration", "Ticks", hb_value_int(title->duration), "Hours", hb_value_int(title->hours), "Minutes", hb_value_int(title->minutes), "Seconds", hb_value_int(title->seconds), "Geometry", "Width", hb_value_int(title->geometry.width), "Height", hb_value_int(title->geometry.height), "PAR", "Num", hb_value_int(title->geometry.par.num), "Den", hb_value_int(title->geometry.par.den), "Crop", hb_value_int(title->crop[0]), hb_value_int(title->crop[1]), hb_value_int(title->crop[2]), hb_value_int(title->crop[3]), "LooseCrop", hb_value_int(title->loose_crop[0]), hb_value_int(title->loose_crop[1]), hb_value_int(title->loose_crop[2]), hb_value_int(title->loose_crop[3]), "Color", "Format", hb_value_int(title->pix_fmt), "Range", hb_value_int(title->color_range), "Primary", hb_value_int(title->color_prim), "Transfer", hb_value_int(title->color_transfer), "Matrix", hb_value_int(title->color_matrix), "ChromaLocation", hb_value_int(title->chroma_location), "ChromaSubsampling", hb_value_string(chroma_subsampling ? chroma_subsampling : "unknown"), "BitDepth", hb_value_int(hb_get_bit_depth(title->pix_fmt)), "FrameRate", "Num", hb_value_int(title->vrate.num), "Den", hb_value_int(title->vrate.den), "InterlaceDetected", hb_value_bool(title->detected_interlacing), "VideoCodec", hb_value_string(title->video_codec_name), "Metadata", hb_value_dup(title->metadata->dict) ); if (dict == NULL) { hb_error("hb_title_to_dict_internal, json pack failure: %s", error.text); free(chroma_subsampling); return NULL; } free(chroma_subsampling); // Mastering Display Color Volume metadata hb_dict_t *mastering_dict; if (title->mastering.has_primaries || title->mastering.has_luminance) { mastering_dict = json_pack_ex(&error, 0, "{" // DisplayPrimaries[3][2] "s:[[[ii],[ii]],[[ii],[ii]],[[ii],[ii]]]," // WhitePoint[2], "s:[[i,i],[i,i]]," // MinLuminance, MaxLuminance, HasPrimaries, HasLuminance "s:[i,i],s:[i,i],s:b,s:b" "}", "DisplayPrimaries", title->mastering.display_primaries[0][0].num, title->mastering.display_primaries[0][0].den, title->mastering.display_primaries[0][1].num, title->mastering.display_primaries[0][1].den, title->mastering.display_primaries[1][0].num, title->mastering.display_primaries[1][0].den, title->mastering.display_primaries[1][1].num, title->mastering.display_primaries[1][1].den, title->mastering.display_primaries[2][0].num, title->mastering.display_primaries[2][0].den, title->mastering.display_primaries[2][1].num, title->mastering.display_primaries[2][1].den, "WhitePoint", title->mastering.white_point[0].num, title->mastering.white_point[0].den, title->mastering.white_point[1].num, title->mastering.white_point[1].den, "MinLuminance", title->mastering.min_luminance.num, title->mastering.min_luminance.den, "MaxLuminance", title->mastering.max_luminance.num, title->mastering.max_luminance.den, "HasPrimaries", title->mastering.has_primaries, "HasLuminance", title->mastering.has_luminance ); hb_dict_set(dict, "MasteringDisplayColorVolume", mastering_dict); } // Content Light Level metadata hb_dict_t *coll_dict; if (title->coll.max_cll && title->coll.max_fall) { coll_dict = json_pack_ex(&error, 0, "{s:i, s:i}", "MaxCLL", title->coll.max_cll, "MaxFALL", title->coll.max_fall); hb_dict_set(dict, "ContentLightLevel", coll_dict); } // Dolby Vision Configuration Record hb_dict_t *dovi_dict; if (title->dovi.dv_profile) { dovi_dict = json_pack_ex(&error, 0, "{s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i}", "DVVersionMajor", title->dovi.dv_version_major, "DVVersionMinor", title->dovi.dv_version_minor, "DVProfile", title->dovi.dv_profile, "DVLevel", title->dovi.dv_level, "RPUPresentFlag", title->dovi.rpu_present_flag, "ELPresentFlag", title->dovi.el_present_flag, "BLPresentFlag", title->dovi.bl_present_flag, "BLSignalCompatibilityId", title->dovi.dv_bl_signal_compatibility_id); hb_dict_set(dict, "DolbyVisionConfigurationRecord", dovi_dict); } // HDR10+ Flag if (title->hdr_10_plus) { hb_dict_set(dict, "HDR10+", hb_value_int(title->hdr_10_plus)); } if (title->container_name != NULL) { hb_dict_set(dict, "Container", hb_value_string(title->container_name)); } // process chapter list hb_dict_t * chapter_list = hb_value_array_init(); for (ii = 0; ii < hb_list_count(title->list_chapter); ii++) { hb_dict_t *chapter_dict; char *name = ""; hb_chapter_t *chapter = hb_list_item(title->list_chapter, ii); if (chapter->title != NULL) name = chapter->title; chapter_dict = json_pack_ex(&error, 0, "{s:o, s:{s:o, s:o, s:o, s:o}}", "Name", hb_value_string(name), "Duration", "Ticks", hb_value_int(chapter->duration), "Hours", hb_value_int(chapter->hours), "Minutes", hb_value_int(chapter->minutes), "Seconds", hb_value_int(chapter->seconds) ); if (chapter_dict == NULL) { hb_error("hb_title_to_dict_internal, chapter, json pack failure: %s", error.text); return NULL; } hb_value_array_append(chapter_list, chapter_dict); } hb_dict_set(dict, "ChapterList", chapter_list); // process audio list hb_dict_t * audio_list = hb_value_array_init(); for (ii = 0; ii < hb_list_count(title->list_audio); ii++) { const char * codec_name; char channel_layout_name[256]; int channel_count, lfe_count; hb_dict_t * audio_dict, * attributes; hb_audio_t * audio = hb_list_item(title->list_audio, ii); codec_name = hb_audio_decoder_get_name(audio->config.in.codec, audio->config.in.codec_param); hb_layout_get_name(audio->config.in.ch_layout, channel_layout_name, sizeof(channel_layout_name)); channel_count = hb_layout_get_discrete_channel_count( audio->config.in.ch_layout); lfe_count = hb_layout_get_low_freq_channel_count( audio->config.in.ch_layout); attributes = hb_audio_attributes_to_dict(audio->config.lang.attributes); audio_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "TrackNumber", hb_value_int(ii + 1), "Description", hb_value_string(audio->config.lang.description), "Language", hb_value_string(audio->config.lang.simple), "LanguageCode", hb_value_string(audio->config.lang.iso639_2), "Attributes", attributes, "Codec", hb_value_int(audio->config.in.codec), "CodecParam", hb_value_int(audio->config.in.codec_param), "CodecName", hb_value_string(codec_name), "SampleRate", hb_value_int(audio->config.in.samplerate), "BitRate", hb_value_int(audio->config.in.bitrate), "ChannelLayout", hb_value_string(channel_layout_name), "ChannelCount", hb_value_int(channel_count), "LFECount", hb_value_int(lfe_count)); if (audio_dict == NULL) { hb_error("hb_title_to_dict_internal, audio, json pack failure: %s", error.text); return NULL; } if (audio->config.in.name != NULL) { hb_dict_set_string(audio_dict, "Name", audio->config.in.name); } hb_value_array_append(audio_list, audio_dict); } hb_dict_set(dict, "AudioList", audio_list); // process subtitle list hb_value_array_t * subtitle_list = hb_value_array_init(); for (ii = 0; ii < hb_list_count(title->list_subtitle); ii++) { const char * format; hb_dict_t * subtitle_dict, * attributes; hb_subtitle_t * subtitle = hb_list_item(title->list_subtitle, ii); format = subtitle->format == PICTURESUB ? "bitmap" : "text"; attributes = hb_subtitle_attributes_to_dict(subtitle->attributes); subtitle_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "TrackNumber", hb_value_int(ii + 1), "Format", hb_value_string(format), "Source", hb_value_int(subtitle->source), "SourceName", hb_value_string(hb_subsource_name(subtitle->source)), "Attributes", attributes, "Language", hb_value_string(subtitle->lang), "LanguageCode", hb_value_string(subtitle->iso639_2)); if (subtitle_dict == NULL) { hb_error("hb_title_to_dict_internal, subtitle, json pack failure: %s", error.text); return NULL; } if (subtitle->name != NULL) { hb_dict_set_string(subtitle_dict, "Name", subtitle->name); } hb_value_array_append(subtitle_list, subtitle_dict); } hb_dict_set(dict, "SubtitleList", subtitle_list); // process cover arts if (title->metadata && title->metadata->list_coverart) { hb_value_array_t *art_array = hb_value_array_init(); for (ii = 0; ii < hb_list_count(title->metadata->list_coverart); ii++) { hb_coverart_t *art = hb_list_item(title->metadata->list_coverart, ii); hb_dict_t *coverart_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o}", "ID", hb_value_int(ii), "Name", hb_value_string(art->name), "Type", hb_value_int(art->type)); if (coverart_dict) { hb_value_array_append(art_array, coverart_dict); } } hb_dict_set(dict, "CoverArts", art_array); } return dict; } /** * Convert an hb_title_t to a jansson dict * @param title - Pointer to the hb_title_t to convert */ hb_dict_t* hb_title_to_dict( hb_handle_t *h, int title_index ) { hb_title_t *title = hb_find_title_by_index(h, title_index); return hb_title_to_dict_internal(title); } /** * Convert an hb_title_set_t to a jansson dict * @param title - Pointer to the hb_title_set_t to convert */ hb_dict_t* hb_title_set_to_dict( const hb_title_set_t * title_set ) { hb_dict_t *dict; json_error_t error; int ii; dict = json_pack_ex(&error, 0, "{s:o, s:[]}", "MainFeature", hb_value_int(title_set->feature), "TitleList"); // process title list hb_dict_t *title_list = hb_dict_get(dict, "TitleList"); for (ii = 0; ii < hb_list_count(title_set->list_title); ii++) { hb_title_t *title = hb_list_item(title_set->list_title, ii); hb_dict_t *title_dict = hb_title_to_dict_internal(title); hb_value_array_append(title_list, title_dict); } return dict; } /** * Convert an hb_title_t to a json string * @param title - Pointer to hb_title_t to convert */ char* hb_title_to_json( hb_handle_t *h, int title_index ) { hb_dict_t *dict = hb_title_to_dict(h, title_index); if (dict == NULL) return NULL; char *json_title = hb_value_get_json(dict); hb_value_free(&dict); return json_title; } /** * Get the current title set of an hb instance as a json string * @param h - Pointer to hb_handle_t hb instance */ char* hb_get_title_set_json( hb_handle_t * h ) { hb_dict_t *dict = hb_title_set_to_dict(hb_get_title_set(h)); char *json_title_set = hb_value_get_json(dict); hb_value_free(&dict); return json_title_set; } /** * Convert an hb_job_t to an hb_dict_t * @param job - Pointer to the hb_job_t to convert */ hb_dict_t* hb_job_to_dict( const hb_job_t * job ) { hb_dict_t * dict; json_error_t error; int subtitle_search_burn; int ii; if (job == NULL || job->title == NULL) return NULL; // Assumes that the UI has reduced geometry settings to only the // necessary PAR value subtitle_search_burn = job->select_subtitle_config.dest == RENDERSUB; dict = json_pack_ex(&error, 0, "{" // SequenceID "s:o," // Destination {Mux, InlineParameterSets, AlignAVStart, // ChapterMarkers, ChapterList} "s:{s:o, s:o, s:o, s:o, s:[]}," // Source {Path, Title, Angle, HWDecode, KeepDuplicateTitles} "s:{s:o, s:o, s:o, s:o, s:o}," // PAR {Num, Den} "s:{s:o, s:o}," // Video {Encoder, HardwareDecode, AdapterIndex, AsyncDepth} "s:{s:o, s:o, s:o, s:o}," // Audio {CopyMask, FallbackEncoder, AudioList []} "s:{s:[], s:o, s:[]}," // Subtitles {Search {Enable, Forced, Default, Burn}, SubtitleList []} "s:{s:{s:o, s:o, s:o, s:o}, s:[]}," // Metadata "s:o," // Filters {FilterList []} "s:{s:[]}" "}", "SequenceID", hb_value_int(job->sequence_id), "Destination", "Mux", hb_value_int(job->mux), "InlineParameterSets", hb_value_bool(job->inline_parameter_sets), "AlignAVStart", hb_value_bool(job->align_av_start), "ChapterMarkers", hb_value_bool(job->chapter_markers), "ChapterList", "Source", "Path", hb_value_string(job->title->path), "Title", hb_value_int(job->title->index), "Angle", hb_value_int(job->angle), "HWDecode", hb_value_int(job->hw_decode), "KeepDuplicateTitles", hb_value_bool(job->keep_duplicate_titles), "PAR", "Num", hb_value_int(job->par.num), "Den", hb_value_int(job->par.den), "Video", "Encoder", hb_value_int(job->vcodec), "HardwareDecode", hb_value_int(job->hw_decode), "AdapterIndex", hb_value_int(job->hw_device_index), "AsyncDepth", hb_value_int(job->hw_device_async_depth), "Audio", "CopyMask", "FallbackEncoder", hb_value_int(job->acodec_fallback), "AudioList", "Subtitle", "Search", "Enable", hb_value_bool(job->indepth_scan), "Forced", hb_value_bool(job->select_subtitle_config.force), "Default", hb_value_bool(job->select_subtitle_config.default_track), "Burn", hb_value_bool(subtitle_search_burn), "SubtitleList", "Metadata", hb_value_dup(job->metadata->dict), "Filters", "FilterList" ); if (dict == NULL) { hb_error("hb_job_to_dict, json pack failure: %s", error.text); return NULL; } hb_dict_t *dest_dict = hb_dict_get(dict, "Destination"); if (job->file != NULL) { hb_dict_set(dest_dict, "File", hb_value_string(job->file)); } if (job->mux) { hb_dict_t *options_dict; options_dict = json_pack_ex(&error, 0, "{s:o, s:o}", "Optimize", hb_value_bool(job->optimize), "IpodAtom", hb_value_bool(job->ipod_atom)); hb_dict_set(dest_dict, "Options", options_dict); } hb_dict_t *source_dict = hb_dict_get(dict, "Source"); hb_dict_t *range_dict; if (job->start_at_preview > 0) { range_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:o}", "Type", hb_value_string("preview"), "Start", hb_value_int(job->start_at_preview), "End", hb_value_int(job->pts_to_stop), "SeekPoints", hb_value_int(job->seek_points)); } else if (job->pts_to_start != 0 || job->pts_to_stop != 0) { range_dict = hb_dict_init(); hb_dict_set(range_dict, "Type", hb_value_string("time")); if (job->pts_to_start > 0) { hb_dict_set(range_dict, "Start", hb_value_int(job->pts_to_start)); } if (job->pts_to_stop > 0) { hb_dict_set(range_dict, "End", hb_value_int(job->pts_to_start + job->pts_to_stop)); } } else if (job->frame_to_start != 0 || job->frame_to_stop != 0) { range_dict = hb_dict_init(); hb_dict_set(range_dict, "Type", hb_value_string("frame")); if (job->frame_to_start > 0) { hb_dict_set(range_dict, "Start", hb_value_int(job->frame_to_start)); } if (job->frame_to_stop > 0) { hb_dict_set(range_dict, "End", hb_value_int(job->frame_to_start + job->frame_to_stop - 1)); } } else { range_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o}", "Type", hb_value_string("chapter"), "Start", hb_value_int(job->chapter_start), "End", hb_value_int(job->chapter_end)); } hb_dict_set(source_dict, "Range", range_dict); hb_dict_t *video_dict = hb_dict_get(dict, "Video"); hb_dict_set(video_dict, "ColorInputFormat", hb_value_int(job->input_pix_fmt)); hb_dict_set(video_dict, "ColorOutputFormat", hb_value_int(job->output_pix_fmt)); hb_dict_set(video_dict, "ColorRange", hb_value_int(job->color_range)); hb_dict_set(video_dict, "ColorPrimaries", hb_value_int(job->color_prim)); hb_dict_set(video_dict, "ColorTransfer", hb_value_int(job->color_transfer)); hb_dict_set(video_dict, "ColorMatrix", hb_value_int(job->color_matrix)); hb_dict_set(video_dict, "ChromaLocation", hb_value_int(job->chroma_location)); if (job->color_prim_override != HB_COLR_PRI_UNSET) { hb_dict_set(video_dict, "ColorPrimariesOverride", hb_value_int(job->color_prim_override)); } if (job->color_transfer_override != HB_COLR_TRA_UNSET) { hb_dict_set(video_dict, "ColorTransferOverride", hb_value_int(job->color_transfer_override)); } if (job->color_matrix_override != HB_COLR_MAT_UNSET) { hb_dict_set(video_dict, "ColorMatrixOverride", hb_value_int(job->color_matrix_override)); } // Mastering Display Color Volume metadata hb_dict_t *mastering_dict; if (job->mastering.has_primaries || job->mastering.has_luminance) { mastering_dict = json_pack_ex(&error, 0, "{" // DisplayPrimaries[3][2] "s:[[[ii],[ii]],[[ii],[ii]],[[ii],[ii]]]," // WhitePoint[2], "s:[[i,i],[i,i]]," // MinLuminance, MaxLuminance, HasPrimaries, HasLuminance "s:[i,i],s:[i,i],s:b,s:b" "}", "DisplayPrimaries", job->mastering.display_primaries[0][0].num, job->mastering.display_primaries[0][0].den, job->mastering.display_primaries[0][1].num, job->mastering.display_primaries[0][1].den, job->mastering.display_primaries[1][0].num, job->mastering.display_primaries[1][0].den, job->mastering.display_primaries[1][1].num, job->mastering.display_primaries[1][1].den, job->mastering.display_primaries[2][0].num, job->mastering.display_primaries[2][0].den, job->mastering.display_primaries[2][1].num, job->mastering.display_primaries[2][1].den, "WhitePoint", job->mastering.white_point[0].num, job->mastering.white_point[0].den, job->mastering.white_point[1].num, job->mastering.white_point[1].den, "MinLuminance", job->mastering.min_luminance.num, job->mastering.min_luminance.den, "MaxLuminance", job->mastering.max_luminance.num, job->mastering.max_luminance.den, "HasPrimaries", job->mastering.has_primaries, "HasLuminance", job->mastering.has_luminance ); hb_dict_set(video_dict, "MasteringDisplayColorVolume", mastering_dict); } // Content Light Level metadata hb_dict_t *coll_dict; if (job->coll.max_cll && job->coll.max_fall) { coll_dict = json_pack_ex(&error, 0, "{s:i, s:i}", "MaxCLL", job->coll.max_cll, "MaxFALL", job->coll.max_fall); hb_dict_set(video_dict, "ContentLightLevel", coll_dict); } // Dolby Vision Configuration Record hb_dict_t *dovi_dict; if (job->dovi.dv_profile) { dovi_dict = json_pack_ex(&error, 0, "{s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i}", "DVVersionMajor", job->dovi.dv_version_major, "DVVersionMinor", job->dovi.dv_version_minor, "DVProfile", job->dovi.dv_profile, "DVLevel", job->dovi.dv_level, "RPUPresentFlag", job->dovi.rpu_present_flag, "ELPresentFlag", job->dovi.el_present_flag, "BLPresentFlag", job->dovi.bl_present_flag, "BLSignalCompatibilityId", job->dovi.dv_bl_signal_compatibility_id); hb_dict_set(video_dict, "DolbyVisionConfigurationRecord", dovi_dict); } if (job->vquality > HB_INVALID_VIDEO_QUALITY) { hb_dict_set(video_dict, "Quality", hb_value_double(job->vquality)); } else { hb_dict_set(video_dict, "Bitrate", hb_value_int(job->vbitrate)); hb_dict_set(video_dict, "MultiPass", hb_value_bool(job->multipass)); hb_dict_set(video_dict, "Turbo", hb_value_bool(job->fastanalysispass)); } hb_dict_set(video_dict, "PasshtruHDRDynamicMetadata", hb_value_int(job->passthru_dynamic_hdr_metadata)); if (job->encoder_preset != NULL) { hb_dict_set(video_dict, "Preset", hb_value_string(job->encoder_preset)); } if (job->encoder_tune != NULL) { hb_dict_set(video_dict, "Tune", hb_value_string(job->encoder_tune)); } if (job->encoder_profile != NULL) { hb_dict_set(video_dict, "Profile", hb_value_string(job->encoder_profile)); } if (job->encoder_level != NULL) { hb_dict_set(video_dict, "Level", hb_value_string(job->encoder_level)); } if (job->encoder_options != NULL) { hb_dict_set(video_dict, "Options", hb_value_string(job->encoder_options)); } // process chapter list hb_dict_t *chapter_list = hb_dict_get(dest_dict, "ChapterList"); for (ii = 0; ii < hb_list_count(job->list_chapter); ii++) { hb_dict_t *chapter_dict; char *name = ""; hb_chapter_t *chapter = hb_list_item(job->list_chapter, ii); if (chapter->title != NULL) name = chapter->title; chapter_dict = json_pack_ex(&error, 0, "{s:o, s:{s:o, s:o, s:o, s:o}}", "Name", hb_value_string(name), "Duration", "Ticks", hb_value_int(chapter->duration), "Hours", hb_value_int(chapter->hours), "Minutes", hb_value_int(chapter->minutes), "Seconds", hb_value_int(chapter->seconds) ); hb_value_array_append(chapter_list, chapter_dict); } // process filter list hb_dict_t *filters_dict = hb_dict_get(dict, "Filters"); hb_value_array_t *filter_list = hb_dict_get(filters_dict, "FilterList"); for (ii = 0; ii < hb_list_count(job->list_filter); ii++) { hb_dict_t *filter_dict; hb_filter_object_t *filter = hb_list_item(job->list_filter, ii); filter_dict = json_pack_ex(&error, 0, "{s:o}", "ID", hb_value_int(filter->id)); if (filter->settings != NULL) { hb_dict_set(filter_dict, "Settings", hb_value_dup(filter->settings)); } hb_value_array_append(filter_list, filter_dict); } hb_dict_t *audios_dict = hb_dict_get(dict, "Audio"); // Construct audio CopyMask hb_value_array_t *copy_mask = hb_dict_get(audios_dict, "CopyMask"); int acodec; for (acodec = 1; acodec != HB_ACODEC_PASS_FLAG; acodec <<= 1) { if (acodec & job->acodec_copy_mask) { const char *name; name = hb_audio_encoder_get_name(acodec | HB_ACODEC_PASS_FLAG); if (name != NULL) { hb_value_t *val = hb_value_string(name); hb_value_array_append(copy_mask, val); } } } // process audio list hb_dict_t *audio_list = hb_dict_get(audios_dict, "AudioList"); for (ii = 0; ii < hb_list_count(job->list_audio); ii++) { hb_dict_t *audio_dict; hb_audio_t *audio = hb_list_item(job->list_audio, ii); audio_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "Track", hb_value_int(audio->config.index), "Encoder", hb_value_int(audio->config.out.codec), "Gain", hb_value_double(audio->config.out.gain), "DRC", hb_value_double(audio->config.out.dynamic_range_compression), "Mixdown", hb_value_int(audio->config.out.mixdown), "NormalizeMixLevel", hb_value_bool(audio->config.out.normalize_mix_level), "DitherMethod", hb_value_int(audio->config.out.dither_method), "Samplerate", hb_value_int(audio->config.out.samplerate), "Bitrate", hb_value_int(audio->config.out.bitrate), "Quality", hb_value_double(audio->config.out.quality), "CompressionLevel", hb_value_double(audio->config.out.compression_level)); if (audio->config.out.name != NULL) { hb_dict_set_string(audio_dict, "Name", audio->config.out.name); } hb_value_array_append(audio_list, audio_dict); } // process subtitle list hb_dict_t *subtitles_dict = hb_dict_get(dict, "Subtitle"); if (job->select_subtitle_config.external_filename != NULL) { hb_dict_t *search = hb_dict_get(subtitles_dict, "Search"); hb_dict_set_string(search, "ExternalFilename", job->select_subtitle_config.external_filename); } hb_dict_t *subtitle_list = hb_dict_get(subtitles_dict, "SubtitleList"); for (ii = 0; ii < hb_list_count(job->list_subtitle); ii++) { hb_dict_t *subtitle_dict; hb_subtitle_t *subtitle = hb_list_item(job->list_subtitle, ii); if (subtitle->source == IMPORTSRT || subtitle->source == IMPORTSSA) { subtitle_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:{s:o, s:o, s:o}}", "Default", hb_value_bool(subtitle->config.default_track), "Burn", hb_value_bool(subtitle->config.dest == RENDERSUB), "Offset", hb_value_int(subtitle->config.offset), "Import", "Format", hb_value_string(subtitle->source == IMPORTSRT ? "SRT" : "SSA"), "Filename", hb_value_string(subtitle->config.src_filename), "Language", hb_value_string(subtitle->iso639_2)); if (subtitle->source == IMPORTSRT) { hb_dict_t *import_dict = hb_dict_get(subtitle_dict, "Import"); hb_dict_set(import_dict, "Codeset", hb_value_string(subtitle->config.src_codeset)); } } else { subtitle_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:o, s:o}", "Track", hb_value_int(subtitle->track), "Default", hb_value_bool(subtitle->config.default_track), "Forced", hb_value_bool(subtitle->config.force), "Burn", hb_value_bool(subtitle->config.dest == RENDERSUB), "Offset", hb_value_int(subtitle->config.offset)); } if (subtitle->config.name != NULL) { hb_dict_set_string(subtitle_dict, "Name", subtitle->config.name); } if (subtitle->config.external_filename != NULL) { hb_dict_set_string(subtitle_dict, "ExternalFilename", subtitle->config.external_filename); } hb_value_array_append(subtitle_list, subtitle_dict); } // process cover arts if (job->metadata && job->metadata->list_coverart) { hb_value_array_t *art_array = hb_value_array_init(); for (ii = 0; ii < hb_list_count(job->metadata->list_coverart); ii++) { hb_coverart_t *art = hb_list_item(job->metadata->list_coverart, ii); hb_dict_t *art_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o}", "ID", hb_value_int(ii), "Name", hb_value_string(art->name), "Type", hb_value_int(art->type)); if (art_dict) { hb_value_array_append(art_array, art_dict); } } hb_dict_set(dict, "CoverArts", art_array); } return dict; } /** * Convert an hb_job_t to a json string * @param job - Pointer to the hb_job_t to convert */ char* hb_job_to_json( const hb_job_t * job ) { hb_dict_t *dict = hb_job_to_dict(job); if (dict == NULL) return NULL; char *json_job = hb_value_get_json(dict); hb_value_free(&dict); return json_job; } // These functions exist only to perform type checking when using // json_unpack_ex(). typedef const char * const_str_t; static double* unpack_f(double *f) { return f; } static int* unpack_i(int *i) { return i; } static unsigned* unpack_u(unsigned *u) { return u; } static json_int_t* unpack_I(json_int_t *i) { return i; } static int * unpack_b(int *b) { return b; } static const_str_t* unpack_s(const_str_t *s){ return s; } static json_t** unpack_o(json_t** o) { return o; } void hb_json_job_scan( hb_handle_t * h, const char * json_job ) { hb_dict_t * dict; int result; json_error_t error; dict = hb_value_json(json_job); int title_index, hw_decode, keep_duplicate_titles; const char *path = NULL; result = json_unpack_ex(dict, &error, 0, "{s:{s:s, s:i, s?i, s?b}}", "Source", "Path", unpack_s(&path), "Title", unpack_i(&title_index), "HWDecode", unpack_i(&hw_decode), "KeepDuplicateTitles", unpack_b(&keep_duplicate_titles) ); if (result < 0) { hb_error("json unpack failure, failed to find title: %s", error.text); hb_value_free(&dict); return; } // If the job wants to use Hardware decode, it must also be // enabled during scan. So enable it here. hb_list_t *file_paths = hb_list_init(); hb_list_add(file_paths, (char *)path); hb_scan(h, file_paths, title_index, -1, 0, 0, 0, 0, 0, NULL, hw_decode, keep_duplicate_titles); hb_list_close(&file_paths); // Wait for scan to complete hb_state_t state; hb_get_state2(h, &state); while (state.state == HB_STATE_SCANNING) { hb_snooze(50); hb_get_state2(h, &state); } hb_value_free(&dict); } static int validate_audio_codec_mux(int codec, int mux, int track) { const hb_encoder_t *enc = NULL; while ((enc = hb_audio_encoder_get_next(enc)) != NULL) { if ((enc->codec == codec) && (enc->muxers & mux) == 0) { if (codec != HB_ACODEC_NONE) { hb_error("track %d: incompatible encoder '%s' for muxer '%s'", track + 1, enc->short_name, hb_container_get_short_name(mux)); } return -1; } } return 0; } /** * Convert a json string representation of a job to an hb_job_t * @param h - Pointer to the hb_handle_t hb instance which contains the * title that the job refers to. * @param json_job - Pointer to json string representation of a job */ hb_job_t* hb_dict_to_job( hb_handle_t * h, hb_dict_t *dict ) { hb_job_t * job; int result; json_error_t error; int titleindex; if (dict == NULL) return NULL; result = json_unpack_ex(dict, &error, 0, "{s:{s:i}}", "Source", "Title", unpack_i(&titleindex)); if (result < 0) { hb_error("hb_dict_to_job: failed to find title: %s", error.text); return NULL; } job = hb_job_init_by_index(h, titleindex); if (job == NULL) { hb_error("hb_dict_to_job: Title %d doesn't exist", titleindex); return NULL; } hb_value_array_t * chapter_list = NULL; hb_value_array_t * audio_list = NULL; hb_value_array_t * subtitle_list = NULL; hb_value_array_t * filter_list = NULL; hb_value_t * mux = NULL, * vcodec = NULL; hb_dict_t * mastering_dict = NULL; hb_dict_t * coll_dict = NULL; hb_dict_t * dovi_dict = NULL; hb_value_t * acodec_copy_mask = NULL, * acodec_fallback = NULL; const char * destfile = NULL; const char * range_type = NULL; const char * video_preset = NULL, * video_tune = NULL; const char * video_profile = NULL, * video_level = NULL; const char * video_options = NULL; int passthru_dynamic_hdr_metadata = -1; int subtitle_search_burn = 0; const char * subtitle_search_external_filename = NULL; json_int_t range_start = -1, range_end = -1, range_seek_points = -1; int vbitrate = -1; double vquality = HB_INVALID_VIDEO_QUALITY; hb_dict_t * meta_dict = NULL; hb_value_array_t * art_array = NULL; result = json_unpack_ex(dict, &error, 0, "{" // SequenceID "s:i," // Destination {File, Mux, InlineParameterSets, AlignAVStart, // ChapterMarkers, ChapterList, // Options {Optimize, IpodAtom}} "s:{s?s, s:o, s?b, s?b, s:b, s?o s?{s?b, s?b}}," // Source {Angle, KeepDuplicateTitles, Range {Type, Start, End, SeekPoints}} "s:{s?i, s?b, s?{s:s, s?I, s?I, s?I}}," // PAR {Num, Den} "s?{s:i, s:i}," // Video {Codec, Quality, Bitrate, Preset, Tune, Profile, Level, Options // MultiPass, Turbo, PasshtruHDRDynamicMetadata // ColorInputFormat, ColorOutputFormat, ColorRange, // ColorPrimaries, ColorTransfer, ColorMatrix, ChromaLocation, // MasteringDisplayColorVolume, // ContentLightLevel, // DolbyVisionConfigurationRecord // ColorPrimariesOverride, ColorTransferOverride, ColorMatrixOverride, // HardwareDecode, AdapterIndex, AsyncDepth "s:{s:o, s?F, s?i, s?s, s?s, s?s, s?s, s?s," " s?b, s?b, s?i," " s?i, s?i, s?i," " s?i, s?i, s?i, s?i," " s?o," " s?o," " s?o," " s?i, s?i, s?i," " s?i, s?i, s?i}," // Audio {CopyMask, FallbackEncoder, AudioList} "s?{s?o, s?o, s?o}," // Subtitle {Search {Enable, Forced, Default, Burn, ExternalFilename}, SubtitleList} "s?{s?{s:b, s?b, s?b, s?b, s?s}, s?o}," // Metadata "s?o," // Cover arts "s?o," // Filters {FilterList} "s?{s?o}" "}", "SequenceID", unpack_i(&job->sequence_id), "Destination", "File", unpack_s(&destfile), "Mux", unpack_o(&mux), "InlineParameterSets", unpack_b(&job->inline_parameter_sets), "AlignAVStart", unpack_b(&job->align_av_start), "ChapterMarkers", unpack_b(&job->chapter_markers), "ChapterList", unpack_o(&chapter_list), "Options", "Optimize", unpack_b(&job->optimize), "IpodAtom", unpack_b(&job->ipod_atom), "Source", "Angle", unpack_i(&job->angle), "KeepDuplicateTitles", unpack_b(&job->keep_duplicate_titles), "Range", "Type", unpack_s(&range_type), "Start", unpack_I(&range_start), "End", unpack_I(&range_end), "SeekPoints", unpack_I(&range_seek_points), "PAR", "Num", unpack_i(&job->par.num), "Den", unpack_i(&job->par.den), "Video", "Encoder", unpack_o(&vcodec), "Quality", unpack_f(&vquality), "Bitrate", unpack_i(&vbitrate), "Preset", unpack_s(&video_preset), "Tune", unpack_s(&video_tune), "Profile", unpack_s(&video_profile), "Level", unpack_s(&video_level), "Options", unpack_s(&video_options), "MultiPass", unpack_b(&job->multipass), "Turbo", unpack_b(&job->fastanalysispass), "PasshtruHDRDynamicMetadata", unpack_i(&passthru_dynamic_hdr_metadata), "ColorInputFormat", unpack_i(&job->input_pix_fmt), "ColorOutputFormat", unpack_i(&job->output_pix_fmt), "ColorRange", unpack_i(&job->color_range), "ColorPrimaries", unpack_i(&job->color_prim), "ColorTransfer", unpack_i(&job->color_transfer), "ColorMatrix", unpack_i(&job->color_matrix), "ChromaLocation", unpack_i(&job->chroma_location), "MasteringDisplayColorVolume", unpack_o(&mastering_dict), "ContentLightLevel", unpack_o(&coll_dict), "DolbyVisionConfigurationRecord", unpack_o(&dovi_dict), "ColorPrimariesOverride", unpack_i(&job->color_prim_override), "ColorTransferOverride", unpack_i(&job->color_transfer_override), "ColorMatrixOverride", unpack_i(&job->color_matrix_override), "HardwareDecode", unpack_i(&job->hw_decode), "AdapterIndex", unpack_i(&job->hw_device_index), "AsyncDepth", unpack_i(&job->hw_device_async_depth), "Audio", "CopyMask", unpack_o(&acodec_copy_mask), "FallbackEncoder", unpack_o(&acodec_fallback), "AudioList", unpack_o(&audio_list), "Subtitle", "Search", "Enable", unpack_b(&job->indepth_scan), "Forced", unpack_b(&job->select_subtitle_config.force), "Default", unpack_b(&job->select_subtitle_config.default_track), "Burn", unpack_b(&subtitle_search_burn), "ExternalFilename", unpack_s(&subtitle_search_external_filename), "SubtitleList", unpack_o(&subtitle_list), "Metadata", unpack_o(&meta_dict), "CoverArts", unpack_o(&art_array), "Filters", "FilterList", unpack_o(&filter_list) ); if (result < 0) { hb_error("hb_dict_to_job: failed to parse dict: %s", error.text); goto fail; } if (meta_dict != NULL) { hb_value_free(&job->metadata->dict); job->metadata->dict = hb_value_dup(meta_dict); } if (art_array != NULL) { if (hb_value_type(art_array) == HB_VALUE_TYPE_ARRAY) { int count = hb_value_array_len(art_array); for (int ii = hb_list_count(job->metadata->list_coverart) - 1; ii >= 0; ii--) { int found = 0; for (int jj = count; jj >= 0; jj--) { hb_dict_t *art_dict = hb_value_array_get(art_array, jj); if (art_dict) { int index = hb_dict_get_int(art_dict, "ID"); if (index == ii) { found = 1; break; } } } if (found == 0) { hb_metadata_rem_coverart(job->metadata, ii); } } } } // Lookup mux id if (hb_value_type(mux) == HB_VALUE_TYPE_STRING) { const char *s = hb_value_get_string(mux); job->mux = hb_container_get_from_name(s); if (job->mux == 0) job->mux = hb_container_get_from_extension(s); } else { job->mux = hb_value_get_int(mux); } // Lookup video codec if (hb_value_type(vcodec) == HB_VALUE_TYPE_STRING) { const char *s = hb_value_get_string(vcodec); job->vcodec = hb_video_encoder_get_from_name(s); } else { job->vcodec = hb_value_get_int(vcodec); } if (range_type != NULL) { if (!strcasecmp(range_type, "preview")) { if (range_start >= 0) job->start_at_preview = range_start; if (range_end >= 0) job->pts_to_stop = range_end; if (range_seek_points >= 0) job->seek_points = range_seek_points; } else if (!strcasecmp(range_type, "chapter")) { if (range_start >= 0) job->chapter_start = range_start; if (range_end >= 0) job->chapter_end = range_end; } else if (!strcasecmp(range_type, "time")) { if (range_start >= 0) job->pts_to_start = range_start; if (range_end >= 0) job->pts_to_stop = range_end - job->pts_to_start; } else if (!strcasecmp(range_type, "frame")) { if (range_start > 0) job->frame_to_start = range_start; if (range_end > 0) job->frame_to_stop = range_end - job->frame_to_start + 1; } } if (passthru_dynamic_hdr_metadata > -1) { job->passthru_dynamic_hdr_metadata = passthru_dynamic_hdr_metadata; } if (destfile != NULL && destfile[0] != 0) { hb_job_set_file(job, destfile); } hb_job_set_encoder_preset(job, video_preset); hb_job_set_encoder_tune(job, video_tune); hb_job_set_encoder_profile(job, video_profile); hb_job_set_encoder_level(job, video_level); hb_job_set_encoder_options(job, video_options); // If both vbitrate and vquality were specified, vbitrate is used; // we need to ensure the unused rate control mode is always set to an // invalid value, as if both values are valid, behavior is undefined // (some encoders first check for a valid vquality, whereas others // check for a valid vbitrate instead) if (vbitrate > 0) { job->vbitrate = vbitrate; job->vquality = HB_INVALID_VIDEO_QUALITY; } else if (vquality > HB_INVALID_VIDEO_QUALITY) { job->vbitrate = -1; job->vquality = vquality; } // If neither were specified, defaults are used (set in job_setup()) if (mastering_dict != NULL) { result = json_unpack_ex(mastering_dict, &error, 0, "{" // DisplayPrimaries[3][2] "s:[[[ii],[ii]],[[ii],[ii]],[[ii],[ii]]]," // WhitePoint[2], "s:[[i,i],[i,i]]," // MinLuminance, MaxLuminance, HasPrimaries, HasLuminance "s:[i,i],s:[i,i],s:b,s:b" "}", "DisplayPrimaries", unpack_i(&job->mastering.display_primaries[0][0].num), unpack_i(&job->mastering.display_primaries[0][0].den), unpack_i(&job->mastering.display_primaries[0][1].num), unpack_i(&job->mastering.display_primaries[0][1].den), unpack_i(&job->mastering.display_primaries[1][0].num), unpack_i(&job->mastering.display_primaries[1][0].den), unpack_i(&job->mastering.display_primaries[1][1].num), unpack_i(&job->mastering.display_primaries[1][1].den), unpack_i(&job->mastering.display_primaries[2][0].num), unpack_i(&job->mastering.display_primaries[2][0].den), unpack_i(&job->mastering.display_primaries[2][1].num), unpack_i(&job->mastering.display_primaries[2][1].den), "WhitePoint", unpack_i(&job->mastering.white_point[0].num), unpack_i(&job->mastering.white_point[0].den), unpack_i(&job->mastering.white_point[1].num), unpack_i(&job->mastering.white_point[1].den), "MinLuminance", unpack_i(&job->mastering.min_luminance.num), unpack_i(&job->mastering.min_luminance.den), "MaxLuminance", unpack_i(&job->mastering.max_luminance.num), unpack_i(&job->mastering.max_luminance.den), "HasPrimaries", unpack_b(&job->mastering.has_primaries), "HasLuminance", unpack_b(&job->mastering.has_luminance) ); if (result < 0) { hb_error("hb_dict_to_job: failed to parse mastering_dict: %s", error.text); goto fail; } } if (coll_dict != NULL) { result = json_unpack_ex(coll_dict, &error, 0, // {MaxCLL, MaxFALL} "{s:i, s:i}", "MaxCLL", unpack_u(&job->coll.max_cll), "MaxFALL", unpack_u(&job->coll.max_fall) ); if (result < 0) { hb_error("hb_dict_to_job: failed to parse coll_dict: %s", error.text); goto fail; } } if (dovi_dict != NULL) { result = json_unpack_ex(dovi_dict, &error, 0, "{s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i}", "DVVersionMajor", unpack_u(&job->dovi.dv_version_major), "DVVersionMinor", unpack_u(&job->dovi.dv_version_minor), "DVProfile", unpack_u(&job->dovi.dv_profile), "DVLevel", unpack_u(&job->dovi.dv_level), "RPUPresentFlag", unpack_u(&job->dovi.rpu_present_flag), "ELPresentFlag", unpack_u(&job->dovi.el_present_flag), "BLPresentFlag", unpack_u(&job->dovi.bl_present_flag), "BLSignalCompatibilityId", unpack_u(&job->dovi.dv_bl_signal_compatibility_id) ); if (result < 0) { hb_error("hb_dict_to_job: failed to parse dovi_dict: %s", error.text); goto fail; } } // process chapter list if (chapter_list != NULL && hb_value_type(chapter_list) == HB_VALUE_TYPE_ARRAY) { int ii, count; hb_dict_t *chapter_dict; count = hb_value_array_len(chapter_list); for (ii = 0; ii < count; ii++) { chapter_dict = hb_value_array_get(chapter_list, ii); const char *name = NULL; result = json_unpack_ex(chapter_dict, &error, 0, "{s:s}", "Name", unpack_s(&name)); if (result < 0) { hb_error("hb_dict_to_job: failed to find chapter name: %s", error.text); goto fail; } if (name != NULL && name[0] != 0) { hb_chapter_t *chapter; chapter = hb_list_item(job->list_chapter, ii); if (chapter != NULL) { hb_chapter_set_title(chapter, name); } } } } // process filter list if (filter_list != NULL && hb_value_type(filter_list) == HB_VALUE_TYPE_ARRAY) { int ii, count; hb_dict_t *filter_dict; count = hb_value_array_len(filter_list); for (ii = 0; ii < count; ii++) { filter_dict = hb_value_array_get(filter_list, ii); int filter_id = -1; hb_value_t *filter_settings = NULL; result = json_unpack_ex(filter_dict, &error, 0, "{s:i, s?o}", "ID", unpack_i(&filter_id), "Settings", unpack_o(&filter_settings)); if (result < 0) { hb_error("hb_dict_to_job: failed to find filter settings: %s", error.text); goto fail; } if (filter_id >= HB_FILTER_FIRST && filter_id <= HB_FILTER_LAST) { hb_filter_object_t *filter; filter = hb_filter_init(filter_id); hb_add_filter_dict(job, filter, filter_settings); } } } // process audio list if (acodec_fallback != NULL) { if (hb_value_type(acodec_fallback) == HB_VALUE_TYPE_STRING) { const char *s = hb_value_get_string(acodec_fallback); job->acodec_fallback = hb_audio_encoder_get_from_name(s); } else { job->acodec_fallback = hb_value_get_int(acodec_fallback); } } if (acodec_copy_mask != NULL) { if (hb_value_type(acodec_copy_mask) == HB_VALUE_TYPE_ARRAY) { int count, ii; count = hb_value_array_len(acodec_copy_mask); for (ii = 0; ii < count; ii++) { hb_value_t *value = hb_value_array_get(acodec_copy_mask, ii); if (hb_value_type(value) == HB_VALUE_TYPE_STRING) { const char *s = hb_value_get_string(value); job->acodec_copy_mask |= hb_audio_encoder_get_from_name(s); } else { job->acodec_copy_mask |= hb_value_get_int(value); } } } else if (hb_value_type(acodec_copy_mask) == HB_VALUE_TYPE_STRING) { // Split the string at ',' char *s = strdup(hb_value_get_string(acodec_copy_mask)); char *cur = s; while (cur != NULL && cur[0] != 0) { char *next = strchr(cur, ','); if (next != NULL) { *next = 0; next++; } job->acodec_copy_mask |= hb_audio_encoder_get_from_name(cur); cur = next; } free(s); } else { job->acodec_copy_mask = hb_value_get_int(acodec_copy_mask); } } if (audio_list != NULL && hb_value_type(audio_list) == HB_VALUE_TYPE_ARRAY) { int ii, count; hb_dict_t *audio_dict; count = hb_value_array_len(audio_list); for (ii = 0; ii < count; ii++) { audio_dict = hb_value_array_get(audio_list, ii); hb_audio_config_t audio; hb_value_t *acodec = NULL, *samplerate = NULL, *mixdown = NULL; hb_value_t *dither = NULL; const char *name = NULL; hb_audio_config_init(&audio); result = json_unpack_ex(audio_dict, &error, 0, "{s:i, s?s, s?o, s?F, s?F, s?o, s?b, s?o, s?o, s?i, s?F, s?F}", "Track", unpack_i(&audio.index), "Name", unpack_s(&name), "Encoder", unpack_o(&acodec), "Gain", unpack_f(&audio.out.gain), "DRC", unpack_f(&audio.out.dynamic_range_compression), "Mixdown", unpack_o(&mixdown), "NormalizeMixLevel", unpack_b(&audio.out.normalize_mix_level), "DitherMethod", unpack_o(&dither), "Samplerate", unpack_o(&samplerate), "Bitrate", unpack_i(&audio.out.bitrate), "Quality", unpack_f(&audio.out.quality), "CompressionLevel", unpack_f(&audio.out.compression_level)); if (result < 0) { hb_error("hb_dict_to_job: failed to find audio settings: %s", error.text); goto fail; } if (acodec != NULL) { if (hb_value_type(acodec) == HB_VALUE_TYPE_STRING) { const char *s = hb_value_get_string(acodec); audio.out.codec = hb_audio_encoder_get_from_name(s); } else { audio.out.codec = hb_value_get_int(acodec); } } if (mixdown != NULL) { if (hb_value_type(mixdown) == HB_VALUE_TYPE_STRING) { const char *s = hb_value_get_string(mixdown); audio.out.mixdown = hb_mixdown_get_from_name(s); } else { audio.out.mixdown = hb_value_get_int(mixdown); } } if (samplerate != NULL) { if (hb_value_type(samplerate) == HB_VALUE_TYPE_STRING) { const char *s = hb_value_get_string(samplerate); audio.out.samplerate = hb_audio_samplerate_get_from_name(s); if (audio.out.samplerate < 0) audio.out.samplerate = 0; } else { audio.out.samplerate = hb_value_get_int(samplerate); } } if (dither != NULL) { if (hb_value_type(dither) == HB_VALUE_TYPE_STRING) { const char *s = hb_value_get_string(dither); audio.out.dither_method = hb_audio_dither_get_from_name(s); } else { audio.out.dither_method = hb_value_get_int(dither); } } if (name != NULL) { audio.out.name = name; } if (audio.index >= 0) { audio.out.track = ii; hb_audio_add(job, &audio); } hb_audio_config_close(&audio); } } // Audio sanity checks int ii; for (ii = 0; ii < hb_list_count(job->list_audio); ) { hb_audio_config_t *acfg; acfg = hb_list_audio_config_item(job->list_audio, ii); if (validate_audio_codec_mux(acfg->out.codec, job->mux, ii)) { // drop the track hb_audio_t * audio = hb_list_item(job->list_audio, ii); hb_list_rem(job->list_audio, audio); hb_audio_close(&audio); continue; } ii++; } job->select_subtitle_config.dest = subtitle_search_burn ? RENDERSUB : PASSTHRUSUB; hb_update_str(&job->select_subtitle_config.external_filename, subtitle_search_external_filename); // process subtitle list if (subtitle_list != NULL && hb_value_type(subtitle_list) == HB_VALUE_TYPE_ARRAY) { int ii, count; hb_dict_t *subtitle_dict; count = hb_value_array_len(subtitle_list); for (ii = 0; ii < count; ii++) { subtitle_dict = hb_value_array_get(subtitle_list, ii); hb_subtitle_config_t sub_config = {0}; int track = -1; int burn = 0; const char *importfile = NULL; json_int_t offset = 0; const char *name = NULL; const char *external_filename = NULL; result = json_unpack_ex(subtitle_dict, &error, 0, "{s?i, s?s, s?s, s?{s:s}, s?{s:s}}", "Track", unpack_i(&track), "Name", unpack_s(&name), "ExternalFilename", unpack_s(&external_filename), // Support legacy "SRT" import "SRT", "Filename", unpack_s(&importfile), "Import", "Filename", unpack_s(&importfile)); if (result < 0) { hb_error("json unpack failure: %s", error.text); hb_job_close(&job); return NULL; } // Embedded subtitle track if (track >= 0 && importfile == NULL) { hb_subtitle_t *subtitle; subtitle = hb_list_item(job->title->list_subtitle, track); if (subtitle != NULL) { sub_config = subtitle->config; result = json_unpack_ex(subtitle_dict, &error, 0, "{s?b, s?b, s?b, s?I}", "Default", unpack_b(&sub_config.default_track), "Forced", unpack_b(&sub_config.force), "Burn", unpack_b(&burn), "Offset", unpack_I(&offset)); if (result < 0) { hb_error("json unpack failure: %s", error.text); hb_job_close(&job); return NULL; } sub_config.name = name; sub_config.offset = offset; sub_config.dest = burn ? RENDERSUB : PASSTHRUSUB; sub_config.external_filename = (char*)external_filename; hb_subtitle_add(job, &sub_config, track); } } else if (importfile != NULL) { sub_config.src_filename = strdup(importfile); const char * lang = "und"; const char * srtcodeset = "UTF-8"; const char * format = "SRT"; int source = IMPORTSRT; result = json_unpack_ex(subtitle_dict, &error, 0, "{s?b, s?b, s?I, " // Common "s?{s?s, s?s, s?s}," // Legacy SRT settings "s?{s?s, s?s, s?s, s?s}}", // Import settings "Default", unpack_b(&sub_config.default_track), "Burn", unpack_b(&burn), "Offset", unpack_I(&offset), "SRT", "Filename", unpack_s(&importfile), "Language", unpack_s(&lang), "Codeset", unpack_s(&srtcodeset), "Import", "Format", unpack_s(&format), "Filename", unpack_s(&importfile), "Language", unpack_s(&lang), "Codeset", unpack_s(&srtcodeset)); if (result < 0) { hb_error("json unpack failure: %s", error.text); hb_job_close(&job); return NULL; } sub_config.name = name; sub_config.offset = offset; sub_config.dest = burn ? RENDERSUB : PASSTHRUSUB; strncpy(sub_config.src_codeset, srtcodeset, 39); sub_config.src_codeset[39] = 0; if (!strcasecmp(format, "SSA")) { source = IMPORTSSA; } sub_config.external_filename = (char*)external_filename; hb_import_subtitle_add(job, &sub_config, lang, source); } } } return job; fail: hb_job_close(&job); return NULL; } hb_job_t* hb_json_to_job( hb_handle_t * h, const char * json_job ) { hb_dict_t *dict = hb_value_json(json_job); hb_job_t *job = hb_dict_to_job(h, dict); hb_value_free(&dict); return job; } /** * Initialize an hb_job_t and return a json string representation of the job * @param h - Pointer to hb_handle_t instance that contains the * specified title_index * @param title_index - Index of hb_title_t to use for job initialization. * Index comes from title->index or "Index" key * in json representation of a title. */ char* hb_job_init_json(hb_handle_t *h, int title_index) { hb_job_t *job = hb_job_init_by_index(h, title_index); char *json_job = hb_job_to_json(job); hb_job_close(&job); return json_job; } char* hb_preset_job_init_json(hb_handle_t *h, int title_index, const char *json_preset) { hb_dict_t * preset = hb_value_json(json_preset); hb_dict_t * job = hb_preset_job_init(h, title_index, preset); char * json_job = hb_value_get_json(job); hb_value_free(&preset); hb_value_free(&job); return json_job; } /** * Add a json string job to the hb queue * @param h - Pointer to hb_handle_t instance that job is added to * @param json_job - json string representation of job to add */ int hb_add_json( hb_handle_t * h, const char * json_job ) { hb_job_t job; job.json = json_job; return hb_add(h, &job); } /** * Calculates destination width and height for anamorphic content * * Returns geometry as json string {Width, Height, PAR {Num, Den}} * @param json_param - contains source and destination geometry params. * This encapsulates the values that are in * hb_geometry_t and hb_geometry_settings_t */ char* hb_set_anamorphic_size_json(const char * json_param) { int json_result; json_error_t error; hb_dict_t * dict; hb_geometry_t geo_result; hb_geometry_t src; hb_geometry_settings_t ui_geo; // Clear dest geometry since some fields are optional. memset(&ui_geo, 0, sizeof(ui_geo)); dict = hb_value_json(json_param); json_result = json_unpack_ex(dict, &error, 0, "{" // SourceGeometry // {Width, Height, PAR {Num, Den}} "s:{s:i, s:i, s:{s:i, s:i}}," // DestSettings "s:{" // Geometry {Width, Height, PAR {Num, Den}}, "s:{s:i, s:i, s:{s:i, s:i}}," // AnamorphicMode, Flags, Keep, ItuPAR, Modulus, MaxWidth, MaxHeight, // DisplayWidth, DisplayHeight "s:i, s?i, s?i, s?b, s?i, s?i, s?i, s?i, s?i" // Crop [Top, Bottom, Left, Right] "s?[iiii]" // Pad [Top, Bottom, Left, Right] "s?[iiii]" " }" "}", "SourceGeometry", "Width", unpack_i(&src.width), "Height", unpack_i(&src.height), "PAR", "Num", unpack_i(&src.par.num), "Den", unpack_i(&src.par.den), "DestSettings", "Geometry", "Width", unpack_i(&ui_geo.geometry.width), "Height", unpack_i(&ui_geo.geometry.height), "PAR", "Num", unpack_i(&ui_geo.geometry.par.num), "Den", unpack_i(&ui_geo.geometry.par.den), "AnamorphicMode", unpack_i(&ui_geo.mode), "Flags", unpack_i(&ui_geo.flags), "Keep", unpack_i(&ui_geo.keep), "ItuPAR", unpack_b(&ui_geo.itu_par), "Modulus", unpack_i(&ui_geo.modulus), "MaxWidth", unpack_i(&ui_geo.maxWidth), "MaxHeight", unpack_i(&ui_geo.maxHeight), "DisplayWidth", unpack_i(&ui_geo.displayWidth), "DisplayHeight", unpack_i(&ui_geo.displayHeight), "Crop", unpack_i(&ui_geo.crop[0]), unpack_i(&ui_geo.crop[1]), unpack_i(&ui_geo.crop[2]), unpack_i(&ui_geo.crop[3]), "Pad", unpack_i(&ui_geo.pad[0]), unpack_i(&ui_geo.pad[1]), unpack_i(&ui_geo.pad[2]), unpack_i(&ui_geo.pad[3]) ); hb_value_free(&dict); if (json_result < 0) { hb_error("json unpack failure: %s", error.text); return NULL; } hb_set_anamorphic_size2(&src, &ui_geo, &geo_result); dict = json_pack_ex(&error, 0, "{s:o, s:o, s:{s:o, s:o}}", "Width", hb_value_int(geo_result.width), "Height", hb_value_int(geo_result.height), "PAR", "Num", hb_value_int(geo_result.par.num), "Den", hb_value_int(geo_result.par.den)); if (dict == NULL) { hb_error("hb_set_anamorphic_size_json: pack failure: %s", error.text); return NULL; } char *result = hb_value_get_json(dict); hb_value_free(&dict); return result; } hb_image_t * hb_get_preview3_json(hb_handle_t * h, int picture, const char *json_job) { hb_image_t * image; hb_dict_t * job_dict; job_dict = hb_value_json(json_job); image = hb_get_preview3(h, picture, job_dict); hb_value_free(&job_dict); return image; } char* hb_get_preview_params_json(int title_idx, int preview_idx, int deinterlace, hb_geometry_settings_t *settings) { json_error_t error; hb_dict_t * dict; dict = json_pack_ex(&error, 0, "{" "s:o, s:o, s:o," "s:{" " s:{s:o, s:o, s:{s:o, s:o}}," " s:o, s:o, s:o, s:o, s:o, s:o" " s:[oooo]" " }" "}", "Title", hb_value_int(title_idx), "Preview", hb_value_int(preview_idx), "Deinterlace", hb_value_bool(deinterlace), "DestSettings", "Geometry", "Width", hb_value_int(settings->geometry.width), "Height", hb_value_int(settings->geometry.height), "PAR", "Num", hb_value_int(settings->geometry.par.num), "Den", hb_value_int(settings->geometry.par.den), "AnamorphicMode", hb_value_int(settings->mode), "Keep", hb_value_int(settings->keep), "ItuPAR", hb_value_bool(settings->itu_par), "Modulus", hb_value_int(settings->modulus), "MaxWidth", hb_value_int(settings->maxWidth), "MaxHeight", hb_value_int(settings->maxHeight), "Crop", hb_value_int(settings->crop[0]), hb_value_int(settings->crop[1]), hb_value_int(settings->crop[2]), hb_value_int(settings->crop[3]) ); if (dict == NULL) { hb_error("hb_get_preview_params_json: pack failure: %s", error.text); return NULL; } char *result = hb_value_get_json(dict); hb_value_free(&dict); return result; } hb_image_t* hb_json_to_image(char *json_image) { int json_result; json_error_t error; hb_dict_t * dict; int pix_fmt, width, height; dict = hb_value_json(json_image); json_result = json_unpack_ex(dict, &error, 0, "{" // Format, Width, Height "s:i, s:i, s:i," "}", "Format", unpack_i(&pix_fmt), "Width", unpack_i(&width), "Height", unpack_b(&height) ); if (json_result < 0) { hb_error("image: json unpack failure: %s", error.text); hb_value_free(&dict); return NULL; } hb_image_t *image = hb_image_init(pix_fmt, width, height); if (image == NULL) { hb_value_free(&dict); return NULL; } hb_value_array_t * planes = NULL; json_result = json_unpack_ex(dict, &error, 0, "{s:o}", "Planes", unpack_o(&planes)); if (json_result < 0) { hb_error("image::planes: json unpack failure: %s", error.text); hb_value_free(&dict); return image; } if (hb_value_type(planes) == HB_VALUE_TYPE_ARRAY) { int ii, count; hb_dict_t *plane_dict; count = hb_value_array_len(planes); for (ii = 0; ii < count; ii++) { plane_dict = hb_value_array_get(planes, ii); const char *data = NULL; int size; json_result = json_unpack_ex(plane_dict, &error, 0, "{s:i, s:s}", "Size", unpack_i(&size), "Data", unpack_s(&data)); if (json_result < 0) { hb_error("image::plane::data: json unpack failure: %s", error.text); hb_value_free(&dict); return image; } if (image->plane[ii].size > 0 && data != NULL) { av_base64_decode(image->plane[ii].data, data, image->plane[ii].size); } } } hb_value_free(&dict); return image; }