mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-07-19 12:44:19 +01:00
Merge pull request #4984 from Bond-009/subtitleedit
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using MediaBrowser.MediaEncoding.Subtitles;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.MediaEncoding.Subtitles.Tests
|
||||
@@ -14,25 +15,15 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
|
||||
{
|
||||
using (var stream = File.OpenRead("Test Data/example.ass"))
|
||||
{
|
||||
var parsed = new AssParser().Parse(stream, CancellationToken.None);
|
||||
var parsed = new AssParser(new NullLogger<AssParser>()).Parse(stream, CancellationToken.None);
|
||||
Assert.Single(parsed.TrackEvents);
|
||||
var trackEvent = parsed.TrackEvents[0];
|
||||
|
||||
Assert.Equal("1", trackEvent.Id);
|
||||
Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
|
||||
Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
|
||||
Assert.Equal("Like an Angel with pity on nobody\r\nThe second line in subtitle", trackEvent.Text);
|
||||
Assert.Equal("{\\pos(400,570)}Like an Angel with pity on nobody" + Environment.NewLine + "The second line in subtitle", trackEvent.Text);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFieldHeaders_Valid_Success()
|
||||
{
|
||||
const string Line = "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text";
|
||||
var headers = AssParser.ParseFieldHeaders(Line);
|
||||
Assert.Equal(1, headers["Start"]);
|
||||
Assert.Equal(2, headers["End"]);
|
||||
Assert.Equal(9, headers["Text"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Jellyfin.MediaEncoding.Subtitles.Tests
|
||||
Assert.Equal("1", trackEvent1.Id);
|
||||
Assert.Equal(TimeSpan.Parse("00:02:17.440", CultureInfo.InvariantCulture).Ticks, trackEvent1.StartPositionTicks);
|
||||
Assert.Equal(TimeSpan.Parse("00:02:20.375", CultureInfo.InvariantCulture).Ticks, trackEvent1.EndPositionTicks);
|
||||
Assert.Equal("Senator, we're making\r\nour final approach into Coruscant.", trackEvent1.Text);
|
||||
Assert.Equal("Senator, we're making" + Environment.NewLine + "our final approach into Coruscant.", trackEvent1.Text);
|
||||
|
||||
var trackEvent2 = parsed.TrackEvents[1];
|
||||
Assert.Equal("2", trackEvent2.Id);
|
||||
|
||||
@@ -1,37 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using MediaBrowser.MediaEncoding.Subtitles;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.MediaEncoding.Tests
|
||||
namespace Jellyfin.MediaEncoding.Subtitles.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();
|
||||
private readonly SsaParser _parser = new SsaParser(new NullLogger<AssParser>());
|
||||
|
||||
[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
|
||||
// TODO: Fix upstream
|
||||
// [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
|
||||
// TODO: Fix upstream
|
||||
// [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);
|
||||
SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, CancellationToken.None);
|
||||
SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[0];
|
||||
Assert.Equal(expectedText, actual.Text);
|
||||
}
|
||||
@@ -43,7 +48,7 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||
{
|
||||
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(ssa)))
|
||||
{
|
||||
SubtitleTrackInfo subtitleTrackInfo = parser.Parse(stream, CancellationToken.None);
|
||||
SubtitleTrackInfo subtitleTrackInfo = _parser.Parse(stream, CancellationToken.None);
|
||||
|
||||
Assert.Equal(expectedSubtitleTrackEvents.Count, subtitleTrackInfo.TrackEvents.Count);
|
||||
|
||||
@@ -52,9 +57,10 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||
SubtitleTrackEvent expected = expectedSubtitleTrackEvents[i];
|
||||
SubtitleTrackEvent actual = subtitleTrackInfo.TrackEvents[i];
|
||||
|
||||
Assert.Equal(expected.Id, actual.Id);
|
||||
Assert.Equal(expected.Text, actual.Text);
|
||||
Assert.Equal(expected.StartPositionTicks, actual.StartPositionTicks);
|
||||
Assert.Equal(expected.EndPositionTicks, actual.EndPositionTicks);
|
||||
Assert.Equal(expected.Text, actual.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,26 +77,39 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||
",
|
||||
new List<SubtitleTrackEvent>
|
||||
{
|
||||
new SubtitleTrackEvent
|
||||
new SubtitleTrackEvent("1", "dialogue1")
|
||||
{
|
||||
StartPositionTicks = 11800000,
|
||||
EndPositionTicks = 18500000,
|
||||
Text = "dialogue1"
|
||||
EndPositionTicks = 18500000
|
||||
},
|
||||
new SubtitleTrackEvent
|
||||
new SubtitleTrackEvent("2", "dialogue2")
|
||||
{
|
||||
StartPositionTicks = 21800000,
|
||||
EndPositionTicks = 28500000,
|
||||
Text = "dialogue2"
|
||||
EndPositionTicks = 28500000
|
||||
},
|
||||
new SubtitleTrackEvent
|
||||
new SubtitleTrackEvent("3", "dialogue3")
|
||||
{
|
||||
StartPositionTicks = 31800000,
|
||||
EndPositionTicks = 38500000,
|
||||
Text = "dialogue3"
|
||||
EndPositionTicks = 38500000
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_Valid_Success()
|
||||
{
|
||||
using (var stream = File.OpenRead("Test Data/example.ssa"))
|
||||
{
|
||||
var parsed = _parser.Parse(stream, CancellationToken.None);
|
||||
Assert.Single(parsed.TrackEvents);
|
||||
var trackEvent = parsed.TrackEvents[0];
|
||||
|
||||
Assert.Equal("1", trackEvent.Id);
|
||||
Assert.Equal(TimeSpan.Parse("00:00:01.18", CultureInfo.InvariantCulture).Ticks, trackEvent.StartPositionTicks);
|
||||
Assert.Equal(TimeSpan.Parse("00:00:06.85", CultureInfo.InvariantCulture).Ticks, trackEvent.EndPositionTicks);
|
||||
Assert.Equal("{\\pos(400,570)}Like an angel with pity on nobody", trackEvent.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa
Normal file
20
tests/Jellyfin.MediaEncoding.Tests/Test Data/example.ssa
Normal file
@@ -0,0 +1,20 @@
|
||||
[Script Info]
|
||||
; This is a Sub Station Alpha v4 script.
|
||||
; For Sub Station Alpha info and downloads,
|
||||
; go to http://www.eswat.demon.co.uk/
|
||||
Title: Neon Genesis Evangelion - Episode 26 (neutral Spanish)
|
||||
Original Script: RoRo
|
||||
Script Updated By: version 2.8.01
|
||||
ScriptType: v4.00
|
||||
Collisions: Normal
|
||||
PlayResY: 600
|
||||
PlayDepth: 0
|
||||
Timer: 100,0000
|
||||
|
||||
[V4 Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
|
||||
Style: DefaultVCD, Arial,28,11861244,11861244,11861244,-2147483640,-1,0,1,1,2,2,30,30,30,0,0
|
||||
|
||||
[Events]
|
||||
Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: Marked=0,0:00:01.18,0:00:06.85,DefaultVCD, NTP,0000,0000,0000,,{\pos(400,570)}Like an angel with pity on nobody
|
||||
Reference in New Issue
Block a user