Fix string culture specificity

Fix bug in SsaParser.cs primary color {\1c} formatting that would leave
behind the {\1c} closing token and instead append </font> token
unconditionally to the dialogue text. Add tests.

Change AlphanumComparatorTests.cs complementary test data generation
from an array shuffle to an array reversal. Although it was previously
using a seeded Random, the shuffle itself could result in no
rearrangement of elements if the seed or test data changed over time.
The reversal guarantees reordering of elements and has the added benefit
of simplifying the test code since no special handling is needed for
arrays of 2 elements.

Change DailyTrigger.cs logging of TriggerDate format to
"yyyy-MM-dd HH:mm:ss.fff zzz" for consistency with configured log
timestamp format and change DueTime format to culture-invariant "c"
format.
This commit is contained in:
obradovichv 2021-01-03 20:13:21 +02:00
parent 841996c642
commit 0282a1ed09
4 changed files with 107 additions and 17 deletions

View File

@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
var dueTime = triggerDate - now; var dueTime = triggerDate - now;
logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:g}, which is {DueTime:g} from now.", taskName, triggerDate, dueTime); logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime);
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
} }

View File

@ -325,7 +325,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
} }
text += "</font>"; int indexOfEndTag = text.IndexOf("{\\1c}", start, StringComparison.Ordinal);
if (indexOfEndTag > 0)
{
text = text.Remove(indexOfEndTag, "{\\1c}".Length).Insert(indexOfEndTag, "</font>");
}
else
{
text += "</font>";
}
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using Xunit; using Xunit;
@ -8,8 +7,6 @@ namespace Jellyfin.Controller.Tests
{ {
public class AlphanumComparatorTests public class AlphanumComparatorTests
{ {
private readonly Random _rng = new Random(42);
// InlineData is pre-sorted // InlineData is pre-sorted
[Theory] [Theory]
[InlineData(null, "", "1", "9", "10", "a", "z")] [InlineData(null, "", "1", "9", "10", "a", "z")]
@ -25,18 +22,7 @@ namespace Jellyfin.Controller.Tests
[InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")] [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")]
public void AlphanumComparatorTest(params string?[] strings) public void AlphanumComparatorTest(params string?[] strings)
{ {
var copy = (string?[])strings.Clone(); var copy = strings.Reverse().ToArray();
if (strings.Length == 2)
{
var tmp = copy[0];
copy[0] = copy[1];
copy[1] = tmp;
}
else
{
copy.Shuffle(_rng);
}
Array.Sort(copy, new AlphanumComparator()); Array.Sort(copy, new AlphanumComparator());
Assert.True(strings.SequenceEqual(copy)); Assert.True(strings.SequenceEqual(copy));
} }

View File

@ -0,0 +1,96 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.Model.MediaInfo;
using Xunit;
namespace Jellyfin.MediaEncoding.Tests
{
public class SsaParserTests
{
// commonly shared invariant value between tests, assumes default format order
private const string InvariantDialoguePrefix = "[Events]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,";
private SsaParser parser = new SsaParser();
[Theory]
[InlineData("[EvEnTs]\nDialogue: ,0:00:00.00,0:00:00.01,,,,,,,text", "text")] // label casing insensitivity
[InlineData("[Events]\n,0:00:00.00,0:00:00.01,,,,,,,labelless dialogue", "labelless dialogue")] // no "Dialogue:" label, it is optional
[InlineData("[Events]\nFormat: Text, Start, End, Layer, Effect, Style\nDialogue: reordered text,0:00:00.00,0:00:00.01", "reordered text")] // reordered formats
[InlineData(InvariantDialoguePrefix + "Cased TEXT", "Cased TEXT")] // preserve text casing
[InlineData(InvariantDialoguePrefix + " text ", " text ")] // do not trim text
[InlineData(InvariantDialoguePrefix + "text, more text", "text, more text")] // append excess dialogue values (> 10) to text
[InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text{\\fn} end", "start <font face=\"Font Name\">text</font> end")] // font name
[InlineData(InvariantDialoguePrefix + "start {\\fs10}text{\\fs} end", "start <font size=\"10\">text</font> end")] // font size
[InlineData(InvariantDialoguePrefix + "start {\\c&H112233}text{\\c} end", "start <font color=\"#332211\">text</font> end")] // color
[InlineData(InvariantDialoguePrefix + "start {\\1c&H112233}text{\\1c} end", "start <font color=\"#332211\">text</font> end")] // primay color
[InlineData(InvariantDialoguePrefix + "start {\\fnFont Name}text1 {\\fs10}text2{\\fs}{\\fn} {\\1c&H112233}text3{\\1c} end", "start <font face=\"Font Name\">text1 <font size=\"10\">text2</font></font> <font color=\"#332211\">text3</font> end")] // nested formatting
public void Parse(string ssa, string expectedText)
{
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
{
SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[0];
Assert.Equal(expectedText, actual.Text);
}
}
[Theory]
[MemberData(nameof(Parse_MultipleDialogues_TestData))]
public void Parse_MultipleDialogues(string ssa, IReadOnlyList<SubtitleTrackEvent> expectedSubtitleTrackEvents)
{
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
{
SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
for (int i = 0; i < expectedSubtitleTrackEvents.Count; ++i)
{
SubtitleTrackEvent expected = expectedSubtitleTrackEvents[i];
SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[i];
Assert.Equal(expected.StartPositionTicks, actual.StartPositionTicks);
Assert.Equal(expected.EndPositionTicks, actual.EndPositionTicks);
Assert.Equal(expected.Text, actual.Text);
}
}
}
public static IEnumerable<object[]> Parse_MultipleDialogues_TestData()
{
yield return new object[]
{
@"[Events]
Format: Layer, Start, End, Text
Dialogue: ,0:00:01.18,0:00:01.85,dialogue1
Dialogue: ,0:00:02.18,0:00:02.85,dialogue2
Dialogue: ,0:00:03.18,0:00:03.85,dialogue3
",
new List<SubtitleTrackEvent>
{
new SubtitleTrackEvent
{
StartPositionTicks = 11800000,
EndPositionTicks = 18500000,
Text = "dialogue1"
},
new SubtitleTrackEvent
{
StartPositionTicks = 21800000,
EndPositionTicks = 28500000,
Text = "dialogue2"
},
new SubtitleTrackEvent
{
StartPositionTicks = 31800000,
EndPositionTicks = 38500000,
Text = "dialogue3"
}
}
};
}
}
}