From 78351ffc2b10a21c21d586dd6e242da0464738f7 Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Fri, 29 Mar 2024 08:06:13 +0100 Subject: [PATCH] Add some support for PGS in mkv for "embed subs" --- LanguageBaseEnglish.xml | 1 + src/ui/Forms/GenerateVideoWithSoftSubs.cs | 97 ++++++++++++--- src/ui/Logic/LanguageDeserializer.cs | 3 + src/ui/Logic/VideoPreviewGenerator.cs | 138 +++++++++++++++++----- src/ui/Logic/VideoPreviewGeneratorSub.cs | 3 +- 5 files changed, 190 insertions(+), 52 deletions(-) diff --git a/LanguageBaseEnglish.xml b/LanguageBaseEnglish.xml index 22eff82f8..63148818a 100644 --- a/LanguageBaseEnglish.xml +++ b/LanguageBaseEnglish.xml @@ -1029,6 +1029,7 @@ We leverage the intrinsic rhythm of the image. Toggle default Default "{0}" generated with embedded subtitles + Delete input video file after "Generate" Need dictionaries? diff --git a/src/ui/Forms/GenerateVideoWithSoftSubs.cs b/src/ui/Forms/GenerateVideoWithSoftSubs.cs index 76f75ae6c..e63fa98db 100644 --- a/src/ui/Forms/GenerateVideoWithSoftSubs.cs +++ b/src/ui/Forms/GenerateVideoWithSoftSubs.cs @@ -26,6 +26,7 @@ namespace Nikse.SubtitleEdit.Forms private string _inputVideoFileName; private StringBuilder _log; private readonly List _softSubs = new List(); + private readonly List _tracksToDelete = new List(); private bool _promptFFmpegParameters; private readonly List _cleanUpFolders = new List(); @@ -132,7 +133,6 @@ namespace Nikse.SubtitleEdit.Forms _inputVideoFileName = inputVideoFileName; _videoInfo = videoInfo; - using (var matroska = new MatroskaFile(inputVideoFileName)) { if (matroska.IsValid) @@ -159,6 +159,8 @@ namespace Nikse.SubtitleEdit.Forms listViewSubtitles.EndUpdate(); } + + _tracksToDelete.Clear(); } private void AddListViewItem(Trak track) @@ -185,7 +187,7 @@ namespace Nikse.SubtitleEdit.Forms var item = new ListViewItem { Tag = sub, - Text = sub.SubtitleFormat != null ? sub.SubtitleFormat.Name : sub.Format, + Text = sub.SubtitleFormat ?? sub.Format, }; item.SubItems.Add(GetDisplayLanguage(sub.Language)); item.SubItems.Add(sub.IsDefault.ToString(CultureInfo.InvariantCulture)); @@ -221,8 +223,24 @@ namespace Nikse.SubtitleEdit.Forms private void AddListViewItem(MatroskaTrackInfo track, MatroskaFile matroska) { + if (track.CodecId.Equals("S_HDMV/PGS", StringComparison.OrdinalIgnoreCase)) + { + AddListViewItem(new VideoPreviewGeneratorSub + { + Name = track.CodecId, + Language = track.Language, + IsNew = false, + IsForced = track.IsForced, + IsDefault = track.IsDefault, + Tag = track, + SubtitleFormat = track.CodecId, + }); + + return; + } + + if (track.CodecId.Equals("S_VOBSUB", StringComparison.OrdinalIgnoreCase) || - track.CodecId.Equals("S_HDMV/PGS", StringComparison.OrdinalIgnoreCase) || track.CodecId.Equals("S_HDMV/TEXTST", StringComparison.OrdinalIgnoreCase) || track.CodecId.Equals("S_DVBSUB", StringComparison.OrdinalIgnoreCase)) { @@ -253,7 +271,7 @@ namespace Nikse.SubtitleEdit.Forms IsForced = track.IsForced, IsDefault = track.IsDefault, Tag = track, - SubtitleFormat = format, + SubtitleFormat = format.GetType().Name, FileName = fileName, }); } @@ -268,19 +286,17 @@ namespace Nikse.SubtitleEdit.Forms if (fileName.EndsWith(".sup", StringComparison.OrdinalIgnoreCase) && FileUtil.IsBluRaySup(fileName)) { - MessageBox.Show("FFmpeg does not support embedding of PGS/Blu-ray sup :("); - - //AddListViewItem(new VideoPreviewGeneratorSub - //{ - // Name = Path.GetFileName(fileName), - // Language = "eng", //TODO: get from file name or sup - // Format = "Blu-ray sup", - // SubtitleFormat = null, - // IsNew = true, - // IsForced = false, - // IsDefault = false, - // FileName = fileName, - //}); + AddListViewItem(new VideoPreviewGeneratorSub + { + Name = Path.GetFileName(fileName), + Language = GetLanguageFromFileName(fileName), + Format = "Blu-ray sup", + SubtitleFormat = null, + IsNew = true, + IsForced = false, + IsDefault = false, + FileName = fileName, + }); return; } @@ -301,7 +317,7 @@ namespace Nikse.SubtitleEdit.Forms Name = Path.GetFileName(fileName), Language = LanguageAutoDetect.AutoDetectGoogleLanguage(subtitle), Format = subtitle.OriginalFormat.FriendlyName, - SubtitleFormat = subtitle.OriginalFormat, + SubtitleFormat = subtitle.OriginalFormat.GetType().Name, IsNew = true, IsForced = false, IsDefault = false, @@ -309,6 +325,41 @@ namespace Nikse.SubtitleEdit.Forms }); } + private static string GetLanguageFromFileName(string fileName) + { + var defaultLanguage = "eng"; + + if (string.IsNullOrEmpty(fileName)) + { + return defaultLanguage; + } + + var fileNameNoExt = Path.GetFileNameWithoutExtension(fileName); + var split = fileNameNoExt.Split('.', '_', '_'); + var last = split.LastOrDefault(); + + if (last == null) + { + return defaultLanguage; + } + + if (last.Length == 3) + { + return last; + } + + if (last.Length == 2) + { + var threeLetterCode = Iso639Dash2LanguageCode.GetThreeLetterCodeFromTwoLetterCode(last); + if (threeLetterCode.Length == 3) + { + return threeLetterCode; + } + } + + return defaultLanguage; + } + private string GetKnownFileNameOrConvertToSrtOrUtf8(string fileName, Subtitle subtitle) { SubtitleFormat targetFormat = new SubRip(); @@ -390,6 +441,13 @@ namespace Nikse.SubtitleEdit.Forms VideoFileName = saveDialog.FileName; } + var isMp4 = VideoFileName.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase); + if (isMp4 && _softSubs.Any(p => p.SubtitleFormat == "S_HDMV/PGS")) + { + MessageBox.Show("Cannot embed S_HDMV/PGS in MP4"); + return; + } + if (File.Exists(VideoFileName)) { try @@ -550,6 +608,7 @@ namespace Nikse.SubtitleEdit.Forms return VideoPreviewGenerator.GenerateSoftCodedVideoFile( inputVideoFileName, _softSubs, + _tracksToDelete, outputVideoFileName, OutputHandler); } @@ -756,6 +815,7 @@ namespace Nikse.SubtitleEdit.Forms foreach (var index in list.OrderByDescending(p => p)) { + _tracksToDelete.Add(_softSubs[index]); _softSubs.RemoveAt(index); listViewSubtitles.Items.RemoveAt(index); } @@ -780,6 +840,7 @@ namespace Nikse.SubtitleEdit.Forms private void buttonClear_Click(object sender, EventArgs e) { listViewSubtitles.Items.Clear(); + _tracksToDelete.AddRange(_softSubs); _softSubs.Clear(); labelSubtitles.Text = string.Format(LanguageSettings.Current.GenerateVideoWithEmbeddedSubs.SubtitlesX, listViewSubtitles.Items.Count); } diff --git a/src/ui/Logic/LanguageDeserializer.cs b/src/ui/Logic/LanguageDeserializer.cs index e502d35c8..de6dab80e 100644 --- a/src/ui/Logic/LanguageDeserializer.cs +++ b/src/ui/Logic/LanguageDeserializer.cs @@ -2716,6 +2716,9 @@ namespace Nikse.SubtitleEdit.Logic case "GenerateVideoWithEmbeddedSubs/XGeneratedWithEmbeddedSubs": language.GenerateVideoWithEmbeddedSubs.XGeneratedWithEmbeddedSubs = reader.Value; break; + case "GenerateVideoWithEmbeddedSubs/DeleteInputVideo": + language.GenerateVideoWithEmbeddedSubs.DeleteInputVideo = reader.Value; + break; case "GetDictionaries/Title": language.GetDictionaries.Title = reader.Value; break; diff --git a/src/ui/Logic/VideoPreviewGenerator.cs b/src/ui/Logic/VideoPreviewGenerator.cs index 012bbdfcb..c54eab1c8 100644 --- a/src/ui/Logic/VideoPreviewGenerator.cs +++ b/src/ui/Logic/VideoPreviewGenerator.cs @@ -1,5 +1,4 @@ using Nikse.SubtitleEdit.Core.Common; -using Nikse.SubtitleEdit.Core.SubtitleFormats; using Nikse.SubtitleEdit.Forms; using System; using System.Collections.Generic; @@ -9,6 +8,7 @@ using System.Drawing.Imaging; using System.Globalization; using System.IO; using System.Linq; +using Nikse.SubtitleEdit.Core.ContainerFormats.Matroska; namespace Nikse.SubtitleEdit.Logic { @@ -355,18 +355,116 @@ namespace Nikse.SubtitleEdit.Logic return ffmpegLocation; } - public static Process GenerateSoftCodedVideoFile(string inputVideoFileName, List softSubs, string outputVideoFileName, DataReceivedEventHandler outputHandler) + public static Process GenerateSoftCodedVideoFile(string inputVideoFileName, List softSubs, List softSubsToDelete, string outputVideoFileName, DataReceivedEventHandler outputHandler) + { + var isMp4 = outputVideoFileName.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase); + if (isMp4) + { + return GenerateSoftCodedVideoFileMp4(inputVideoFileName, softSubs, outputVideoFileName, outputHandler); + } + + var subsInput = string.Empty; + var subsMeta = string.Empty; + var map = "-map 0"; + + foreach (var trackToDelete in softSubsToDelete) + { + if (!trackToDelete.IsNew && trackToDelete.Tag is MatroskaTrackInfo trackInfo) + { + map += $" -map -0:{trackInfo.TrackNumber - 1}"; + } + } + + var count = 1; + var number = 0; + foreach (var softSub in softSubs.Where(p => p.IsNew)) + { + map += $" -map {count}"; + + subsInput += $" -i \"{softSub.FileName}\""; + + if (!string.IsNullOrEmpty(softSub.Language)) + { + var lang = string.IsNullOrEmpty(softSub.Language) ? string.Empty : softSub.Language.ToLowerInvariant(); + var threeLetterCode = Iso639Dash2LanguageCode.GetThreeLetterCodeFromTwoLetterCode(lang); + if (lang.Length == 3) + { + threeLetterCode = lang; + } + else if (lang.IndexOf('-') == 2) + { + threeLetterCode = Iso639Dash2LanguageCode.GetThreeLetterCodeFromTwoLetterCode(lang.Substring(0, 2)); + } + + var languageName = Iso639Dash2LanguageCode.List.FirstOrDefault(p => p.ThreeLetterCode == threeLetterCode)?.EnglishName; + if (languageName == null) + { + languageName = Iso639Dash2LanguageCode.List.FirstOrDefault(p => p.TwoLetterCode == lang || p.EnglishName.ToLowerInvariant() == lang)?.EnglishName; + } + + if (!string.IsNullOrEmpty(threeLetterCode) && !string.IsNullOrEmpty(languageName)) + { + subsMeta += $" -metadata:s:s:{number} language=\"{threeLetterCode}\""; + subsMeta += $" -metadata:s:s:{number} title=\"{languageName}\""; + } + else if (!string.IsNullOrEmpty(softSub.Language)) + { + subsMeta += $" -metadata:s:s:{number} language=\"{softSub.Language}\""; + subsMeta += $" -metadata:s:s:{number} title=\"{softSub.Language}\""; + } + } + + if (softSub.IsDefault) + { + subsMeta += $" -disposition:s:s:{number} default"; + } + + if (softSub.IsForced) + { + subsMeta += $" -disposition:s:s:{number} forced"; + subsMeta += $" -metadata:s:s:{number} forced=1"; + } + + count++; + number++; + } + + subsInput = " " + subsInput.Trim(); + if (subsInput.Trim().Length == 0) + { + subsInput = string.Empty; + } + + subsMeta = " " + subsMeta.Trim(); + if (subsMeta.Trim().Length == 0) + { + subsMeta = string.Empty; + } + + var arguments = $"-i \"{inputVideoFileName}\" {subsInput.Trim()} {map.Trim()} -c copy {subsMeta.Trim()} \"{outputVideoFileName}\"".TrimStart(); + var processMakeVideo = new Process + { + StartInfo = + { + FileName = GetFfmpegLocation(), + Arguments = arguments, + UseShellExecute = false, + CreateNoWindow = true, + } + }; + + processMakeVideo.StartInfo.Arguments = processMakeVideo.StartInfo.Arguments.Trim(); + SetupDataReceiveHandler(outputHandler, processMakeVideo); + return processMakeVideo; + } + + private static Process GenerateSoftCodedVideoFileMp4(string inputVideoFileName, List softSubs, string outputVideoFileName, DataReceivedEventHandler outputHandler) { var subsInput = string.Empty; var subsMap = string.Empty; var subsMeta = string.Empty; var subsFormat = string.Empty; - var ffmpegInfo = FfmpegMediaInfo.Parse(inputVideoFileName); - var audioTrackCount = ffmpegInfo.Tracks.Count(p => p.TrackType == FfmpegTrackType.Audio); - - var isMp4 = outputVideoFileName.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase); - var count = 1; var number = 0; foreach (var softSub in softSubs) @@ -416,31 +514,7 @@ namespace Nikse.SubtitleEdit.Logic subsMeta += $" -metadata:s:s:{number} forced=1"; } - if (isMp4) - { - subsFormat = " -c:s mov_text"; - } - else if (softSub.SubtitleFormat == null && softSub.Format == "Blu-ray sup") - { - subsFormat += $" -c:s:s:{number} copy"; // should be "pgs" or "pgssub" or ? - } - else if (softSub.SubtitleFormat?.GetType() == typeof(SubRip)) - { - subsFormat += $" -c:s:s:{number} srt"; - } - else if (softSub.SubtitleFormat?.GetType() == typeof(AdvancedSubStationAlpha)) - { - subsFormat += $" -c:s:s:{number} ass"; - } - else if (softSub.SubtitleFormat?.GetType() == typeof(SubStationAlpha)) - { - subsFormat += $" -c:s:s:{number} ssa"; - } - else if (softSub.SubtitleFormat?.GetType() == typeof(WebVTT) || - softSub.SubtitleFormat?.GetType() == typeof(WebVTTFileWithLineNumber)) - { - subsFormat += $" -c:s:s:{number} webvtt"; - } + subsFormat = " -c:s mov_text"; count++; number++; diff --git a/src/ui/Logic/VideoPreviewGeneratorSub.cs b/src/ui/Logic/VideoPreviewGeneratorSub.cs index 4f9d6849a..e6f80eedb 100644 --- a/src/ui/Logic/VideoPreviewGeneratorSub.cs +++ b/src/ui/Logic/VideoPreviewGeneratorSub.cs @@ -1,5 +1,4 @@ using Nikse.SubtitleEdit.Core.Common; -using Nikse.SubtitleEdit.Core.SubtitleFormats; namespace Nikse.SubtitleEdit.Logic { @@ -9,7 +8,7 @@ namespace Nikse.SubtitleEdit.Logic public bool IsNew { get; set; } public string FileName { get; set; } public Subtitle Subtitle { get; set; } - public SubtitleFormat SubtitleFormat { get; set; } + public string SubtitleFormat { get; set; } public string Format { get; set; } public string Language { get; set; } public bool IsForced { get; set; }