Skip to content

Commit

Permalink
Merge pull request #1339 from andy840119/switch-the-lrc-parser
Browse files Browse the repository at this point in the history
Switch the lrc parser
  • Loading branch information
andy840119 authored May 27, 2022
2 parents 5b8c56f + e2c9f02 commit 8bfa6c3
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public void TestLyricTimeTag(string text, string[] timeTags)
TimeTagAssert.ArePropertyEqual(expected, actual);
}

[Ignore("Time-tags with same time might be allowed.")]
[TestCase("[00:04.00]か[00:04.00]ら[00:05.00]お[00:06.00]け[00:07.00]")]
public void TestDecodeLyricWithDuplicatedTimeTag(string text)
{
Expand Down
97 changes: 40 additions & 57 deletions osu.Game.Rulesets.Karaoke/Beatmaps/Formats/LrcDecoder.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Copyright (c) andy840119 <[email protected]>. Licensed under the GPL Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using LyricMaker.Extensions;
using LyricMaker.Parser;
using osu.Framework.Graphics.Sprites;
using LrcParser.Model;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Rulesets.Karaoke.Objects;
using Lyric = osu.Game.Rulesets.Karaoke.Objects.Lyric;
using RubyTag = osu.Game.Rulesets.Karaoke.Objects.RubyTag;
using TextIndex = osu.Framework.Graphics.Sprites.TextIndex;

namespace osu.Game.Rulesets.Karaoke.Beatmaps.Formats
{
Expand All @@ -28,67 +28,50 @@ protected override void ParseStreamInto(LineBufferedReader stream, Beatmap outpu
output.HitObjects.Clear();

string lyricText = stream.ReadToEnd();
var result = new LrcParser().Decode(lyricText);
var result = new LrcParser.Parser.Lrc.LrcParser().Decode(lyricText);

// Convert line
for (int i = 0; i < result.Lines.Length; i++)
foreach (var lrcLyric in result.Lyrics)
{
// Empty line should not be imported
var line = result.Lines[i];
if (string.IsNullOrEmpty(line.Text))
continue;
var lrcTimeTags = lrcLyric.TimeTags.Select(convertTimeTag).ToList();
var lrcRubies = lrcLyric.RubyTags.Select(convertRubyTag).ToArray();

try
double? startTime = lrcTimeTags.Select(x => x.Time).Min();
double? endTime = lrcTimeTags.Select(x => x.Time).Max();
double? duration = endTime - startTime;

var lyric = new Lyric
{
// todo : check list ls sorted by time.
var lrcTimeTag = line.TimeTags;
var timeTags = line.TimeTags.Where(x => x.Check).ToDictionary(k =>
{
int index = (Array.IndexOf(lrcTimeTag, k) - 1) / 2;
var state = (Array.IndexOf(lrcTimeTag, k) - 1) % 2 == 0 ? TextIndex.IndexState.Start : TextIndex.IndexState.End;
return new TextIndex(index, state);
}, v => (double)v.Time);
ID = output.HitObjects.Count, // id is star from zero.
Order = output.HitObjects.Count + 1, // should create default order.
Text = lrcLyric.Text,
// Start time and end time should be re-assigned
StartTime = startTime ?? 0,
Duration = duration ?? 0,
TimeTags = lrcTimeTags,
RubyTags = lrcRubies
};
lyric.InitialWorkingTime();
output.HitObjects.Add(lyric);
}

double startTime = timeTags.FirstOrDefault(x => x.Value > 0).Value;
double duration = timeTags.LastOrDefault(x => x.Value > 0).Value - startTime;
static TimeTag convertTimeTag(KeyValuePair<LrcParser.Model.TextIndex, int?> timeTag)
=> new(convertTextIndex(timeTag.Key), timeTag.Value);

var lyric = new Lyric
{
ID = output.HitObjects.Count, // id is star from zero.
Order = output.HitObjects.Count + 1, // should create default order.
Text = line.Text,
// Start time and end time should be re-assigned
StartTime = startTime,
Duration = duration,
TimeTags = ToTimeTagList(timeTags),
RubyTags = result.QueryRubies(line.Text).Select(ruby => new RubyTag
{
Text = ruby.Ruby.Ruby,
StartIndex = ruby.StartIndex,
EndIndex = ruby.EndIndex
}).ToArray()
};
lyric.InitialWorkingTime();
output.HitObjects.Add(lyric);
}
catch (Exception ex)
{
string 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);
}
static TextIndex convertTextIndex(LrcParser.Model.TextIndex textIndex)
{
int index = textIndex.Index;
var state = textIndex.State == IndexState.Start ? TextIndex.IndexState.Start : TextIndex.IndexState.End;

return new TextIndex(index, state);
}
}

/// <summary>
/// Convert dictionary to list of time tags.
/// </summary>
/// <param name="dictionary">Dictionary.</param>
/// <returns>Time tags</returns>
internal static TimeTag[] ToTimeTagList(IReadOnlyDictionary<TextIndex, double> dictionary)
{
return dictionary.Select(d => new TimeTag(d.Key, d.Value)).ToArray();
static RubyTag convertRubyTag(LrcParser.Model.RubyTag rubyTag)
=> new()
{
Text = rubyTag.Text,
StartIndex = rubyTag.StartIndex,
EndIndex = rubyTag.EndIndex
};
}
}
}
105 changes: 39 additions & 66 deletions osu.Game.Rulesets.Karaoke/Beatmaps/Formats/LrcEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LyricMaker.Model;
using LyricMaker.Model.Tags;
using LyricMaker.Parser;
using osu.Framework.Graphics.Sprites;
using LrcParser.Model;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Karaoke.Objects;
using osu.Game.Rulesets.Karaoke.Utils;
using Lyric = osu.Game.Rulesets.Karaoke.Objects.Lyric;
using KaraokeTimeTag = osu.Game.Rulesets.Karaoke.Objects.TimeTag;
using RubyTag = osu.Game.Rulesets.Karaoke.Objects.RubyTag;
using TextIndex = osu.Framework.Graphics.Sprites.TextIndex;

namespace osu.Game.Rulesets.Karaoke.Beatmaps.Formats
{
public class LrcEncoder
{
public string Encode(Beatmap output)
{
var lyric = new LyricMaker.Model.Lyric
// Note : save to lyric will lost some tags with no value.
var song = new Song
{
Lines = output.HitObjects.OfType<Lyric>().Select(encodeLyric).ToArray(),
Lyrics = output.HitObjects.OfType<Lyric>().Select(encodeLyric).ToList(),
};
string encodeResult = new LrcParser().Encode(lyric);
string encodeResult = new LrcParser.Parser.Lrc.LrcParser().Encode(song);
return encodeResult;
}

private LyricLine encodeLyric(Lyric lyric) =>
new()
static LrcParser.Model.Lyric encodeLyric(Lyric lyric) =>
new()
{
Text = lyric.Text,
TimeTags = convertTimeTag(lyric.TimeTags),
RubyTags = convertRubyTag(lyric.RubyTags)
};

static SortedDictionary<LrcParser.Model.TextIndex, int?> convertTimeTag(IList<TimeTag> timeTags)
{
Text = lyric.Text,
// Note : save to lyric will lost some tags with no value.
TimeTags = convertTimeTag(lyric.Text, ToDictionary(lyric.TimeTags)).ToArray(),
};
// Note : save to lyric will lost some tags with duplicated index.
var timeTagDictionary = ToDictionary(timeTags).ToDictionary(k => convertTextIndex(k.Key), v => (int?)v.Value);
return new SortedDictionary<LrcParser.Model.TextIndex, int?>(timeTagDictionary);
}

static LrcParser.Model.TextIndex convertTextIndex(TextIndex textIndex)
{
int index = textIndex.Index;
var state = textIndex.State == TextIndex.IndexState.Start ? IndexState.Start : IndexState.End;

return new LrcParser.Model.TextIndex(index, state);
}

static List<LrcParser.Model.RubyTag> convertRubyTag(IEnumerable<RubyTag> rubyTags)
=> rubyTags.Select(x => new LrcParser.Model.RubyTag
{
Text = x.Text,
StartIndex = x.StartIndex,
EndIndex = x.EndIndex
}).ToList();
}

/// <summary>
/// Convert list of time tag to dictionary.
Expand All @@ -43,7 +66,7 @@ private LyricLine encodeLyric(Lyric lyric) =>
/// <param name="other">Fix way</param>
/// <param name="self">Fix way</param>
/// <returns>Time tags with dictionary format.</returns>
internal static IReadOnlyDictionary<TextIndex, double> ToDictionary(IList<KaraokeTimeTag> timeTags, bool applyFix = true, GroupCheck other = GroupCheck.Asc,
internal static IReadOnlyDictionary<TextIndex, double> ToDictionary(IList<TimeTag> timeTags, bool applyFix = true, GroupCheck other = GroupCheck.Asc,
SelfCheck self = SelfCheck.BasedOnStart)
{
if (timeTags == null)
Expand All @@ -59,55 +82,5 @@ internal static IReadOnlyDictionary<TextIndex, double> ToDictionary(IList<Karaok
k => k?.Index ?? throw new ArgumentNullException(nameof(k)),
v => v?.Time ?? throw new ArgumentNullException(nameof(v)));
}

private IEnumerable<TimeTag> convertTimeTag(string text, IReadOnlyDictionary<TextIndex, double> tags)
{
// total time-tag amount in lyric maker.
int totalTags = text.Length * 2 + 2;

for (int i = 0; i < totalTags; i++)
{
// should return empty tag if no time-tag in lyric.
if (tags.Count == 0)
{
yield return new TimeTag();

continue;
}

(var lastTag, double lastTagTime) = tags.LastOrDefault();

// create end time-tag
if ((lastTag.Index + 1) * 2 == i)
{
yield return new TimeTag
{
Time = (int)lastTagTime,
Check = true,
KeyUp = true
};

continue;
}

(var firstTag, double firstTagTime) = tags.FirstOrDefault(x => x.Key.Index * 2 + 1 == i);

// create start time-tag
if (firstTagTime > 0 && firstTag != lastTag)
{
yield return new TimeTag
{
Time = (int)firstTagTime,
Check = true,
KeyUp = true
};

continue;
}

// if has no match tag in lyric, should return empty one.
yield return new TimeTag();
}
}
}
}
4 changes: 2 additions & 2 deletions osu.Game.Rulesets.Karaoke/ILRepack.targets
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<ItemGroup>
<InputAssemblies Include="$(OutputPath)\osu.Game.Rulesets.Karaoke.dll"/>
<InputAssemblies Include="$(OutputPath)\LanguageDetection.dll"/>
<InputAssemblies Include="$(OutputPath)\LrcParser.dll"/>
<InputAssemblies Include="$(OutputPath)\Octokit.dll"/>
<InputAssemblies Include="$(OutputPath)\osu.Framework.KaraokeFont.dll"/>
<InputAssemblies Include="$(OutputPath)\osu.Framework.Microphone.dll"/>
<InputAssemblies Include="$(OutputPath)\NWaves.dll"/>
<InputAssemblies Include="$(OutputPath)\LyricMaker.dll"/>
<InputAssemblies Include="$(OutputPath)\NicoKaraParser.dll"/>
<InputAssemblies Include="$(OutputPath)\SixLabors.Fonts.dll"/>
<InputAssemblies Include="$(OutputPath)\SixLabors.ImageSharp.Drawing.dll"/>
Expand All @@ -23,4 +23,4 @@
OutputFile="$(OutputPath)Packed\$(AssemblyName).dll"
/>
</Target>
</Project>
</Project>
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Karaoke/osu.Game.Rulesets.Karaoke.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
<ItemGroup>
<PackageReference Include="ILRepack.Lib.MSBuild" Version="2.1.18" />
<PackageReference Include="LanguageDetection.karaoke-dev" Version="1.3.3-alpha" />
<PackageReference Include="LrcParser" Version="2022.0528.0" />
<PackageReference Include="Octokit" Version="0.51.0" />
<PackageReference Include="osu.Framework.KaraokeFont" Version="2022.522.0" />
<PackageReference Include="osu.Framework.Microphone" Version="2022.522.0" />
<PackageReference Include="ppy.osu.Game" Version="2022.523.0" />
<PackageReference Include="LyricMaker" Version="1.1.1" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Kuromoji" Version="4.8.0-beta00016" />
<PackageReference Include="NicoKaraParser" Version="1.1.0" />
Expand Down

0 comments on commit 8bfa6c3

Please sign in to comment.