From d0e37ca0d75fbcef5bdf47aafe8543e262314ec5 Mon Sep 17 00:00:00 2001 From: Justin Bull Date: Wed, 13 Feb 2019 09:52:48 -0500 Subject: [PATCH] Add WebM support (#1822) Note that since webm has no official subtitle support, only burned in subtitles can be used with this muxer at this time. --- gtk/src/callbacks.c | 2 +- gtk/src/ghb.m4 | 1 + gtk/src/hb-backend.c | 58 ++++++++++++++- gtk/src/main.c | 5 ++ libhb/common.c | 29 +++++--- libhb/common.h | 26 ++++--- libhb/decavcodec.c | 2 +- libhb/internal.h | 1 + libhb/muxavformat.c | 14 +++- libhb/muxcommon.c | 1 + macosx/HBAudioTrack.m | 2 +- macosx/HBJob+UIAdditions.m | 12 +++ macosx/HBPreviewGenerator.m | 4 + macosx/HBVideo.m | 2 +- .../HandBrakeKit/de.lproj/Localizable.strings | 3 + .../Interop/Model/Encoding/Container.cs | 4 +- .../Converters/Audio/AudioEncoderConverter.cs | 6 +- .../Converters/Video/VideoEncoderConverter.cs | 6 +- win/CS/HandBrakeWPF/Helpers/AutoNameHelper.cs | 4 + .../Properties/Resources.Designer.cs | 22 ++++++ win/CS/HandBrakeWPF/Properties/Resources.resx | 10 +++ .../Encode/Model/Models/OutputFormat.cs | 4 + .../HandBrakeWPF/ViewModels/AudioViewModel.cs | 8 ++ .../Interfaces/ISubtitlesViewModel.cs | 10 +++ .../HandBrakeWPF/ViewModels/MainViewModel.cs | 21 +++++- .../ViewModels/StaticPreviewViewModel.cs | 16 +++- .../ViewModels/SubtitlesViewModel.cs | 74 ++++++++++++++++++- .../ViewModels/SummaryViewModel.cs | 19 ++--- .../HandBrakeWPF/ViewModels/VideoViewModel.cs | 9 ++- win/CS/HandBrakeWPF/Views/SubtitlesView.xaml | 8 +- win/CS/HandBrakeWPF/Views/SummaryView.xaml | 6 +- 31 files changed, 337 insertions(+), 52 deletions(-) diff --git a/gtk/src/callbacks.c b/gtk/src/callbacks.c index 60e4a2b1a..f8614d13c 100644 --- a/gtk/src/callbacks.c +++ b/gtk/src/callbacks.c @@ -1934,7 +1934,7 @@ dvd_source_activate_cb(GtkWidget *widget, signal_user_data_t *ud) void ghb_update_destination_extension(signal_user_data_t *ud) { - static gchar *containers[] = {".mkv", ".mp4", ".m4v", ".error", NULL}; + static gchar *containers[] = {".mkv", ".mp4", ".m4v", ".webm", ".error", NULL}; gchar *filename; const gchar *extension; gint ii; diff --git a/gtk/src/ghb.m4 b/gtk/src/ghb.m4 index d9dea4c8a..108a89aef 100644 --- a/gtk/src/ghb.m4 +++ b/gtk/src/ghb.m4 @@ -16,6 +16,7 @@ filter_output([ + diff --git a/gtk/src/hb-backend.c b/gtk/src/hb-backend.c index e433d50de..a638487f6 100644 --- a/gtk/src/hb-backend.c +++ b/gtk/src/hb-backend.c @@ -4298,6 +4298,8 @@ ghb_validate_video(GhbValue *settings, GtkWindow *parent) mux_id = ghb_dict_get_string(settings, "FileFormat"); mux = ghb_lookup_container_by_name(mux_id); + gboolean v_unsup = FALSE; + vcodec = ghb_settings_video_encoder_codec(settings, "VideoEncoder"); if ((mux->format & HB_MUX_MASK_MP4) && (vcodec == HB_VCODEC_THEORA)) { @@ -4306,6 +4308,21 @@ ghb_validate_video(GhbValue *settings, GtkWindow *parent) _("Theora is not supported in the MP4 container.\n\n" "You should choose a different video codec or container.\n" "If you continue, FFMPEG will be chosen for you.")); + v_unsup = TRUE; + } + else if ((mux->format & HB_MUX_MASK_WEBM) && + (vcodec != HB_VCODEC_FFMPEG_VP8 && vcodec != HB_VCODEC_FFMPEG_VP9)) + { + // webm only supports vp8 and vp9. + message = g_strdup_printf( + _("Only VP8 or VP9 is supported in the WebM container.\n\n" + "You should choose a different video codec or container.\n" + "If you continue, one will be chosen for you.")); + v_unsup = TRUE; + } + + if (v_unsup) + { if (!ghb_message_dialog(parent, GTK_MESSAGE_WARNING, message, _("Cancel"), _("Continue"))) { @@ -4317,6 +4334,7 @@ ghb_validate_video(GhbValue *settings, GtkWindow *parent) ghb_dict_set_string(settings, "VideoEncoder", hb_video_encoder_get_short_name(vcodec)); } + return TRUE; } @@ -4340,6 +4358,12 @@ ghb_validate_subtitles(GhbValue *settings, GtkWindow *parent) gint count, ii, track; gboolean burned, one_burned = FALSE; + const char *mux_id; + const hb_container_t *mux; + + mux_id = ghb_dict_get_string(settings, "FileFormat"); + mux = ghb_lookup_container_by_name(mux_id); + slist = ghb_get_job_subtitle_list(settings); count = ghb_array_len(slist); for (ii = 0; ii < count; ii++) @@ -4369,6 +4393,22 @@ ghb_validate_subtitles(GhbValue *settings, GtkWindow *parent) { one_burned = TRUE; } + else if (mux->format & HB_MUX_MASK_WEBM) + { + // WebM can only handle burned subs afaik. Their specs are ambiguous here + message = g_strdup_printf( + _("WebM in HandBrake only supports burned subtitles.\n\n" + "You should change your subtitle selections.\n" + "If you continue, some subtitles will be lost.")); + if (!ghb_message_dialog(parent, GTK_MESSAGE_WARNING, + message, _("Cancel"), _("Continue"))) + { + g_free(message); + return FALSE; + } + g_free(message); + break; + } if (import != NULL) { const gchar *filename; @@ -4457,6 +4497,10 @@ ghb_validate_audio(GhbValue *settings, GtkWindow *parent) { codec = HB_ACODEC_LAME; } + else if (mux->format & HB_MUX_MASK_WEBM) + { + codec = hb_audio_encoder_get_default(mux->format); + } else { codec = HB_ACODEC_FFAAC; @@ -4464,8 +4508,8 @@ ghb_validate_audio(GhbValue *settings, GtkWindow *parent) const char *name = hb_audio_encoder_get_short_name(codec); ghb_dict_set_string(asettings, "Encoder", name); } - gchar *a_unsup = NULL; - gchar *mux_s = NULL; + const gchar *a_unsup = NULL; + const gchar *mux_s = NULL; if (mux->format & HB_MUX_MASK_MP4) { mux_s = "MP4"; @@ -4476,6 +4520,16 @@ ghb_validate_audio(GhbValue *settings, GtkWindow *parent) codec = HB_ACODEC_FFAAC; } } + if (mux->format & HB_MUX_MASK_WEBM) + { + mux_s = "WebM"; + // WebM only supports Vorbis and Opus codecs + if (codec != HB_ACODEC_VORBIS && codec != HB_ACODEC_OPUS) + { + a_unsup = hb_audio_encoder_get_short_name(codec); + codec = hb_audio_encoder_get_default(mux->format); + } + } if (a_unsup) { message = g_strdup_printf( diff --git a/gtk/src/main.c b/gtk/src/main.c index e82360668..dd86cf242 100644 --- a/gtk/src/main.c +++ b/gtk/src/main.c @@ -1126,6 +1126,7 @@ ghb_activate_cb(GApplication * app, signal_user_data_t * ud) SourceFilterEVO SourceFilterVOB SourceFilterMKV + SourceFilterWebM SourceFilterMP4 SourceFilterAVI SourceFilterMOV @@ -1174,6 +1175,10 @@ ghb_activate_cb(GApplication * app, signal_user_data_t * ud) gtk_file_filter_add_pattern(filter, "*.mkv"); gtk_file_filter_add_pattern(filter, "*.MKV"); gtk_file_chooser_add_filter(chooser, filter); + filter = GTK_FILE_FILTER(GHB_OBJECT(ud->builder, "SourceFilterWebM")); + gtk_file_filter_set_name(filter, "WebM"); + gtk_file_filter_add_pattern(filter, "*.webm"); + gtk_file_chooser_add_filter(chooser, filter); filter = GTK_FILE_FILTER(GHB_OBJECT(ud->builder, "SourceFilterMP4")); gtk_file_filter_set_name(filter, "MP4"); gtk_file_filter_add_pattern(filter, "*.mp4"); diff --git a/libhb/common.c b/libhb/common.c index 26e7ff02a..7e3636d14 100644 --- a/libhb/common.c +++ b/libhb/common.c @@ -80,6 +80,7 @@ enum HB_GID_ACODEC_OPUS, HB_GID_MUX_MKV, HB_GID_MUX_MP4, + HB_GID_MUX_WEBM, }; #define HB_VIDEO_CLOCK 27000000 // 27MHz clock @@ -265,8 +266,8 @@ hb_encoder_internal_t hb_video_encoders[] = { { "H.265 (VideoToolbox)","vt_h265", "H.265 (libavcodec)", HB_VCODEC_FFMPEG_VT_H265, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H265, }, { { "MPEG-4", "mpeg4", "MPEG-4 (libavcodec)", HB_VCODEC_FFMPEG_MPEG4, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_MPEG4, }, { { "MPEG-2", "mpeg2", "MPEG-2 (libavcodec)", HB_VCODEC_FFMPEG_MPEG2, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_MPEG2, }, - { { "VP8", "VP8", "VP8 (libvpx)", HB_VCODEC_FFMPEG_VP8, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_VP8, }, - { { "VP9", "VP9", "VP9 (libvpx)", HB_VCODEC_FFMPEG_VP9, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_VP9, }, + { { "VP8", "VP8", "VP8 (libvpx)", HB_VCODEC_FFMPEG_VP8, HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_VP8, }, + { { "VP9", "VP9", "VP9 (libvpx)", HB_VCODEC_FFMPEG_VP9, HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_VP9, }, { { "Theora", "theora", "Theora (libtheora)", HB_VCODEC_THEORA, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_THEORA, }, }; int hb_video_encoders_count = sizeof(hb_video_encoders) / sizeof(hb_video_encoders[0]); @@ -375,11 +376,11 @@ hb_encoder_internal_t hb_audio_encoders[] = { { "DTS-HD Passthru", "copy:dtshd", "DTS-HD Passthru", HB_ACODEC_DCA_HD_PASS, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_DTSHD_PASS, }, { { "MP3", "mp3", "MP3 (libmp3lame)", HB_ACODEC_LAME, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_MP3, }, { { "MP3 Passthru", "copy:mp3", "MP3 Passthru", HB_ACODEC_MP3_PASS, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_MP3_PASS, }, - { { "Vorbis", "vorbis", "Vorbis (libvorbis)", HB_ACODEC_VORBIS, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_VORBIS, }, + { { "Vorbis", "vorbis", "Vorbis (libvorbis)", HB_ACODEC_VORBIS, HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_VORBIS, }, { { "FLAC 16-bit", "flac16", "FLAC 16-bit (libavcodec)", HB_ACODEC_FFFLAC, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_FLAC, }, { { "FLAC 24-bit", "flac24", "FLAC 24-bit (libavcodec)", HB_ACODEC_FFFLAC24, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_FLAC, }, { { "FLAC Passthru", "copy:flac", "FLAC Passthru", HB_ACODEC_FLAC_PASS, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_FLAC_PASS, }, - { { "Opus", "opus", "Opus (libopus)", HB_ACODEC_OPUS, HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_OPUS, }, + { { "Opus", "opus", "Opus (libopus)", HB_ACODEC_OPUS, HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_OPUS, }, { { "Auto Passthru", "copy", "Auto Passthru", HB_ACODEC_AUTO_PASS, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_ACODEC_AUTO_PASS, }, }; int hb_audio_encoders_count = sizeof(hb_audio_encoders) / sizeof(hb_audio_encoders[0]); @@ -443,14 +444,15 @@ hb_container_t *hb_containers_last_item = NULL; hb_container_internal_t hb_containers[] = { // legacy muxers, back to HB 0.9.4 whenever possible (disabled) - { { "M4V file", "m4v", NULL, "m4v", 0, }, NULL, 0, HB_GID_MUX_MP4, }, - { { "MP4 file", "mp4", NULL, "mp4", 0, }, NULL, 0, HB_GID_MUX_MP4, }, - { { "MKV file", "mkv", NULL, "mkv", 0, }, NULL, 0, HB_GID_MUX_MKV, }, + { { "M4V file", "m4v", NULL, "m4v", 0, }, NULL, 0, HB_GID_MUX_MP4, }, + { { "MP4 file", "mp4", NULL, "mp4", 0, }, NULL, 0, HB_GID_MUX_MP4, }, + { { "MKV file", "mkv", NULL, "mkv", 0, }, NULL, 0, HB_GID_MUX_MKV, }, // actual muxers - { { "MPEG-4 (avformat)", "av_mp4", "MPEG-4 (libavformat)", "mp4", HB_MUX_AV_MP4, }, NULL, 1, HB_GID_MUX_MP4, }, - { { "MPEG-4 (mp4v2)", "mp4v2", "MPEG-4 (libmp4v2)", "mp4", HB_MUX_MP4V2, }, NULL, 1, HB_GID_MUX_MP4, }, - { { "Matroska (avformat)", "av_mkv", "Matroska (libavformat)", "mkv", HB_MUX_AV_MKV, }, NULL, 1, HB_GID_MUX_MKV, }, - { { "Matroska (libmkv)", "libmkv", "Matroska (libmkv)", "mkv", HB_MUX_LIBMKV, }, NULL, 1, HB_GID_MUX_MKV, }, + { { "MPEG-4 (avformat)", "av_mp4", "MPEG-4 (libavformat)", "mp4", HB_MUX_AV_MP4, }, NULL, 1, HB_GID_MUX_MP4, }, + { { "MPEG-4 (mp4v2)", "mp4v2", "MPEG-4 (libmp4v2)", "mp4", HB_MUX_MP4V2, }, NULL, 1, HB_GID_MUX_MP4, }, + { { "Matroska (avformat)", "av_mkv", "Matroska (libavformat)", "mkv", HB_MUX_AV_MKV, }, NULL, 1, HB_GID_MUX_MKV, }, + { { "Matroska (libmkv)", "libmkv", "Matroska (libmkv)", "mkv", HB_MUX_LIBMKV, }, NULL, 1, HB_GID_MUX_MKV, }, + { { "WebM (avformat)", "av_webm", "WebM (libavformat)", "webm", HB_MUX_AV_WEBM, }, NULL, 1, HB_GID_MUX_WEBM, }, }; int hb_containers_count = sizeof(hb_containers) / sizeof(hb_containers[0]); static int hb_container_is_enabled(int format) @@ -459,6 +461,7 @@ static int hb_container_is_enabled(int format) { case HB_MUX_AV_MP4: case HB_MUX_AV_MKV: + case HB_MUX_AV_WEBM: return 1; default: @@ -4961,6 +4964,10 @@ int hb_subtitle_can_pass( int source, int mux ) return 0; } break; + // webm can't support subtitles unless they're burned. + // there's ambiguity in the spec about WebVTT... TODO + case HB_MUX_AV_WEBM: + return 0; default: // Internal error. Should never get here. hb_error("internal error. Bad mux %d\n", mux); diff --git a/libhb/common.h b/libhb/common.h index 0e1da8794..2d332d7fe 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -606,18 +606,22 @@ struct hb_job_s * mux: output file format * file: file path */ -#define HB_MUX_MASK 0xFF0001 -#define HB_MUX_INVALID 0x000000 -#define HB_MUX_MP4V2 0x010000 -#define HB_MUX_AV_MP4 0x020000 -#define HB_MUX_MASK_MP4 0x030000 -#define HB_MUX_LIBMKV 0x100000 -#define HB_MUX_AV_MKV 0x200000 -#define HB_MUX_MASK_MKV 0x300000 -#define HB_MUX_MASK_AV 0x220000 +#define HB_MUX_MASK 0xFF0001 +#define HB_MUX_INVALID 0x000000 +#define HB_MUX_MP4V2 0x010000 +#define HB_MUX_AV_MP4 0x020000 +#define HB_MUX_MASK_MP4 0x030000 +#define HB_MUX_LIBMKV 0x100000 +#define HB_MUX_AV_MKV 0x200000 +#define HB_MUX_AV_WEBM 0x400000 +#define HB_MUX_MASK_MKV 0x300000 +#define HB_MUX_MASK_AV 0x620000 +#define HB_MUX_MASK_WEBM 0x400000 + /* default muxer for each container */ -#define HB_MUX_MP4 HB_MUX_AV_MP4 -#define HB_MUX_MKV HB_MUX_AV_MKV +#define HB_MUX_MP4 HB_MUX_AV_MP4 +#define HB_MUX_MKV HB_MUX_AV_MKV +#define HB_MUX_WEBM HB_MUX_AV_WEBM int mux; char * file; diff --git a/libhb/decavcodec.c b/libhb/decavcodec.c index 957e2ae75..e9bf81a1d 100644 --- a/libhb/decavcodec.c +++ b/libhb/decavcodec.c @@ -853,7 +853,7 @@ static int decavcodecaBSInfo( hb_work_object_t *w, const hb_buffer_t *buf, { // Parse ADTS AAC streams for AudioSpecificConfig. // This data is required in order to write - // proper headers in MP4 and MKV files. + // proper headers in MP4, WebM, and MKV files. parse_adts_extradata(audio, context, &avp); } diff --git a/libhb/internal.h b/libhb/internal.h index 1284a1ff0..8983a13cd 100644 --- a/libhb/internal.h +++ b/libhb/internal.h @@ -493,6 +493,7 @@ typedef struct hb_mux_data_s hb_mux_data_t; DECLARE_MUX( mp4 ); DECLARE_MUX( mkv ); +DECLARE_MUX( webm ); DECLARE_MUX( avformat ); void hb_deinterlace(hb_buffer_t *dst, hb_buffer_t *src); diff --git a/libhb/muxavformat.c b/libhb/muxavformat.c index 2d4390f53..40df89c8e 100644 --- a/libhb/muxavformat.c +++ b/libhb/muxavformat.c @@ -70,6 +70,7 @@ enum { META_MUX_MP4, META_MUX_MKV, + META_MUX_WEBM, META_MUX_LAST }; @@ -98,6 +99,7 @@ static char* lookup_lang_code(int mux, char *iso639_2) out = iso639_2; break; case HB_MUX_AV_MKV: + case HB_MUX_AV_WEBM: // webm is a subset of mkv // MKV lang codes should be ISO-639-2B if it exists, // else ISO-639-2 lang = lang_for_code2( iso639_2 ); @@ -164,6 +166,15 @@ static int avformatInit( hb_mux_object_t * m ) meta_mux = META_MUX_MKV; break; + case HB_MUX_AV_WEBM: + // libavformat is essentially hard coded such that it only + // works with a timebase of 1/1000 + m->time_base.num = 1; + m->time_base.den = 1000; + muxer_name = "webm"; + meta_mux = META_MUX_WEBM; + break; + default: { hb_error("Invalid Mux %x", job->mux); @@ -1184,7 +1195,8 @@ static int avformatMux(hb_mux_object_t *m, hb_mux_data_t *track, hb_buffer_t *bu return 0; } - if (track->type == MUX_TYPE_VIDEO && (job->mux & HB_MUX_MASK_MKV) && + if (track->type == MUX_TYPE_VIDEO && + (job->mux & (HB_MUX_MASK_MKV | HB_MUX_MASK_WEBM)) && buf->s.renderOffset < 0) { // libav matroska muxer doesn't write dts to the output, but diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c index 5034b32e3..93b144a23 100644 --- a/libhb/muxcommon.c +++ b/libhb/muxcommon.c @@ -611,6 +611,7 @@ static int muxInit( hb_work_object_t * muxer, hb_job_t * job ) { case HB_MUX_AV_MP4: case HB_MUX_AV_MKV: + case HB_MUX_AV_WEBM: mux->m = hb_mux_avformat_init( job ); break; default: diff --git a/macosx/HBAudioTrack.m b/macosx/HBAudioTrack.m index a77305cf4..fd7865646 100644 --- a/macosx/HBAudioTrack.m +++ b/macosx/HBAudioTrack.m @@ -251,7 +251,7 @@ NSString *keyAudioTrackLanguageIsoCode = @"keyAudioTrackLanguageIsoCode"; } } - return HB_ACODEC_CA_AAC; + return hb_audio_encoder_get_default(self.container); } else { diff --git a/macosx/HBJob+UIAdditions.m b/macosx/HBJob+UIAdditions.m index 4c1cdc8ae..56c721271 100644 --- a/macosx/HBJob+UIAdditions.m +++ b/macosx/HBJob+UIAdditions.m @@ -78,6 +78,10 @@ static NSDictionary *shortHeightAttr; { title = HBKitLocalizedString(@"MKV File", @"HBJob -> Format display name"); } + else if (container->format & HB_MUX_MASK_WEBM) + { + title = HBKitLocalizedString(@"WebM File", @"HBJob -> Format display name"); + } else { title = @(container->name); @@ -922,6 +926,10 @@ static NSDictionary *shortHeightAttr; { return HBKitLocalizedString(@"MKV File", @"HBJob -> Format display name"); } + else if (container & HB_MUX_MASK_WEBM) + { + return HBKitLocalizedString(@"WebM File", @"HBJob -> Format display name"); + } else { const char *name = hb_container_get_name(container); @@ -951,6 +959,10 @@ static NSDictionary *shortHeightAttr; { return @(HB_MUX_AV_MKV); } + else if ([value isEqualToString:HBKitLocalizedString(@"WebM File", @"HBJob -> Format display name")]) + { + return @(HB_MUX_AV_WEBM); + } return @(hb_container_get_from_name([value UTF8String])); } diff --git a/macosx/HBPreviewGenerator.m b/macosx/HBPreviewGenerator.m index efc95e011..dde0ccd32 100644 --- a/macosx/HBPreviewGenerator.m +++ b/macosx/HBPreviewGenerator.m @@ -255,6 +255,10 @@ { destURL = [HBPreviewGenerator generateFileURLForType:@"mkv"]; } + else if (self.job.container & 0x400000 /*HB_MUX_MASK_WEBM*/) + { + destURL = [HBPreviewGenerator generateFileURLForType:@"webm"]; + } // return if we couldn't get the fileURL. if (!destURL) diff --git a/macosx/HBVideo.m b/macosx/HBVideo.m index a1fb9bd71..bf8c00d67 100644 --- a/macosx/HBVideo.m +++ b/macosx/HBVideo.m @@ -202,7 +202,7 @@ NSString * const HBVideoChangedNotification = @"HBVideoChangedNotification"; if (!encoderSupported) { - self.encoder = HB_VCODEC_X264; + self.encoder = hb_video_encoder_get_default(self.job.container); } } diff --git a/macosx/HandBrakeKit/de.lproj/Localizable.strings b/macosx/HandBrakeKit/de.lproj/Localizable.strings index d3a9eb993..5506abf4c 100644 --- a/macosx/HandBrakeKit/de.lproj/Localizable.strings +++ b/macosx/HandBrakeKit/de.lproj/Localizable.strings @@ -207,6 +207,9 @@ /* HBJob -> Format display name */ "MKV File" = "MKV-Datei"; +/* HBJob -> Format display name */ +"WebM File" = "WebM-Datei"; + /* HBJob -> Format display name */ "MP4 File" = "MP4-Datei"; diff --git a/win/CS/HandBrake.Interop/Interop/Model/Encoding/Container.cs b/win/CS/HandBrake.Interop/Interop/Model/Encoding/Container.cs index 8950d2b92..9ceeb6132 100644 --- a/win/CS/HandBrake.Interop/Interop/Model/Encoding/Container.cs +++ b/win/CS/HandBrake.Interop/Interop/Model/Encoding/Container.cs @@ -24,6 +24,8 @@ namespace HandBrake.Interop.Interop.Model.Encoding [DisplayName("MP4")] MP4, [DisplayName("MKV")] - MKV + MKV, + [DisplayName("WebM")] + WebM } } diff --git a/win/CS/HandBrakeWPF/Converters/Audio/AudioEncoderConverter.cs b/win/CS/HandBrakeWPF/Converters/Audio/AudioEncoderConverter.cs index 7216c6fe4..cc317c14c 100644 --- a/win/CS/HandBrakeWPF/Converters/Audio/AudioEncoderConverter.cs +++ b/win/CS/HandBrakeWPF/Converters/Audio/AudioEncoderConverter.cs @@ -64,7 +64,7 @@ namespace HandBrakeWPF.Converters.Audio encoders.Remove(AudioEncoder.fdkheaac); } - if (task != null && task.OutputFormat != OutputFormat.Mkv) + if (task != null && task.OutputFormat == OutputFormat.Mp4) { encoders.Remove(AudioEncoder.Vorbis); encoders.Remove(AudioEncoder.ffflac); @@ -74,6 +74,10 @@ namespace HandBrakeWPF.Converters.Audio encoders.Remove(AudioEncoder.TrueHDPassthrough); } + else if (task != null && task.OutputFormat == OutputFormat.WebM) + { + encoders.RemoveAll(ae => !(ae.Equals(AudioEncoder.Vorbis) || ae.Equals(AudioEncoder.Opus))); + } // Hide the Passthru options and show the "None" option if (parameter != null && parameter.ToString() == "True") diff --git a/win/CS/HandBrakeWPF/Converters/Video/VideoEncoderConverter.cs b/win/CS/HandBrakeWPF/Converters/Video/VideoEncoderConverter.cs index 12ce80eca..21db31b8a 100644 --- a/win/CS/HandBrakeWPF/Converters/Video/VideoEncoderConverter.cs +++ b/win/CS/HandBrakeWPF/Converters/Video/VideoEncoderConverter.cs @@ -79,12 +79,16 @@ namespace HandBrakeWPF.Converters.Video encoders.Remove(VideoEncoder.X265_12); } - if (task != null && task.OutputFormat != OutputFormat.Mkv) + if (task != null && task.OutputFormat == OutputFormat.Mp4) { encoders.Remove(VideoEncoder.Theora); encoders.Remove(VideoEncoder.VP8); encoders.Remove(VideoEncoder.VP9); } + else if (task != null && task.OutputFormat == OutputFormat.WebM) + { + encoders.RemoveAll(ve => !(ve.Equals(VideoEncoder.VP9) || ve.Equals(VideoEncoder.VP8))); + } if (!isQsvEnabled || !SystemInfo.IsQsvAvailableH264) { diff --git a/win/CS/HandBrakeWPF/Helpers/AutoNameHelper.cs b/win/CS/HandBrakeWPF/Helpers/AutoNameHelper.cs index 0b000eff7..fa6f71b96 100644 --- a/win/CS/HandBrakeWPF/Helpers/AutoNameHelper.cs +++ b/win/CS/HandBrakeWPF/Helpers/AutoNameHelper.cs @@ -172,6 +172,10 @@ namespace HandBrakeWPF.Helpers { destinationFilename += ".mkv"; } + else if (task.OutputFormat == OutputFormat.WebM) + { + destinationFilename += ".webm"; + } /* * File Destination Path diff --git a/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs b/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs index b9c05191f..54f78fe92 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs +++ b/win/CS/HandBrakeWPF/Properties/Resources.Designer.cs @@ -4616,6 +4616,28 @@ namespace HandBrakeWPF.Properties { } } + /// + /// Looks up a localized string similar to WebM in HandBrake only supports burned subtitles. + /// + ///You should change your subtitle selections. + /// + ///If you continue, your non-burned subtitles will be lost.. + /// + public static string Subtitles_WebmSubtitleIncompatibilityError { + get { + return ResourceManager.GetString("Subtitles_WebmSubtitleIncompatibilityError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to WebM Subtitle Compatibility. + /// + public static string Subtitles_WebmSubtitleIncompatibilityHeader { + get { + return ResourceManager.GetString("Subtitles_WebmSubtitleIncompatibilityHeader", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add Closed Captions when available. /// diff --git a/win/CS/HandBrakeWPF/Properties/Resources.resx b/win/CS/HandBrakeWPF/Properties/Resources.resx index 1cf4cfc4e..d6117ac7b 100644 --- a/win/CS/HandBrakeWPF/Properties/Resources.resx +++ b/win/CS/HandBrakeWPF/Properties/Resources.resx @@ -371,6 +371,16 @@ Please choose a different filename. Foreign Audio Track - The Foreign Audio track will be burned in if available. First Track - The first track will be burned in. Foreign Audio Preferred, else First - If the foreign audio track exists, it will be burned in, otherwise the first track will be chosen. + + + WebM Subtitle Compatibility + + + WebM in HandBrake only supports burned subtitles. + +You should change your subtitle selections. + +If you continue, your non-burned subtitles will be lost. No valid source or titles found. diff --git a/win/CS/HandBrakeWPF/Services/Encode/Model/Models/OutputFormat.cs b/win/CS/HandBrakeWPF/Services/Encode/Model/Models/OutputFormat.cs index dc4f00480..e9eb3c53a 100644 --- a/win/CS/HandBrakeWPF/Services/Encode/Model/Models/OutputFormat.cs +++ b/win/CS/HandBrakeWPF/Services/Encode/Model/Models/OutputFormat.cs @@ -23,5 +23,9 @@ namespace HandBrakeWPF.Services.Encode.Model.Models [DisplayName("MKV")] [ShortName("mkv")] Mkv, + + [DisplayName("WebM")] + [ShortName("webm")] + WebM } } diff --git a/win/CS/HandBrakeWPF/ViewModels/AudioViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/AudioViewModel.cs index 90a3d249f..9af13bc71 100644 --- a/win/CS/HandBrakeWPF/ViewModels/AudioViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/AudioViewModel.cs @@ -209,6 +209,14 @@ namespace HandBrakeWPF.ViewModels } } + if (this.Task.OutputFormat == OutputFormat.WebM) + { + foreach (AudioTrack track in this.Task.AudioTracks.Where(track => track.Encoder != AudioEncoder.Vorbis && track.Encoder != AudioEncoder.Opus)) + { + track.Encoder = AudioEncoder.Vorbis; + } + } + this.AudioDefaultsViewModel.RefreshTask(); } diff --git a/win/CS/HandBrakeWPF/ViewModels/Interfaces/ISubtitlesViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/Interfaces/ISubtitlesViewModel.cs index c5f0f5e3c..4889332ee 100644 --- a/win/CS/HandBrakeWPF/ViewModels/Interfaces/ISubtitlesViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/Interfaces/ISubtitlesViewModel.cs @@ -28,5 +28,15 @@ namespace HandBrakeWPF.ViewModels.Interfaces /// String array of files. /// void Import(string[] subtitleFiles); + + /// + /// Trigger a Notify Property Changed on the Task to force various UI elements to update. + /// + void RefreshTask(); + + /// + /// Checks the configuration of the subtitles and warns the user about any potential issues. + /// + bool ValidateSubtitles(); } } diff --git a/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs index de7070d3d..2f0baa730 100644 --- a/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs @@ -556,7 +556,7 @@ namespace HandBrakeWPF.ViewModels /// /// Gets RangeMode. /// - public IEnumerable OutputFormats => new List { OutputFormat.Mp4, OutputFormat.Mkv }; + public IEnumerable OutputFormats => new List { OutputFormat.Mp4, OutputFormat.Mkv, OutputFormat.WebM }; /// /// Gets or sets Destination. @@ -608,6 +608,9 @@ namespace HandBrakeWPF.ViewModels case ".m4v": this.SummaryViewModel.SetContainer(OutputFormat.Mp4); break; + case ".webm": + this.SummaryViewModel.SetContainer(OutputFormat.WebM); + break; } } else @@ -1154,6 +1157,7 @@ namespace HandBrakeWPF.ViewModels this.VideoViewModel.RefreshTask(); this.AudioViewModel.RefreshTask(); + this.SubtitleViewModel.RefreshTask(); } public void Shutdown() @@ -1377,6 +1381,12 @@ namespace HandBrakeWPF.ViewModels return new AddQueueError(Resources.Main_MatchingFileOverwriteWarning, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); } + // defer to subtitle's validation messages + if (!this.SubtitleViewModel.ValidateSubtitles()) + { + return false; + } + QueueTask task = new QueueTask(new EncodeTask(this.CurrentTask), HBConfigurationFactory.Create(), this.ScannedSource.ScanPath, this.SelectedPreset); if (!this.queueProcessor.CheckForDestinationPathDuplicates(task.Task.Destination)) @@ -1731,7 +1741,7 @@ namespace HandBrakeWPF.ViewModels { SaveFileDialog saveFileDialog = new SaveFileDialog { - Filter = "mp4|*.mp4;*.m4v|mkv|*.mkv", + Filter = "mp4|*.mp4;*.m4v|mkv|*.mkv|webm|*.webm", CheckPathExists = true, AddExtension = true, DefaultExt = ".mp4", @@ -1743,7 +1753,9 @@ namespace HandBrakeWPF.ViewModels saveFileDialog.FilterIndex = !string.IsNullOrEmpty(this.CurrentTask.Destination) && !string.IsNullOrEmpty(extension) ? (extension == ".mp4" || extension == ".m4v" ? 1 : 2) - : (this.CurrentTask.OutputFormat == OutputFormat.Mkv ? 2 : 0); + : (this.CurrentTask.OutputFormat == OutputFormat.Mkv + ? 2 + : (this.CurrentTask.OutputFormat == OutputFormat.WebM ? 3 : 0)); string mruDir = this.GetMru(Constants.FileSaveMru); if (!string.IsNullOrEmpty(mruDir)) @@ -1781,6 +1793,9 @@ namespace HandBrakeWPF.ViewModels case ".m4v": this.SummaryViewModel.SetContainer(OutputFormat.Mp4); break; + case ".webm": + this.SummaryViewModel.SetContainer(OutputFormat.WebM); + break; } this.NotifyOfPropertyChange(() => this.CurrentTask); diff --git a/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs index d8ad6e9b9..5a9c38383 100644 --- a/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/StaticPreviewViewModel.cs @@ -570,7 +570,21 @@ namespace HandBrakeWPF.ViewModels // Filename handling. if (string.IsNullOrEmpty(encodeTask.Destination)) { - string filename = Path.ChangeExtension(Path.GetTempFileName(), encodeTask.OutputFormat == OutputFormat.Mkv ? "m4v" : "mkv"); + string formatExtension; + switch (encodeTask.OutputFormat) + { + case OutputFormat.WebM: + formatExtension = "webm"; + break; + case OutputFormat.Mp4: + formatExtension = "m4v"; + break; + case OutputFormat.Mkv: + default: + formatExtension = "mkv"; + break; + } + string filename = Path.ChangeExtension(Path.GetTempFileName(), formatExtension); encodeTask.Destination = filename; this.CurrentlyPlaying = filename; } diff --git a/win/CS/HandBrakeWPF/ViewModels/SubtitlesViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/SubtitlesViewModel.cs index e164f1192..432e15f51 100644 --- a/win/CS/HandBrakeWPF/ViewModels/SubtitlesViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/SubtitlesViewModel.cs @@ -14,6 +14,7 @@ namespace HandBrakeWPF.ViewModels using System.IO; using System.Linq; using System.Runtime.CompilerServices; + using System.Windows; using Caliburn.Micro; @@ -22,6 +23,7 @@ namespace HandBrakeWPF.ViewModels using HandBrakeWPF.EventArgs; using HandBrakeWPF.Model.Subtitles; using HandBrakeWPF.Properties; + using HandBrakeWPF.Services.Interfaces; using HandBrakeWPF.Services.Presets.Model; using HandBrakeWPF.Services.Scan.Model; using HandBrakeWPF.ViewModels.Interfaces; @@ -38,6 +40,7 @@ namespace HandBrakeWPF.ViewModels /// public class SubtitlesViewModel : ViewModelBase, ISubtitlesViewModel { + private readonly IErrorService errorService; private readonly IWindowManager windowManager; #region Constants and Fields @@ -52,11 +55,15 @@ namespace HandBrakeWPF.ViewModels /// /// Initializes a new instance of the class. /// + /// + /// The Error Service + /// /// /// The window Manager. /// - public SubtitlesViewModel(IWindowManager windowManager) + public SubtitlesViewModel(IErrorService errorService, IWindowManager windowManager) { + this.errorService = errorService; this.windowManager = windowManager; this.SubtitleDefaultsViewModel = new SubtitlesDefaultsViewModel(); this.Task = new EncodeTask(); @@ -144,6 +151,14 @@ namespace HandBrakeWPF.ViewModels } } + public bool IsBurnableOnly + { + get + { + return this.Task.OutputFormat == OutputFormat.WebM; + } + } + #endregion #region Public Methods @@ -453,6 +468,26 @@ namespace HandBrakeWPF.ViewModels this.AutomaticSubtitleSelection(); } + /// + /// Trigger a Notify Property Changed on the Task to force various UI elements to update. + /// + public void RefreshTask() + { + this.NotifyOfPropertyChange(() => this.Task); + this.NotifyOfPropertyChange(() => this.IsBurnableOnly); + + if (this.IsBurnableOnly) + { + foreach (var subtitleTrack in this.Task.SubtitleTracks) + { + if (subtitleTrack.Default) + { + subtitleTrack.Default = false; + } + } + } + } + #endregion #region Implemented Interfaces @@ -553,6 +588,43 @@ namespace HandBrakeWPF.ViewModels this.AutomaticSubtitleSelection(); } + /// + /// Checks the configuration of the subtitles and warns the user about any potential issues. + /// + public bool ValidateSubtitles() + { + var nonBurnedSubtitles = this.Task.SubtitleTracks.Where(subtitleTrack => !subtitleTrack.Burned).ToList(); + + if (nonBurnedSubtitles.Count > 0 && this.IsBurnableOnly) + { + MessageBoxResult result = this.errorService.ShowMessageBox( + Resources.Subtitles_WebmSubtitleIncompatibilityError, + Resources.Subtitles_WebmSubtitleIncompatibilityHeader, + MessageBoxButton.OKCancel, + MessageBoxImage.Warning); + if (result == MessageBoxResult.OK) + { + foreach (var subtitleTrack in nonBurnedSubtitles) + { + if (!subtitleTrack.Burned) + { + this.Remove(subtitleTrack); + } + } + } + else if (result == MessageBoxResult.Cancel) + { + return false; + } + else + { + return false; + } + } + + return true; + } + #endregion #region Methods diff --git a/win/CS/HandBrakeWPF/ViewModels/SummaryViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/SummaryViewModel.cs index 0da1fc70b..b5f711e5a 100644 --- a/win/CS/HandBrakeWPF/ViewModels/SummaryViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/SummaryViewModel.cs @@ -123,7 +123,7 @@ namespace HandBrakeWPF.ViewModels { return new List { - OutputFormat.Mp4, OutputFormat.Mkv + OutputFormat.Mp4, OutputFormat.Mkv, OutputFormat.WebM }; } } @@ -211,8 +211,9 @@ namespace HandBrakeWPF.ViewModels this.Task.OutputFormat = value; this.NotifyOfPropertyChange(() => this.SelectedOutputFormat); this.NotifyOfPropertyChange(() => this.Task.OutputFormat); - this.NotifyOfPropertyChange(() => this.IsMkv); + this.NotifyOfPropertyChange(() => this.IsMkvOrWebm); this.SetExtension(string.Format(".{0}", this.Task.OutputFormat.ToString().ToLower())); + this.UpdateDisplayedInfo(); // output format may coreced to another due to container incompatibility this.OnOutputFormatChanged(new OutputFormatChangedEventArgs(null)); this.OnTabStatusChanged(null); @@ -221,13 +222,13 @@ namespace HandBrakeWPF.ViewModels } /// - /// Gets or sets a value indicating whether IsMkv. + /// Gets or sets a value indicating whether IsMkvOrWebm. /// - public bool IsMkv + public bool IsMkvOrWebm { get { - return this.SelectedOutputFormat == OutputFormat.Mkv; + return this.SelectedOutputFormat == OutputFormat.Mkv || this.SelectedOutputFormat == OutputFormat.WebM; } } @@ -316,8 +317,8 @@ namespace HandBrakeWPF.ViewModels this.UpdateDisplayedInfo(); this.NotifyOfPropertyChange(() => this.SelectedOutputFormat); - this.NotifyOfPropertyChange(() => this.IsMkv); - + this.NotifyOfPropertyChange(() => this.IsMkvOrWebm); + this.NotifyOfPropertyChange(() => this.OptimizeMP4); this.NotifyOfPropertyChange(() => this.IPod5GSupport); this.NotifyOfPropertyChange(() => this.AlignAVStart); @@ -454,14 +455,14 @@ namespace HandBrakeWPF.ViewModels } // Now disable controls that are not required. The Following are for MP4 only! - if (newExtension == ".mkv") + if (newExtension == ".mkv" || newExtension == ".webm") { this.OptimizeMP4 = false; this.IPod5GSupport = false; this.AlignAVStart = false; } - this.NotifyOfPropertyChange(() => this.IsMkv); + this.NotifyOfPropertyChange(() => this.IsMkvOrWebm); // Update The browse file extension display if (Path.HasExtension(newExtension)) diff --git a/win/CS/HandBrakeWPF/ViewModels/VideoViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/VideoViewModel.cs index 72b74ffa1..3280b9424 100644 --- a/win/CS/HandBrakeWPF/ViewModels/VideoViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/VideoViewModel.cs @@ -1115,10 +1115,17 @@ namespace HandBrakeWPF.ViewModels { this.NotifyOfPropertyChange(() => this.Task); - if ((Task.OutputFormat == OutputFormat.Mp4) && this.SelectedVideoEncoder == VideoEncoder.Theora) + VideoEncoder[] allowableWebmEncoders = { VideoEncoder.VP8, VideoEncoder.VP9 }; + + if ((Task.OutputFormat == OutputFormat.Mp4) && (this.SelectedVideoEncoder == VideoEncoder.Theora || allowableWebmEncoders.Contains(this.SelectedVideoEncoder))) { this.SelectedVideoEncoder = VideoEncoder.X264; } + + if ((Task.OutputFormat == OutputFormat.WebM) && !allowableWebmEncoders.Contains(this.SelectedVideoEncoder)) + { + this.SelectedVideoEncoder = VideoEncoder.VP8; + } } /// diff --git a/win/CS/HandBrakeWPF/Views/SubtitlesView.xaml b/win/CS/HandBrakeWPF/Views/SubtitlesView.xaml index 01195259a..1fedb3962 100644 --- a/win/CS/HandBrakeWPF/Views/SubtitlesView.xaml +++ b/win/CS/HandBrakeWPF/Views/SubtitlesView.xaml @@ -11,12 +11,14 @@ xmlns:splitButton="clr-namespace:HandBrakeWPF.Controls.SplitButton" xmlns:Properties="clr-namespace:HandBrakeWPF.Properties" xmlns:subtitles="clr-namespace:HandBrakeWPF.Converters.Subtitles" + xmlns:viewModels="clr-namespace:HandBrakeWPF.ViewModels" d:DesignHeight="350" d:DesignWidth="500" mc:Ignorable="d" x:Name="subTab"> +