Skip to content

Commit

Permalink
fix: lyrics parsing in some cases
Browse files Browse the repository at this point in the history
this brings more advanced lrc parsing as a fallback
  • Loading branch information
MSOB7YY committed Jan 21, 2024
1 parent 715b75b commit b675fc8
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 40 deletions.
18 changes: 6 additions & 12 deletions lib/controller/lyrics_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ class Lyrics {
final embedded = track.lyrics;

if (settings.prioritizeEmbeddedLyrics.value && embedded != '') {
try {
final lrc = embedded.toLrc();
final lrc = embedded.parseLRC();
if (lrc != null) {
currentLyricsLRC.value = lrc;
_updateWidgets(lrc);
} catch (e) {
} else {
currentLyricsText.value = embedded;
}
return;
Expand Down Expand Up @@ -111,11 +111,7 @@ class Lyrics {

Future<Lrc?> parseLRCFile(File file) async {
final content = await file.readAsString();
try {
return content.toLrc();
} catch (_) {
return null;
}
return content.parseLRC();
}

Lrc? lrc;
Expand All @@ -133,9 +129,7 @@ class Lyrics {
if (await fc.existsAndValid()) {
lrc = await parseLRCFile(fc);
} else if (trackLyrics != '') {
try {
lrc = trackLyrics.toLrc();
} catch (_) {}
lrc = trackLyrics.parseLRC();
}
}

Expand Down Expand Up @@ -164,7 +158,7 @@ class Lyrics {

final text = lyrics.firstOrNull?.lyrics;
if (text != null) await fc.writeAsString(text);
return text?.toLrc();
return text?.parseLRC();
}
return lrc;
}
Expand Down
25 changes: 25 additions & 0 deletions lib/core/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:dart_extensions/dart_extensions.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:lrc/lrc.dart';

import 'package:namida/class/track.dart';
import 'package:namida/controller/indexer_controller.dart';
Expand All @@ -18,6 +19,7 @@ import 'package:namida/core/constants.dart';
import 'package:namida/core/enums.dart';
import 'package:namida/core/namida_converter_ext.dart';
import 'package:namida/core/translations/language.dart';
import 'package:namida/packages/lyrics_parser/parser_smart.dart';
import 'package:namida/ui/widgets/custom_widgets.dart';

export 'package:dart_extensions/dart_extensions.dart';
Expand Down Expand Up @@ -340,6 +342,29 @@ extension TitleAndArtistUtils on String {
}
}

extension LRCParsingUtils on String {
Lrc? parseLRC() {
try {
return toLrc();
} catch (_) {
try {
final res = LRCParserSmart(this).parseLines();
final lines = res
.map(
(e) => LrcLine(
timestamp: e.timeStamp ?? Duration.zero,
lyrics: e.mainText ?? '',
type: LrcTypes.simple,
),
)
.toList();
return Lrc(lyrics: lines);
} catch (_) {}
}
return null;
}
}

extension TagFieldsUtils on TagField {
bool get isNumeric => this == TagField.trackNumber || this == TagField.trackTotal || this == TagField.discNumber || this == TagField.discTotal || this == TagField.year;
}
Expand Down
51 changes: 51 additions & 0 deletions lib/packages/lyrics_parser/models.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// all parsers extend this class
abstract class LyricsParse {
String lyric;

LyricsParse(this.lyric);

/// call this method to parse
List<LyricsLineModel> parseLines({bool isMain = true});

/// verify [lyric] is matching
bool isOK() => true;
}

class LyricsLineModel {
String? mainText;
String? extText;
int? startTime;
int? endTime;
List<LyricSpanInfo>? spanList;

Duration? get timeStamp => startTime == null ? null : Duration(milliseconds: startTime!);

bool get hasExt => extText?.isNotEmpty == true;

bool get hasMain => mainText?.isNotEmpty == true;

List<LyricSpanInfo>? _defaultSpanList;

get defaultSpanList => _defaultSpanList ??= [
LyricSpanInfo()
..duration = (endTime ?? 0) - (startTime ?? 0)
..start = startTime ?? 0
..length = mainText?.length ?? 0
..raw = mainText ?? ""
];
}

class LyricSpanInfo {
int index = 0;
int length = 0;
int duration = 0;
int start = 0;
String raw = "";

double drawWidth = 0;
double drawHeight = 0;

int get end => start + duration;

int get endIndex => index + length;
}
65 changes: 65 additions & 0 deletions lib/packages/lyrics_parser/parser_lrc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'models.dart';

///normal lyric parser
class LRCParserLrc extends LyricsParse {
RegExp pattern = RegExp(r"\[\d{2}:\d{2}.\d{2,3}]");

///匹配普通格式内容
///eg:[00:03.47] -> 00:03.47
RegExp valuePattern = RegExp(r"\[(\d{2}:\d{2}.\d{2,3})\]");

LRCParserLrc(String lyric) : super(lyric);

@override
List<LyricsLineModel> parseLines({bool isMain = true}) {
//读每一行
var lines = lyric.split("\n");
if (lines.isEmpty) {
return [];
}
List<LyricsLineModel> lineList = [];
for (var line in lines) {
//匹配time
var time = pattern.stringMatch(line);
if (time == null) {
//没有匹配到直接返回
continue;
}
//移除time,拿到真实歌词
var realLyrics = line.replaceFirst(pattern, "");
//转时间戳
var ts = timeTagToTS(time);
var lineModel = LyricsLineModel()..startTime = ts;
if (realLyrics == "//") {
realLyrics = "";
}
if (isMain) {
lineModel.mainText = realLyrics;
} else {
lineModel.extText = realLyrics;
}
lineList.add(lineModel);
}
return lineList;
}

int? timeTagToTS(String timeTag) {
if (timeTag.trim().isEmpty) {
return null;
}
//通过正则取出value
var value = valuePattern.firstMatch(timeTag)?.group(1) ?? "";
if (value.isEmpty) {
return null;
}
var timeArray = value.split(".");
var padZero = 3 - timeArray.last.length;
var millisecond = timeArray.last.padRight(padZero, "0");
//避免出现奇葩
if (millisecond.length > 3) {
millisecond = millisecond.substring(0, 3);
}
var minAndSecArray = timeArray.first.split(":");
return Duration(minutes: int.parse(minAndSecArray.first), seconds: int.parse(minAndSecArray.last), milliseconds: int.parse(millisecond)).inMilliseconds;
}
}
71 changes: 71 additions & 0 deletions lib/packages/lyrics_parser/parser_qrc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'models.dart';

///qrc lyric parser
class LRCParserQrc extends LyricsParse {
RegExp advancedPattern = RegExp(r"""\[\d+,\d+]""");
RegExp qrcPattern = RegExp(r"""\((\d+,\d+)\)""");

RegExp advancedValuePattern = RegExp(r"\[(\d*,\d*)\]");

LRCParserQrc(String lyric) : super(lyric);

@override
List<LyricsLineModel> parseLines({bool isMain = true}) {
lyric = RegExp(r"""LyricContent="([\s\S]*)">""").firstMatch(lyric)?.group(1) ?? lyric;
//读每一行
var lines = lyric.split("\n");
if (lines.isEmpty) {
return [];
}
List<LyricsLineModel> lineList = [];
for (var line in lines) {
//匹配time
var time = advancedPattern.stringMatch(line);
if (time == null) {
//没有匹配到直接返回
continue;
}
//转时间戳
var ts = int.parse(advancedValuePattern.firstMatch(time)?.group(1)?.split(",")[0] ?? "0");
//移除time,拿到真实歌词
var realLyrics = line.replaceFirst(advancedPattern, "");

List<LyricSpanInfo> spanList = getSpanList(realLyrics);

var lineModel = LyricsLineModel()
..mainText = realLyrics.replaceAll(qrcPattern, "")
..startTime = ts
..spanList = spanList;
lineList.add(lineModel);
}
return lineList;
}

///get line span info list
List<LyricSpanInfo> getSpanList(String realLyrics) {
var invalidLength = 0;
var startIndex = 0;
var spanList = qrcPattern.allMatches(realLyrics).map((element) {
var span = LyricSpanInfo();

span.raw = realLyrics.substring(startIndex + invalidLength, element.start);

var elementText = element.group(0) ?? "";
span.index = startIndex;
span.length = element.start - span.index - invalidLength;
invalidLength += elementText.length;
startIndex += span.length;

var time = (element.group(1)?.split(",") ?? ["0", "0"]);
span.start = int.parse(time[0]);
span.duration = int.parse(time[1]);
return span;
}).toList();
return spanList;
}

@override
bool isOK() {
return lyric.contains("LyricContent=") || advancedPattern.stringMatch(lyric) != null;
}
}
18 changes: 18 additions & 0 deletions lib/packages/lyrics_parser/parser_smart.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'models.dart';
import 'parser_lrc.dart';
import 'parser_qrc.dart';

///smart parser
///Parser is automatically selected
class LRCParserSmart extends LyricsParse {
LRCParserSmart(String lyric) : super(lyric);

@override
List<LyricsLineModel> parseLines({bool isMain = true}) {
var qrc = LRCParserQrc(lyric);
if (qrc.isOK()) {
return qrc.parseLines(isMain: isMain);
}
return LRCParserLrc(lyric).parseLines(isMain: isMain);
}
}
42 changes: 14 additions & 28 deletions lib/ui/dialogs/set_lrc_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,16 @@ void showLRCSetDialog(Track track, Color colorScheme) async {
final localLRCFiles = Lyrics.inst.lyricsFilesDevice(track);

if (embedded != '') {
try {
embedded.toLrc();
availableLyrics.add(
LyricsModel(
lyrics: embedded,
synced: true,
fromInternet: false,
isInCache: false,
file: null,
isEmbedded: true,
),
);
} catch (_) {
availableLyrics.add(
LyricsModel(
lyrics: embedded,
synced: false,
fromInternet: false,
isInCache: false,
file: null,
isEmbedded: true,
),
);
}
availableLyrics.add(
LyricsModel(
lyrics: embedded,
synced: embedded.parseLRC() != null,
fromInternet: false,
isInCache: false,
file: null,
isEmbedded: true,
),
);
}
if (cachedTxt.existsSync()) {
availableLyrics.add(
Expand Down Expand Up @@ -130,10 +116,10 @@ void showLRCSetDialog(Track track, Color colorScheme) async {
void showEditCachedSyncedTimeOffsetDialog(LyricsModel l) async {
Lrc? lrc;
int offsetMS = 0;
try {
lrc = l.lyrics.toLrc();
offsetMS = lrc.offset ?? 0;
} catch (_) {}

lrc = l.lyrics.parseLRC();
offsetMS = lrc?.offset ?? 0;

final newOffset = offsetMS.obs;
Timer? timer;
void updatey(bool increase) {
Expand Down

0 comments on commit b675fc8

Please sign in to comment.