type
PLyricLine = ^TLyricLine;
TLyricLine = record
MilliSeconds: Cardinal; //歌词在歌曲中按照毫秒计算的出现时间
Lyric: ShortString;
end;
TLyricLines = array of TLyricLine;
TLyricParsingEvent = procedure(Sender: TObject; Percent: Double) of object;
TCustomLyricParser = class(TObject)
private
FOnParsing: TLyricParsingEvent;
protected
procedure DoParsing(Percent: Double); dynamic;
function ParseTimeTextToMillisec(TimeText: string; var Millisec: Cardinal): Boolean;
procedure Sort(var LyricLines: TLyricLines); virtual;
public
procedure ParseFrom(var LyricLines: TLyricLines; SourceLyrics: TStrings); virtual; abstract;
procedure ParseTo(const LyricLines: TLyricLines; DestLyrics: TStrings); virtual; abstract;
property OnParsing: TLyricParsingEvent read FOnParsing write FOnParsing;
end;
ELyricLinesIsEmpty = class(Exception);
ELyricOutOfBounds = class(Exception);
TLyricParser = class(TCustomLyricParser)
private
function ParseLine(const LineText: string; var Time, Lyric: string): Integer;
protected
public
procedure ParseFrom(var LyricLines: TLyricLines; SourceLyrics: TStrings); override;
procedure ParseTo(const LyricLines: TLyricLines; DestLyrics: TStrings); override;
end;
implementation
uses UCommonProcs;
procedure InvertLyricLine(R1, R2: PLyricLine);
var
L: TLyricLine;
begin
L.MilliSeconds := R1^.MilliSeconds;
L.Lyric := R1^.Lyric;
R1^.MilliSeconds := R2^.MilliSeconds;
R1^.Lyric := R2^.Lyric;
R2^.MilliSeconds := L.MilliSeconds;
R2^.Lyric := L.Lyric;
end;
{ TCustomLyricParser }
procedure TCustomLyricParser.DoParsing(Percent: Double);
begin
if Assigned(FOnParsing) then FOnParsing(Self, Percent);
end;
function TCustomLyricParser.ParseTimeTextToMillisec(TimeText: string;
var Millisec: Cardinal): Boolean;
var
ttArray: array of Cardinal;
ttLen: Integer;
s: string;
c: Char;
begin
Result := False;
ttLen := 2;
SetLength(ttArray, ttLen + 1);
try
s := '';
while (Length(TimeText) > 0) do
begin
c := TimeText[Length(TimeText)];
if not (c in ['.', ':']) then
begin
if not (c in ['[', ']']) then
s := c + s
end
else begin
try
ttArray[ttLen] := StrToInt(s);
Result := True;
except
Result := False;
Exit;
end;
s := '';
Dec(ttLen);
end;
Delete(TimeText, Length(TimeText), 1);
end;
if s <> '' then
try
ttArray[0] := StrToInt(s);
except
Result := False;
Exit;
end;
if Result then
begin
Millisec := 0;
for ttLen := High(ttArray) downto 0 do
case ttLen of
2: //毫秒
begin
Millisec := ttArray[ttLen];
if (Millisec > 0) and (Millisec < 100) then
Millisec := Millisec * 10;
end;
1: //秒
Millisec := ttArray[ttLen] * 1000 + Millisec;
0: //分
Millisec := ttArray[ttLen] * 60 * 1000 + Millisec;
else
Result := False;
Break;
end;
end;
finally
ttArray := nil;
end;
end;
procedure TCustomLyricParser.Sort(var LyricLines: TLyricLines);
procedure QSort(var Lyric: TLyricLines; Left, Right: Word);
var
l, r, Pivot: Integer;
begin
l := Left;
r := Right;
Pivot := Lyric[(Left + Right) div 2].MilliSeconds;
while l < r do
begin
while Lyric[l].MilliSeconds < Pivot do Inc(l);
while Lyric[r].MilliSeconds > Pivot do Dec(r);
if l >= r then Break;
InvertLyricLine(@Lyric[l], @Lyric[r]);
if l <> Pivot then Dec(r);
if r <> Pivot then Inc(l);
end;
if l = r then Inc(l);
if Left < r then QSort(Lyric, Left, l + 1);
if l < Right then QSort(Lyric, r + 1, Right);
end; // 好像有bug,容易堆栈溢出
procedure Bubble(var Lyric: TLyricLines; Size: Integer);
var
I, Pass: Integer;
begin
for Pass := 1 to Size - 1 do
begin
I := 0;
while i < Size - Pass do
begin
if Lyric.MilliSeconds > Lyric[I + 1].MilliSeconds then
InvertLyricLine(@Lyric, @Lyric[I + 1]);
Inc(I);
end;
end;
end;
begin
Bubble(LyricLines, Length(LyricLines));
end;
{ TLyricParser }
procedure TLyricParser.ParseFrom(var LyricLines: TLyricLines;
SourceLyrics: TStrings);
var
iLine: Integer;
sTime, sSingleTime, sLyric: string;
ResMSec: Cardinal;
iPos: Integer;
begin
for iLine := 0 to SourceLyrics.Count - 1 do
begin
if ParseLine(Trim(SourceLyrics[iLine]), sTime, sLyric) = 0 then Continue;
repeat
iPos := Pos(']', sTime);
//Assert(iPos > 0, 'Assert: TLyricParser.ParseFrom::iPos<=0');
sSingleTime := Copy(sTime, 1, iPos);
//Assert(sSingleTime <> '', 'Assert: TLyricParser.ParseFrom::sSingleTime=''''');
Delete(sTime, 1, iPos);
if not ParseTimeTextToMillisec(Trim(sSingleTime), ResMSec) then Continue;
SetLength(LyricLines, Length(LyricLines) + 1);
with LyricLines[High(LyricLines)] do
begin
MilliSeconds := ResMSec;
Lyric := sLyric;
end;
until sTime = '';
DoParsing(GetPercentFrom(iLine + 1, SourceLyrics.Count));
end;
Sort(LyricLines);
end;
function TLyricParser.ParseLine(const LineText: string; var Time,
Lyric: string): Integer;
type
FoundArea = (faSplitFirst, faSplitLast, faText);
var
pIndex: Integer;
TempStr: string;
Area: FoundArea;
begin
Time := '';
TempStr := '';
Result := 0;
Area := faText;
for pIndex := 1 to Length(LineText) do
begin
TempStr := TempStr + LineText[pIndex];
case LineText[pIndex] of
'[':
Area := faSplitFirst;
']':
if Area = faSplitFirst then
begin
Area := faSplitLast;
Inc(Result);
Time := Time + TempStr;
TempStr := '';
end
else
Area := faText;
else // case else
if Area = faSplitLast then Area := faText;
end; // case
end; // for
Lyric := TempStr;
end;
procedure TLyricParser.ParseTo(const LyricLines: TLyricLines;
DestLyrics: TStrings);
var
iLine: Integer;
iLineTotal: Integer;
begin
iLineTotal := High(LyricLines);
for iLine := Low(LyricLines) to iLineTotal do
begin
DoParsing(GetPercentFrom(iLine, iLineTotal));
end;
end;