From d0e37ca0d75fbcef5bdf47aafe8543e262314ec5 Mon Sep 17 00:00:00 2001 From: Justin Bull Date: Wed, 13 Feb 2019 09:52:48 -0500 Subject: 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. --- .../Converters/Audio/AudioEncoderConverter.cs | 6 +- .../Converters/Video/VideoEncoderConverter.cs | 6 +- win/CS/HandBrakeWPF/Helpers/AutoNameHelper.cs | 4 ++ .../HandBrakeWPF/Properties/Resources.Designer.cs | 22 +++++++ win/CS/HandBrakeWPF/Properties/Resources.resx | 10 +++ .../Services/Encode/Model/Models/OutputFormat.cs | 4 ++ win/CS/HandBrakeWPF/ViewModels/AudioViewModel.cs | 8 +++ .../ViewModels/Interfaces/ISubtitlesViewModel.cs | 10 +++ win/CS/HandBrakeWPF/ViewModels/MainViewModel.cs | 21 +++++- .../ViewModels/StaticPreviewViewModel.cs | 16 ++++- .../HandBrakeWPF/ViewModels/SubtitlesViewModel.cs | 74 +++++++++++++++++++++- win/CS/HandBrakeWPF/ViewModels/SummaryViewModel.cs | 19 +++--- win/CS/HandBrakeWPF/ViewModels/VideoViewModel.cs | 9 ++- win/CS/HandBrakeWPF/Views/SubtitlesView.xaml | 8 ++- win/CS/HandBrakeWPF/Views/SummaryView.xaml | 6 +- 15 files changed, 201 insertions(+), 22 deletions(-) (limited to 'win/CS/HandBrakeWPF') 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"> +