/* json.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 "handbrake/handbrake.h" #include "handbrake/hb_json.h" #include "libavutil/base64.h" #include "handbrake/qsv_common.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; dict = json_pack_ex(&error, 0, "{" // Type, Path, Name, Index, Playlist, AngleCount "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]," // Color {Format, Range, Primary, Transfer, Matrix} "s:{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:{}" "}", "Type", hb_value_int(title->type), "Path", hb_value_string(title->path), "Name", hb_value_string(title->name), "Index", hb_value_int(title->index), "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]), "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), "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" ); if (dict == NULL) { hb_error("hb_title_to_dict_internal, json pack failure: %s", error.text); return NULL; } if (title->container_name != NULL) { hb_dict_set(dict, "Container", hb_value_string(title->container_name)); } // Add metadata hb_dict_t *meta_dict = hb_dict_get(dict, "Metadata"); if (title->metadata->name != NULL) { hb_dict_set(meta_dict, "Name", hb_value_string(title->metadata->name)); } if (title->metadata->artist != NULL) { hb_dict_set(meta_dict, "Artist", hb_value_string(title->metadata->artist)); } if (title->metadata->composer != NULL) { hb_dict_set(meta_dict, "Composer", hb_value_string(title->metadata->composer)); } if (title->metadata->comment != NULL) { hb_dict_set(meta_dict, "Comment", hb_value_string(title->metadata->comment)); } if (title->metadata->genre != NULL) { hb_dict_set(meta_dict, "Genre", hb_value_string(title->metadata->genre)); } if (title->metadata->album != NULL) { hb_dict_set(meta_dict, "Album", hb_value_string(title->metadata->album)); } if (title->metadata->album_artist != NULL) { hb_dict_set(meta_dict, "AlbumArtist", hb_value_string(title->metadata->album_artist)); } if (title->metadata->description != NULL) { hb_dict_set(meta_dict, "Description", hb_value_string(title->metadata->description)); } if (title->metadata->long_description != NULL) { hb_dict_set(meta_dict, "LongDescription", hb_value_string(title->metadata->long_description)); } if (title->metadata->release_date != NULL) { hb_dict_set(meta_dict, "ReleaseDate", hb_value_string(title->metadata->release_date)); } // 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[64]; 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(channel_layout_name, sizeof(channel_layout_name), audio->config.in.channel_layout); channel_count = hb_layout_get_discrete_channel_count( audio->config.in.channel_layout); lfe_count = hb_layout_get_low_freq_channel_count( audio->config.in.channel_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, 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_int(audio->config.in.channel_layout), "ChannelLayoutName", 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); 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; int adapter_index; #if HB_PROJECT_FEATURE_QSV adapter_index = job->qsv.ctx->dx_index; #else adapter_index = 0; #endif 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} "s:{s:o, s:o, s:o,}," // PAR {Num, Den} "s:{s:o, s:o}," // Video {Encoder, QSV {Decode, AsyncDepth, AdapterIndex}} "s:{s:o, s:{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:{}," // 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), "PAR", "Num", hb_value_int(job->par.num), "Den", hb_value_int(job->par.den), "Video", "Encoder", hb_value_int(job->vcodec), "QSV", "Decode", hb_value_bool(job->qsv.decode), "AsyncDepth", hb_value_int(job->qsv.async_depth), "AdapterIndex", hb_value_int(adapter_index), "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", "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_MUX_MASK_MP4) { hb_dict_t *mp4_dict; mp4_dict = json_pack_ex(&error, 0, "{s:o, s:o}", "Mp4Optimize", hb_value_bool(job->mp4_optimize), "IpodAtom", hb_value_bool(job->ipod_atom)); hb_dict_set(dest_dict, "Mp4Options", mp4_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 + 1)); } if (job->frame_to_stop > 0) { hb_dict_set(range_dict, "End", hb_value_int(job->frame_to_start + job->frame_to_stop)); } } 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, "ColorFormat", hb_value_int(job->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)); if (job->color_prim_override != HB_COLR_PRI_UNDEF) { hb_dict_set(video_dict, "ColorPrimariesOverride", hb_value_int(job->color_prim_override)); } if (job->color_transfer_override != HB_COLR_TRA_UNDEF) { hb_dict_set(video_dict, "ColorTransferOverride", hb_value_int(job->color_transfer_override)); } if (job->color_matrix_override != HB_COLR_MAT_UNDEF) { hb_dict_set(video_dict, "ColorMatrixOverride", hb_value_int(job->color_matrix_override)); } // Mastering 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, "Mastering", 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); } 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, "TwoPass", hb_value_bool(job->twopass)); hb_dict_set(video_dict, "Turbo", hb_value_bool(job->fastfirstpass)); } 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)); } hb_dict_t *meta_dict = hb_dict_get(dict, "Metadata"); if (job->metadata->name != NULL) { hb_dict_set(meta_dict, "Name", hb_value_string(job->metadata->name)); } if (job->metadata->artist != NULL) { hb_dict_set(meta_dict, "Artist", hb_value_string(job->metadata->artist)); } if (job->metadata->composer != NULL) { hb_dict_set(meta_dict, "Composer", hb_value_string(job->metadata->composer)); } if (job->metadata->comment != NULL) { hb_dict_set(meta_dict, "Comment", hb_value_string(job->metadata->comment)); } if (job->metadata->genre != NULL) { hb_dict_set(meta_dict, "Genre", hb_value_string(job->metadata->genre)); } if (job->metadata->album != NULL) { hb_dict_set(meta_dict, "Album", hb_value_string(job->metadata->album)); } if (job->metadata->album_artist != NULL) { hb_dict_set(meta_dict, "AlbumArtist", hb_value_string(job->metadata->album_artist)); } if (job->metadata->description != NULL) { hb_dict_set(meta_dict, "Description", hb_value_string(job->metadata->description)); } if (job->metadata->long_description != NULL) { hb_dict_set(meta_dict, "LongDescription", hb_value_string(job->metadata->long_description)); } if (job->metadata->release_date != NULL) { hb_dict_set(meta_dict, "ReleaseDate", hb_value_string(job->metadata->release_date)); } // 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.in.track), "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"); 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); } hb_value_array_append(subtitle_list, subtitle_dict); } 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; const char *path = NULL; result = json_unpack_ex(dict, &error, 0, "{s:{s:s, s:i}}", "Source", "Path", unpack_s(&path), "Title", unpack_i(&title_index) ); 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_scan(h, path, title_index, -1, 0, 0); // 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_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 subtitle_search_burn = 0; hb_dict_t * meta_dict = NULL; const char * meta_name = NULL, * meta_artist = NULL; const char * meta_album_artist = NULL, * meta_release = NULL; const char * meta_comment = NULL, * meta_genre = NULL; const char * meta_composer = NULL, * meta_desc = NULL; const char * meta_long_desc = NULL; json_int_t range_start = -1, range_end = -1, range_seek_points = -1; int vbitrate = -1; double vquality = HB_INVALID_VIDEO_QUALITY; int adapter_index = -1; result = json_unpack_ex(dict, &error, 0, "{" // SequenceID "s:i," // Destination {File, Mux, InlineParameterSets, AlignAVStart, // ChapterMarkers, ChapterList, // Mp4Options {Mp4Optimize, IpodAtom}} "s:{s?s, s:o, s?b, s?b, s:b, s?o s?{s?b, s?b}}," // Source {Angle, Range {Type, Start, End, SeekPoints}} "s:{s?i, 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 // TwoPass, Turbo, // ColorFormat, ColorRange, // ColorPrimaries, ColorTransfer, ColorMatrix, // Mastering, // ContentLightLevel, // ColorPrimariesOverride, ColorTransferOverride, ColorMatrixOverride, // QSV {Decode, AsyncDepth, AdapterIndex}} "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?o," " s?o," " s?i, s?i, s?i," " s?{s?b, s?i, s?i}}," // Audio {CopyMask, FallbackEncoder, AudioList} "s?{s?o, s?o, s?o}," // Subtitle {Search {Enable, Forced, Default, Burn}, SubtitleList} "s?{s?{s:b, s?b, s?b, s?b}, s?o}," // Metadata "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), "Mp4Options", "Mp4Optimize", unpack_b(&job->mp4_optimize), "IpodAtom", unpack_b(&job->ipod_atom), "Source", "Angle", unpack_i(&job->angle), "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), "TwoPass", unpack_b(&job->twopass), "Turbo", unpack_b(&job->fastfirstpass), "ColorFormat", unpack_i(&job->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), "Mastering", unpack_o(&mastering_dict), "ContentLightLevel", unpack_o(&coll_dict), "ColorPrimariesOverride", unpack_i(&job->color_prim_override), "ColorTransferOverride", unpack_i(&job->color_transfer_override), "ColorMatrixOverride", unpack_i(&job->color_matrix_override), "QSV", "Decode", unpack_b(&job->qsv.decode), "AsyncDepth", unpack_i(&job->qsv.async_depth), "AdapterIndex", unpack_i(&adapter_index), "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), "SubtitleList", unpack_o(&subtitle_list), "Metadata", unpack_o(&meta_dict), "Filters", "FilterList", unpack_o(&filter_list) ); if (result < 0) { hb_error("hb_dict_to_job: failed to parse dict: %s", error.text); goto fail; } // 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 - 1; if (range_end > 0) job->frame_to_stop = range_end - job->frame_to_start; } } 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 HB_PROJECT_FEATURE_QSV job->qsv.ctx->dx_index = adapter_index; #endif // If both vbitrate and vquality were specified, vbitrate is used; // we need to ensure the unused rate contro 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()) job->select_subtitle_config.dest = subtitle_search_burn ? RENDERSUB : PASSTHRUSUB; 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 (meta_dict != NULL) { // By default, the job is populated with the metadata // from the source title. // // If the metadata dict is present, assume any fields not // present are to be removed from the job's metadata meta_name = meta_artist = meta_composer = meta_album_artist = meta_release = meta_comment = meta_genre = meta_desc = meta_long_desc = ""; result = json_unpack_ex(meta_dict, &error, 0, // {Name, Artist, Composer, AlbumArtist, ReleaseDate, // Comment, Genre, Description, LongDescription} "{s?s, s?s, s?s, s?s, s?s, s?s, s?s, s?s, s?s}", "Name", unpack_s(&meta_name), "Artist", unpack_s(&meta_artist), "Composer", unpack_s(&meta_composer), "AlbumArtist", unpack_s(&meta_album_artist), "ReleaseDate", unpack_s(&meta_release), "Comment", unpack_s(&meta_comment), "Genre", unpack_s(&meta_genre), "Description", unpack_s(&meta_desc), "LongDescription", unpack_s(&meta_long_desc) ); if (result < 0) { hb_error("hb_dict_to_job: failed to parse meta_dict: %s", error.text); goto fail; } } if (meta_name != NULL) { if (meta_name[0] != 0) { hb_metadata_set_name(job->metadata, meta_name); } else { hb_metadata_set_name(job->metadata, NULL); } } if (meta_artist != NULL) { if (meta_artist[0] != 0) { hb_metadata_set_artist(job->metadata, meta_artist); } else { hb_metadata_set_artist(job->metadata, NULL); } } if (meta_composer != NULL) { if (meta_composer[0] != 0) { hb_metadata_set_composer(job->metadata, meta_composer); } else { hb_metadata_set_composer(job->metadata, NULL); } } if (meta_album_artist != NULL) { if (meta_album_artist[0] != 0) { hb_metadata_set_album_artist(job->metadata, meta_album_artist); } else { hb_metadata_set_album_artist(job->metadata, NULL); } } if (meta_release != NULL) { if (meta_release[0] != 0) { hb_metadata_set_release_date(job->metadata, meta_release); } else { hb_metadata_set_release_date(job->metadata, NULL); } } if (meta_comment != NULL) { if (meta_comment[0] != 0) { hb_metadata_set_comment(job->metadata, meta_comment); } else { hb_metadata_set_comment(job->metadata, NULL); } } if (meta_genre != NULL) { if (meta_genre[0] != 0) { hb_metadata_set_genre(job->metadata, meta_genre); } else { hb_metadata_set_genre(job->metadata, NULL); } } if (meta_desc != NULL) { if (meta_desc[0] != 0) { hb_metadata_set_description(job->metadata, meta_desc); } else { hb_metadata_set_description(job->metadata, NULL); } } if (meta_long_desc != NULL) { if (meta_long_desc[0] != 0) { hb_metadata_set_long_description(job->metadata, meta_long_desc); } else { hb_metadata_set_long_description(job->metadata, NULL); } } // 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.in.track), "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 = strdup(name); } if (audio.in.track >= 0) { audio.out.track = ii; hb_audio_add(job, &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++; } // 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; result = json_unpack_ex(subtitle_dict, &error, 0, "{s?i, s?s, s?{s:s}, s?{s:s}}", "Track", unpack_i(&track), "Name", unpack_s(&name), // 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; if (name != NULL) { sub_config.name = strdup(name); } 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.offset = offset; sub_config.dest = burn ? RENDERSUB : PASSTHRUSUB; 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; } if (name != NULL) { sub_config.name = strdup(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; } 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, Keep, ItuPAR, Modulus, MaxWidth, MaxHeight, "s:i, s?i, s?b, s:i, s:i, s:i," // Crop [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), "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), "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]) ); 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; } char* hb_get_preview_json(hb_handle_t * h, const char *json_param) { hb_image_t *image; int ii, title_idx, preview_idx, deinterlace = 0; int json_result; json_error_t error; hb_dict_t * dict; hb_geometry_settings_t settings; // Clear dest geometry since some fields are optional. memset(&settings, 0, sizeof(settings)); dict = hb_value_json(json_param); json_result = json_unpack_ex(dict, &error, 0, "{" // Title, Preview, Deinterlace "s:i, s:i, s?b," // DestSettings "s:{" // Geometry {Width, Height, PAR {Num, Den}}, "s:{s:i, s:i, s:{s:i, s:i}}," // AnamorphicMode, Keep, ItuPAR, Modulus, MaxWidth, MaxHeight, "s:i, s?i, s?b, s:i, s:i, s:i," // Crop [Top, Bottom, Left, Right] "s?[iiii]" " }" "}", "Title", unpack_i(&title_idx), "Preview", unpack_i(&preview_idx), "Deinterlace", unpack_b(&deinterlace), "DestSettings", "Geometry", "Width", unpack_i(&settings.geometry.width), "Height", unpack_i(&settings.geometry.height), "PAR", "Num", unpack_i(&settings.geometry.par.num), "Den", unpack_i(&settings.geometry.par.den), "AnamorphicMode", unpack_i(&settings.mode), "Keep", unpack_i(&settings.keep), "ItuPAR", unpack_b(&settings.itu_par), "Modulus", unpack_i(&settings.modulus), "MaxWidth", unpack_i(&settings.maxWidth), "MaxHeight", unpack_i(&settings.maxHeight), "Crop", unpack_i(&settings.crop[0]), unpack_i(&settings.crop[1]), unpack_i(&settings.crop[2]), unpack_i(&settings.crop[3]) ); hb_value_free(&dict); if (json_result < 0) { hb_error("preview params: json unpack failure: %s", error.text); return NULL; } image = hb_get_preview2(h, title_idx, preview_idx, &settings, deinterlace); if (image == NULL) { return NULL; } dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o}", "Format", hb_value_int(image->format), "Width", hb_value_int(image->width), "Height", hb_value_int(image->height)); if (dict == NULL) { hb_error("hb_get_preview_json: pack failure: %s", error.text); return NULL; } hb_value_array_t * planes = hb_value_array_init(); for (ii = 0; ii < 4; ii++) { int base64size = AV_BASE64_SIZE(image->plane[ii].size); if (image->plane[ii].size <= 0 || base64size <= 0) continue; char *plane_base64 = calloc(base64size, 1); av_base64_encode(plane_base64, base64size, image->plane[ii].data, image->plane[ii].size); base64size = strlen(plane_base64); hb_dict_t *plane_dict; plane_dict = json_pack_ex(&error, 0, "{s:o, s:o, s:o, s:o, s:o, s:o}", "Width", hb_value_int(image->plane[ii].width), "Height", hb_value_int(image->plane[ii].height), "Stride", hb_value_int(image->plane[ii].stride), "HeightStride", hb_value_int(image->plane[ii].height_stride), "Size", hb_value_int(base64size), "Data", hb_value_string(plane_base64) ); if (plane_dict == NULL) { hb_error("plane_dict: json pack failure: %s", error.text); return NULL; } hb_value_array_append(planes, plane_dict); } hb_dict_set(dict, "Planes", planes); hb_image_close(&image); 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; }