From bb2a698d68d54a8cddca34c0e0f7490e59b4abaa Mon Sep 17 00:00:00 2001 From: John Andrews Date: Mon, 15 Apr 2024 08:58:05 +1200 Subject: [PATCH] FF-1481 - improved subtitle track merge detection --- FileFlows.Plugin.dll | Bin 134144 -> 134144 bytes FileFlows.Plugin.pdb | Bin 32552 -> 32564 bytes VideoNodes/ExtensionMethods.cs | 105 +++++++++--------- .../FfmpegBuilderSubtitleTrackMerge.cs | 65 +++++++++-- .../FfmpegBuilder_BasicTests.cs | 21 +++- VideoNodes/VideoNodes/VideoNode.cs | 32 +++--- 6 files changed, 138 insertions(+), 85 deletions(-) diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll index ae2298e189135ebcd3b902e7a0717780ad43ff58..fa793002134daea462bd444df5a8a1f4ff7cfe33 100644 GIT binary patch delta 27600 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 aaM@naeWezqELMIhY4q5YZ^ZH<>;DHDzj}56 delta 27445 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 HV*dXCdbdbn diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb index 0f91d51731348f647c2fddfee606912686861755..e1f43fd77595594a01f8ac0910c5e056165aa047 100644 GIT binary patch delta 2864 zcmZwH2~<;88VB(2W+MU8u!JaOHS7<9AWMjXvIMIJOd!?~R|FAdQ7PJyDNe8nXi-O< zXE|LQ7mh8XRvnEnv^q*#m{xI_inSedsuZnO87AKSVml7%1sI6xq% zP2#I0N=q1xP7b4mPzRgfJ!nbB`4mq6+HeX14&t?rz9>r3M$sgg3&l_dRj?W?&;XlY z8@vzAuopU^8?HF#;izBB(QOODAUuK*kfd-V2POD{8ls>FdZ7<)!95rPcO6IG@G^{n zNEi#rkOq1%Kt2>eA(X;0SOu0^1S>ScR(J2*1gfnmvuE2E|fS=)45RJ#p zfD-&66k;F|Qb7+nFazeoBB&V8S!gAaTCl-J*aq8S4}1hi;W%`{W$1@H@Bn@R;RKG{ zz#IG^1fn4UCcvvO4@%$++y}o@jlB1gyIOBg+gqhNRs zy5JkQ3qz2Zjwge~@DN78B_o;?pn?Dhg=iQDx(vr!!q3wgV<`Pu2k~{{V0R5!GBniS z?BqWZM_M%0gO1+kM>$VJ&`=-x<(ZCJL&{7IMgK3KkGu)aWyVrJ+=fAT1S8;+6-)jQ z0vyCc3YcL*Ry>u1Nbi_8jzWF)aTE>x+Bh2L>>Nuv{a8xZV-qds>m+jimL%O0L4Vap z&Nn4o7_lcawoM(PO*>#sgMPO zS~*Qan`e57DoqNiF?mqE$rBIZiHGo{ttKyO20MIW@}^HsO3Z6fVqztoHTlq0lL}8c zitd<3(?e4j3G>3p4ZOh*LLeFvU;<=;0n9Kb@9E1X3ZvrZIY%webH_MGhyJfd7uKRU zNJE-@4JAQl{oJxl zdRj|Sw+M)O!7=WN+G=NBA1f_?aV|7QBS{z|AuyVfjO$<$M?P6AMJsdW`%~P|x`Tv% z$v_{jQIPF{+7rCM6n^&R6ecA7faVHQDR%2q_++%7~Hl6z&5sED!@#dw}9 zh1D59N6b+4JEN6}C{59iI6)y4h)Aap3Pq$@uBxcl0T>^YYzpXg34Yq+~D{ZjKkE=(1qcAt;>{vu@)R9pDXy&(l4= zxHqiKGBzeo4LZEZXSa$Av|Ui8tzUQcZfL=`1E(j(3=b{ar_p4e8e?yHe12eg;F-yJ zC(Uln(z|cj_bu3^&y6{nok^Cx$eP&tC_Fv)L*JDPddN7YyWo_ z&M%I-c+M8x=NfByAp6tC*`@QAcKNr?+gROwpd&VY zh-j<&8JrxhTfB35E(`&ia8{AIbHsm(Cejw&*{~jkj!Pr>F59#Jd>Qds15_L(% zi6tevMM+6l9IZPSbH36pTgQpS^H|%zSX|Dm=^}^ z$3mB_T_=-bEi19v7NwtABqbLT&{L+eqpPn5WD5#VZ6V#}% zmIBs3mdnUfRKu_@BeAgsd3E)UEhi`0n=<}!>6^^J*Ddj5bNKB`eA!aIZOKe_jh8Q7 z{L&Jchhr8aHl)&8&97Z*lxX0QR z8SaiPuCdK^w&QJ!`Hu3R6js(j8MmDO#smBb_LCJuj78bomd#?mFM6wZpKVe0h!r(1 kA`Ca#_x$!(fy|L%9%HY4@9(f8IQx=Xn}8+q|Ng!1zauB#BLDyZ delta 2839 zcmZwHdr(tX9tZH>Ndig05WpmYys|(51Cci|h)PJPB98=7M_sK-5EP%xiiqnP5oC2* zSM)m6*V=8Zt*b?GBZ9hGw05XxYIP9zG;Sg^Q|h<@5MDkt|@z4?={ngg-z-`(l>;^EFsbx}-n=xC5Y zusoS7b640kq)5@w3|I;4p&oXpU_3>TpFV;@AOduH{@gH1(hsANFa>5pF)W0o@JCn! z>!A)lfnBf%TH!3Tx#rPQhh9rR*%2PYGjM`ngqEb>4MQLpH1Gr5fVHG%;f|Nk9%N2!e2kHuCQh4u4mSp@erQNJj>vj?#_X zayRuLzN#ZTPShH?FWjQ+uFHQDbaWNPH?Gk-a?j9FP{!LqR^;oTHDfqkhda;--S8B| zrs3oTN(h87&_X8Uo8oB(yfE=|#gWP!M?uixqmQF-Gk-FcqRp|CV8$j|%&m5pNg~N$ zj-+%mch5cDo4Fx?4i_a4y70H3PhQqkQlr`34xFX z(_tm-gn#A=$$kf+6S{LnflQ_0oR}h zgcf%SfJDd!n?+1DFgj06A6X={(;}t4a1i|TQaiq6QaWPspoh>00~Sw`SO<~9DkC*S z!f;52$9fs1p*2~(Xr@(8^R3=gVfCRqxjy(2K2&Y>r7f@rzP1h~hgE@j6_{5+-&mD& z#j3)O971=ke)Q0)q370#8hVAqEnh=2P(l!BKnKY%3Nj%#|K0LyXwt8_mbU$x^K~tK zmd}5~Lv$1}K}V60G~wMqzKtjUiTDyZu1fzc&fVljR7)}v5hEpvgW21@8~VlQp9a3} zj=m1H2+`ZV1pP|<%eR5Ad!Rqb!F-P@?4P?{FGkK})JO=7%qh4JCgJl8y#%e)m47wD z6D_}?-RZGWO8q8A*_r%C@d01(0I{pRn}d6!f7f!Hou^iIh((ec8Rd}}dkRrZQeP8^ z3TceohTk(rUhRtCB94-Gx?(jGkwM;xsFw=`B1)DEg(6Cnhq^E$BZ97rlxI%T< zDck*0Fx_z}>7dY_A3k&9F?(C6>Q-s9>0cfG>G_M6h{WFvCS?yZ~rj*U-*meBst%Hjf?UxsK`)?{p^ZxLe zsw~BGVa4bf-B#_@oHaI$CFHPwn|}s=uf_{?dlf4Kr51 z{_7ma;f-fM{p!|}`_E0*X{YxW)cm)l+w<&s4_Vdac-!{ynJkv+SPbJ{8>U4i7Zny3 zrW6|Difk$IHbYTjifvAOT%6u!ODddWOR^b?=fv5H5(^!xQ!^q|cGgtOnl`hh2G-Qf zn$EJOHrCY3warR=Z|R7J``zpZgWogC5m-2ZY2H&{M>MP@hP9Yki;cA`Wi1;VoQ)F> z{R}Uv_i|6th$LKQNr*5exRP5?vcT`jy9N0NQJd1RFc_jv=Rdd|^d6n!WH@j3j_y^n-A4LI;5-e27F-i4pZda*F`X6;R z_NuhHmpf6K9pQn=6ok)kh#-;Q!nzuzR7rvSLktpBuHJ|(t*Hs+hR@&VGhq +/// Extension methods +/// +internal static class ExtensionMethods { - using System.Linq; - using System.Text.RegularExpressions; - - internal static class ExtensionMethods + /// + /// Returns an empty string as null, otherwise returns the original string + /// + /// the input string + /// the string or null if empty + public static string? EmptyAsNull(this string str) { - public static void AddOrUpdate(this Dictionary dict, string key, object value) { - if (dict.ContainsKey(key)) - dict[key] = value; - else - dict.Add(key, value); - } - public static string? EmptyAsNull(this string str) + return str == string.Empty ? null : str; + } + + /// + /// Tries to perform a regex match + /// + /// the regex + /// the input string to match against + /// the match if found + /// true if matches, otherwise false + public static bool TryMatch(this Regex regex, string input, out Match match) + { + match = regex.Match(input); + return match.Success; + } + + /// + /// Trims the specified characters from the beginning and end of the string. + /// + /// The input string to trim. + /// The characters to trim from the string. + /// A new string that has the specified characters removed from the beginning and end. + /// Thrown when input is null. + public static string TrimExtra(this string input, string charsToTrim) + { + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (string.IsNullOrEmpty(charsToTrim)) + return input.Trim(); + + int startIndex = 0; + int endIndex = input.Length - 1; + + while (startIndex <= endIndex && (input[startIndex] == ' ' || charsToTrim.Contains(input[startIndex]))) { - return str == string.Empty ? null : str; + startIndex++; } - public static bool TryMatch(this Regex regex, string input, out Match match) + while (endIndex >= startIndex && (input[endIndex] == ' ' || charsToTrim.Contains(input[endIndex]))) { - match = regex.Match(input); - return match.Success; + endIndex--; } - public static IEnumerable SplitCommandLine(this string commandLine) - { - bool inQuotes = false; - - return commandLine.Split(c => - { - if (c == '\"') - inQuotes = !inQuotes; - - return !inQuotes && c == ' '; - }) - .Select(arg => arg.Trim().TrimMatchingQuotes('\"')) - .Where(arg => !string.IsNullOrEmpty(arg)); - } - public static IEnumerable Split(this string str, - Func controller) - { - int nextPiece = 0; - - for (int c = 0; c < str.Length; c++) - { - if (controller(str[c])) - { - yield return str.Substring(nextPiece, c - nextPiece); - nextPiece = c + 1; - } - } - - yield return str.Substring(nextPiece); - } - public static string TrimMatchingQuotes(this string input, char quote) - { - if ((input.Length >= 2) && - (input[0] == quote) && (input[input.Length - 1] == quote)) - return input.Substring(1, input.Length - 2); - - return input; - } + return input.Substring(startIndex, endIndex - startIndex + 1); } } diff --git a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackMerge.cs b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackMerge.cs index 7a84642b..e756ec4e 100644 --- a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackMerge.cs +++ b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackMerge.cs @@ -76,12 +76,13 @@ public class FfmpegBuilderSubtitleTrackMerge : FfmpegBuilderNode continue; string language = string.Empty; + bool forced = false; if (MatchFilename) { string lang1, lang2; - bool matchesOriginal = FilenameMatches(args.FileName, file, out lang1); - bool matchesWorking = FilenameMatches(args.WorkingFile, file, out lang2); + bool matchesOriginal = FilenameMatches(args.FileName, file, out lang1, out bool forced1); + bool matchesWorking = FilenameMatches(args.WorkingFile, file, out lang2, out bool forced2); if (matchesOriginal == false && matchesWorking == false) continue; @@ -90,6 +91,7 @@ public class FfmpegBuilderSubtitleTrackMerge : FfmpegBuilderNode language = lang1; if (string.IsNullOrEmpty(lang2) == false) language = lang2; + forced = forced1 || forced2; } string subTitle = language; @@ -109,6 +111,7 @@ public class FfmpegBuilderSubtitleTrackMerge : FfmpegBuilderNode InputFileIndex = this.Model.InputFiles.Count - 1, TypeIndex = 0, Language = language, + Forced = forced, Title = subTitle, Codec = ext, IndexString = (this.Model.InputFiles.Count - 1) + ":s:0" @@ -123,18 +126,22 @@ public class FfmpegBuilderSubtitleTrackMerge : FfmpegBuilderNode return count > 0 ? 1 : 2; } - internal bool FilenameMatches(string input, string other, out string languageCode) + internal bool FilenameMatches(string input, string other, out string languageCode, out bool forced) { - languageCode = String.Empty; + languageCode = string.Empty; + forced = false; var inputFileExtension = FileHelper.GetExtension(input); string inputName = FileHelper.GetShortFileName(input).Replace(inputFileExtension, ""); var otherFileExtension = FileHelper.GetExtension(other); string otherName = FileHelper.GetShortFileName(other).Replace(otherFileExtension, ""); - + if (inputName.ToLowerInvariant().Equals(otherName.ToLowerInvariant())) return true; + bool closedCaptions = HasSection(ref otherName, "closedcaptions") || HasSection(ref otherName, "cc"); + forced = HasSection(ref otherName, "forced"); + if(Regex.IsMatch(otherName, @"(\.[a-zA-Z]{2,3}){1,2}$")) { string stripLang = Regex.Replace(otherName, @"(\.[a-zA-Z]{2,3}){1,2}$", string.Empty).Replace(" ", " ").Trim(); @@ -143,14 +150,14 @@ public class FfmpegBuilderSubtitleTrackMerge : FfmpegBuilderNode if (rgxLanguage.IsMatch(otherName)) { string key = rgxLanguage.Match(otherName).Value; - languageCode = LanguageCodes.Codes[key]; + languageCode = LanguageCodes.Codes.GetValueOrDefault(key, key); } if (string.IsNullOrEmpty(languageCode) == false) { if (Regex.IsMatch(otherName, @"\.hi(\.|$)")) languageCode += " (HI)"; - if (Regex.IsMatch(otherName, @"\.cc(\.|$)")) + if (closedCaptions || Regex.IsMatch(otherName, @"\.cc(\.|$)")) languageCode += " (CC)"; if (Regex.IsMatch(otherName, @"\.sdh(\.|$)")) languageCode += " (SDH)"; @@ -169,17 +176,17 @@ public class FfmpegBuilderSubtitleTrackMerge : FfmpegBuilderNode if (rgxLanguage.IsMatch(otherName)) { string key = rgxLanguage.Match(otherName).Value; - languageCode = LanguageCodes.Codes[key]; + languageCode = LanguageCodes.Codes.GetValueOrDefault(key, key); } if (string.IsNullOrEmpty(languageCode) == false) { - if (other.ToLower().Contains("(hi)")) + if (other.ToLowerInvariant().Contains("(hi)")) languageCode += " (HI)"; - else if (other.ToLower().Contains("(cc)")) + else if (closedCaptions || other.ToLowerInvariant().Contains("(cc)")|| other.ToLowerInvariant().Contains("closedcaptions")) languageCode += " (CC)"; - else if (other.ToLower().Contains("(sdh)")) + else if (other.ToLowerInvariant().Contains("(sdh)")) languageCode += " (SDH)"; } if (inputName.ToLowerInvariant().Equals(stripLang.ToLowerInvariant())) @@ -187,5 +194,41 @@ public class FfmpegBuilderSubtitleTrackMerge : FfmpegBuilderNode } return false; + + bool HasSection(ref string input, string section) + { + var matchDot = new Regex(@"\." + Regex.Escape(section) + @"(\.|$)", RegexOptions.IgnoreCase); + if (matchDot.IsMatch(input)) + { + input = matchDot.Replace(input, string.Empty) + .Replace("..", ".") + .Replace(" ", " ") + .Replace("--", "-") + .TrimExtra(".-"); + return true; + } + var matchBracket = new Regex(@"\(" + Regex.Escape(section) + @"(\)|$)", RegexOptions.IgnoreCase); + if (matchBracket.IsMatch(input)) + { + input = matchBracket.Replace(input, string.Empty) + .Replace("..", ".") + .Replace(" ", " ") + .Replace("--", "-") + .TrimExtra(".-"); + return true; + } + var matchHyphen = new Regex(@"\-" + Regex.Escape(section) + @"(\-|$)", RegexOptions.IgnoreCase); + if (matchHyphen.IsMatch(input)) + { + input = matchHyphen.Replace(input, string.Empty) + .Replace("..", ".") + .Replace(" ", " ") + .Replace("--", "-") + .TrimExtra(".-"); + return true; + } + return false; + + } } } diff --git a/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_BasicTests.cs b/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_BasicTests.cs index 7e221b99..b5e2d2aa 100644 --- a/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_BasicTests.cs +++ b/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_BasicTests.cs @@ -1306,14 +1306,28 @@ public class FfmpegBuilder_BasicTests : TestBase public void FfmpegBuilder_SubtitleTrackMerge_FileMatchesTests() { FfmpegBuilderSubtitleTrackMerge ffSubMerge = new(); + foreach (var item in new[] { + + (File: "The Big Bang Theory_S01E01_Pilot.en.closedcaptions.srt", Language: "English (CC)", IsMatch: true, Forced: false), + (File: "The Big Bang Theory_S01E01_Pilot.it.closedcaptions.srt", Language: "Italian (CC)", IsMatch: true, Forced: false), + (File: "The Big Bang Theory_S01E01_Pilot.it.forced.srt", Language: "Italian", IsMatch: true, Forced: true), + }) + { + bool isMatch = ffSubMerge.FilenameMatches("The Big Bang Theory_S01E01_Pilot.mp4", item.File, out string lang, out bool forced); + Assert.AreEqual(item.IsMatch, isMatch, "Not match: " + item.File); + Assert.AreEqual(item.Forced, forced); + Assert.AreEqual(item.Language, lang, "Language not matching in: " + item.Language); + } + + foreach (var item in new[] { +("test.en.cc.srt", "English (CC)", true), ("test.srt", "", true), ("test.en.srt", "English", true), ("test(en).srt", "English", true), ("test (en).srt", "English", true), ("test.en.hi.srt", "English (HI)", true), ("test.en.sdh.srt", "English (SDH)", true), -("test.en.cc.srt", "English (CC)", true), ("test.de.srt", "German", true), ("test(de).srt", "German", true), ("test (de).srt", "German", true), @@ -1324,13 +1338,12 @@ public class FfmpegBuilder_BasicTests : TestBase ("nomatch (en).srt", "English", false) }) { - string lang; - bool isMatch = ffSubMerge.FilenameMatches("Test.mkv", item.Item1, out lang); + TestContext.WriteLine("File: " + item.Item1); + bool isMatch = ffSubMerge.FilenameMatches("Test.mkv", item.Item1, out string lang, out bool forced); Assert.AreEqual(item.Item3, isMatch); Assert.AreEqual(item.Item2, lang, "Language not matching in: " + item.Item1); } - } [TestMethod] diff --git a/VideoNodes/VideoNodes/VideoNode.cs b/VideoNodes/VideoNodes/VideoNode.cs index de92ed59..b51e39da 100644 --- a/VideoNodes/VideoNodes/VideoNode.cs +++ b/VideoNodes/VideoNodes/VideoNode.cs @@ -103,32 +103,32 @@ namespace FileFlows.VideoNodes else args.Parameters.Add(VIDEO_INFO, videoInfo); - variables.AddOrUpdate("vi.VideoInfo", videoInfo); - variables.AddOrUpdate("vi.Width", videoInfo.VideoStreams[0].Width); - variables.AddOrUpdate("vi.Height", videoInfo.VideoStreams[0].Height); - variables.AddOrUpdate("vi.Duration", videoInfo.VideoStreams[0].Duration.TotalSeconds); - variables.AddOrUpdate("vi.Video.Codec", videoInfo.VideoStreams[0].Codec); + variables["vi.VideoInfo"] = videoInfo; + variables["vi.Width"] = videoInfo.VideoStreams[0].Width; + variables["vi.Height"] = videoInfo.VideoStreams[0].Height; + variables["vi.Duration"] = videoInfo.VideoStreams[0].Duration.TotalSeconds; + variables["vi.Video.Codec"] = videoInfo.VideoStreams[0].Codec; if (videoInfo.AudioStreams?.Any() == true) { - variables.AddOrUpdate("vi.Audio.Codec", videoInfo.AudioStreams[0].Codec?.EmptyAsNull()); - variables.AddOrUpdate("vi.Audio.Channels", videoInfo.AudioStreams[0].Channels > 0 ? (object)videoInfo.AudioStreams[0].Channels : null); - variables.AddOrUpdate("vi.Audio.Language", videoInfo.AudioStreams[0].Language?.EmptyAsNull()); - variables.AddOrUpdate("vi.Audio.Codecs", string.Join(", ", videoInfo.AudioStreams.Select(x => x.Codec).Where(x => string.IsNullOrEmpty(x) == false))); - variables.AddOrUpdate("vi.Audio.Languages", string.Join(", ", videoInfo.AudioStreams.Select(x => x.Language).Where(x => string.IsNullOrEmpty(x) == false))); + variables["vi.Audio.Codec"] = videoInfo.AudioStreams[0].Codec?.EmptyAsNull(); + variables["vi.Audio.Channels"] = videoInfo.AudioStreams[0].Channels > 0 ? (object)videoInfo.AudioStreams[0].Channels : null; + variables["vi.Audio.Language"] = videoInfo.AudioStreams[0].Language?.EmptyAsNull(); + variables["vi.Audio.Codecs"] = string.Join(", ", videoInfo.AudioStreams.Select(x => x.Codec).Where(x => string.IsNullOrEmpty(x) == false)); + variables["vi.Audio.Languages"] = string.Join(", ", videoInfo.AudioStreams.Select(x => x.Language).Where(x => string.IsNullOrEmpty(x) == false)); } var resolution = ResolutionHelper.GetResolution(videoInfo.VideoStreams[0].Width, videoInfo.VideoStreams[0].Height); if(resolution == ResolutionHelper.Resolution.r1080p) - variables.AddOrUpdate("vi.Resolution", "1080p"); + variables["vi.Resolution"] = "1080p"; else if (resolution == ResolutionHelper.Resolution.r4k) - variables.AddOrUpdate("vi.Resolution", "4K"); + variables["vi.Resolution"] = "4K"; else if (resolution == ResolutionHelper.Resolution.r720p) - variables.AddOrUpdate("vi.Resolution", "720p"); + variables["vi.Resolution"] = "720p"; else if (resolution == ResolutionHelper.Resolution.r480p) - variables.AddOrUpdate("vi.Resolution", "480p"); + variables["vi.Resolution"] = "480p"; else if (videoInfo.VideoStreams[0].Width < 900 && videoInfo.VideoStreams[0].Height < 800) - variables.AddOrUpdate("vi.Resolution", "SD"); + variables["vi.Resolution"] = "SD"; else - variables.AddOrUpdate("vi.Resolution", videoInfo.VideoStreams[0].Width + "x" + videoInfo.VideoStreams[0].Height); + variables["vi.Resolution"] = videoInfo.VideoStreams[0].Width + "x" + videoInfo.VideoStreams[0].Height; args.UpdateVariables(variables);