diff --git a/osu.Game.Rulesets.Karaoke.Tests/Beatmaps/Formats/LrcDecoderTest.cs b/osu.Game.Rulesets.Karaoke.Tests/Beatmaps/Formats/LrcDecoderTest.cs index 4dd313113..276971d39 100644 --- a/osu.Game.Rulesets.Karaoke.Tests/Beatmaps/Formats/LrcDecoderTest.cs +++ b/osu.Game.Rulesets.Karaoke.Tests/Beatmaps/Formats/LrcDecoderTest.cs @@ -1,9 +1,11 @@ // Copyright (c) andy840119 . Licensed under the GPL Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.IO; using osu.Game.Rulesets.Karaoke.Beatmaps.Formats; using osu.Game.Rulesets.Karaoke.Objects; @@ -14,38 +16,57 @@ namespace osu.Game.Rulesets.Karaoke.Tests.Beatmaps.Formats public class LrcDecoderTest { [Test] - public void TestDecodeNote() + public void TestDecodeLyric() { const string lyric_text = "[00:01.00]か[00:02.00]ら[00:03.00]お[00:04.00]け[00:05.00]"; + var beatmap = decodeLrcLine(lyric_text); + // Get first beatmap + var lyric = beatmap.HitObjects.OfType().FirstOrDefault(); + + // Check lyric + Assert.AreEqual(lyric?.Text, "からおけ"); + Assert.AreEqual(lyric?.StartTime, 1000); + Assert.AreEqual(lyric?.EndTime, 5000); + + // Check time tag + var tags = lyric?.TimeTags; + var checkedTags = tags.ToArray(); + Assert.AreEqual(tags.Count, 5); + Assert.AreEqual(checkedTags.Length, 5); + Assert.AreEqual(string.Join(',', tags.Select(x => x.Key.Index)), "0,1,2,3,4"); + Assert.AreEqual(string.Join(',', tags.Select(x => x.Value)), "1000,2000,3000,4000,5000"); + } + + [Test] + public void TestDecodeLyricWithDulicatedTimeTag() + { + const string wrong_lyric_text = "[00:04.00]か[00:04.00]ら[00:05.00]お[00:06.00]け[00:07.00]"; + Assert.Throws(() => decodeLrcLine(wrong_lyric_text)); + } + + [Test] + [Ignore("Waiting for lyric parser update.")] + public void TestDecodeLyricWithTimeTagNotOrder() + { + const string wrong_lyric_text = "[00:04.00]か[00:03.00]ら[00:02.00]お[00:01.00]け[00:00.00]"; + Assert.Throws(() => decodeLrcLine(wrong_lyric_text)); + } + + private Beatmap decodeLrcLine(string line) + { using (var stream = new MemoryStream()) using (var writer = new StreamWriter(stream)) using (var reader = new LineBufferedReader(stream)) { // Create stream - writer.Write(lyric_text); + writer.Write(line); writer.Flush(); stream.Position = 0; // Create karaoke note decoder var decoder = new LrcDecoder(); - var beatmap = decoder.Decode(reader); - - // Get first beatmap - var lyric = beatmap.HitObjects.OfType().FirstOrDefault(); - - // Check lyric - Assert.AreEqual(lyric?.Text, "からおけ"); - Assert.AreEqual(lyric?.StartTime, 1000); - Assert.AreEqual(lyric?.EndTime, 5000); - - // Check time tag - var tags = lyric?.TimeTags; - var checkedTags = tags.ToArray(); - Assert.AreEqual(tags.Count, 5); - Assert.AreEqual(checkedTags.Length, 5); - Assert.AreEqual(string.Join(',', tags.Select(x => x.Key.Index)), "0,1,2,3,4"); - Assert.AreEqual(string.Join(',', tags.Select(x => x.Value)), "1000,2000,3000,4000,5000"); + return decoder.Decode(reader); } } } diff --git a/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/LrcDecoder.cs b/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/LrcDecoder.cs index 70ca12bfa..2f269d9f5 100644 --- a/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/LrcDecoder.cs +++ b/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/LrcDecoder.cs @@ -30,36 +30,50 @@ protected override void ParseStreamInto(LineBufferedReader stream, Beatmap outpu var result = new LrcParser().Decode(lyricText); // Convert line - foreach (var line in result.Lines) + for (int i = 0; i < result.Lines.Length; i++) { // Empty line should not be imported + var line = result.Lines[i]; if (string.IsNullOrEmpty(line.Text)) continue; - var startTime = line.TimeTags.FirstOrDefault(x => x.Time > 0).Time; - var duration = line.TimeTags.LastOrDefault(x => x.Time > 0).Time - startTime; - - var lyric = line.Text; - output.HitObjects.Add(new LyricLine + try { - Text = lyric, - // Start time and end time should be re-assigned - StartTime = startTime, - Duration = duration, - TimeTags = line.TimeTags.Where(x => x.Check).ToDictionary(k => - { - var index = (int)Math.Ceiling((double)(Array.IndexOf(line.TimeTags, k) - 1) / 2); - var state = (Array.IndexOf(line.TimeTags, k) - 1) % 2 == 0 ? TimeTagIndex.IndexState.Start : TimeTagIndex.IndexState.End; + // todo : check list ls sorted by time. + var timeTags = line.TimeTags; + + var startTime = timeTags.FirstOrDefault(x => x.Time > 0).Time; + var duration = timeTags.LastOrDefault(x => x.Time > 0).Time - startTime; - return new TimeTagIndex(index, state); - }, v => (double)v.Time), - RubyTags = result.QueryRubies(lyric).Select(ruby => new RubyTag + var lyric = line.Text; + output.HitObjects.Add(new LyricLine { - Text = ruby.Ruby.Ruby, - StartIndex = ruby.StartIndex, - EndIndex = ruby.EndIndex - }).ToArray() - }); + Text = lyric, + // Start time and end time should be re-assigned + StartTime = startTime, + Duration = duration, + TimeTags = timeTags.Where(x => x.Check).ToDictionary(k => + { + var index = (int)Math.Ceiling((double)(Array.IndexOf(timeTags, k) - 1) / 2); + var state = (Array.IndexOf(timeTags, k) - 1) % 2 == 0 ? TimeTagIndex.IndexState.Start : TimeTagIndex.IndexState.End; + + return new TimeTagIndex(index, state); + }, v => (double)v.Time), + RubyTags = result.QueryRubies(lyric).Select(ruby => new RubyTag + { + Text = ruby.Ruby.Ruby, + StartIndex = ruby.StartIndex, + EndIndex = ruby.EndIndex + }).ToArray() + }); + } + catch (Exception ex) + { + var message = $"Parsing lyric '{line.Text}' got error in line:{i}" + + "Please check time tag should be ordered and not duplicated." + + "Then re-import again."; + throw new FormatException(message, ex); + } } } }