* Initial check-in

git-svn-id: trunk@11865 -
This commit is contained in:
michael 2008-10-05 14:37:01 +00:00
parent 2add1ed6fc
commit ff2372080e
2 changed files with 634 additions and 0 deletions

1
.gitattributes vendored
View File

@ -935,6 +935,7 @@ packages/cdrom/src/cdromioctl.pp svneol=native#text/plain
packages/cdrom/src/cdromlin.inc svneol=native#text/plain
packages/cdrom/src/cdromw32.inc svneol=native#text/plain
packages/cdrom/src/discid.pp svneol=native#text/plain
packages/cdrom/src/fpcddb.pp svneol=native#text/plain
packages/cdrom/src/lincd.pp svneol=native#text/plain
packages/cdrom/src/major.pp svneol=native#text/plain
packages/cdrom/src/scsidefs.pp svneol=native#text/plain

View File

@ -0,0 +1,633 @@
{
Copyright (c) 2008 by Michael Van Canneyt
Unit to parse CDDB responses and construct a list
of tracks in a CD.
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
unit fpcddb;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
Type
TCDDisk = Class;
{ TCDTrack }
TCDTrack = Class(TCollectionItem)
private
FDuration: TDateTime;
FExtra: String;
FPerformer: String;
FTitle: String;
function GetPerformer: String;
Public
Procedure Assign(Source : TPersistent); override;
Published
Property Title : String Read FTitle Write FTitle;
Property Performer : String Read GetPerformer Write FPerformer;
Property Extra : String Read FExtra Write FExtra;
Property Duration : TDateTime Read FDuration Write FDuration;
end;
{ TCDTracks }
TCDTracks = Class(TCollection)
private
FCDDisk: TCDDisk;
function GetT(AIndex : Integer): TCDTrack;
procedure SetT(AIndex : Integer; const AValue: TCDTrack);
Public
Property CDDisk : TCDDisk Read FCDDisk;
Function AddTrack(Const ATitle,AExtra : String; ADuration : TDateTime) : TCDTrack;
Function AddTrack(Const ATitle,AExtra : String) : TCDTrack;
Function AddTrack(Const ATitle : String) : TCDTrack;
Property Track[AIndex : Integer] : TCDTrack Read GetT Write SetT; default;
end;
{ TCDDisk }
TCDDisk = Class(TCollectionItem)
private
FDiskID: Integer;
FExtra: String;
FPerformer: String;
FPlayOrder: String;
FTitle: String;
FTracks: TCDTracks;
FYear: Word;
function GetDiskID: String;
procedure SetDiskID(const AValue: String);
procedure SetTracks(const AValue: TCDTracks);
Protected
Function CreateTracks : TCDTracks; virtual;
Public
Constructor Create(ADiskID : Integer);
Constructor Create(ACollection : TCollection); override;
Procedure Assign(Source : TPersistent); override;
Property IntDiscID : Integer Read FDiskID Write FDiskID;
Published
Property PlayOrder : String Read FPlayOrder Write FPlayOrder;
Property Year : Word Read FYear Write FYear;
Property Title : String Read FTitle Write FTitle;
Property Performer : String Read FPerformer Write FPerformer;
Property Extra : String Read FExtra Write FExtra;
Property DiscID : String Read GetDiskID Write SetDiskID;
property Tracks : TCDTracks Read FTracks Write SetTracks;
end;
{ TCDDisks }
TCDDisks = Class(TCollection)
private
function GetD(AIndex : Integer): TCDDisk;
procedure SetD(AIndex : Integer; const AValue: TCDDisk);
Public
Function AddDisk(ADiscID : String) : TCDDisk;
Function AddDisk : TCDDisk;
Property Disk[AIndex : Integer] : TCDDisk Read GetD Write SetD; default;
end;
{ TCDDBQueryMatch }
TCDDBQueryMatch = Class(TCollectionItem)
private
FCategory: String;
FDiscID: Integer;
FPerformer: String;
FTitle: String;
Public
Procedure Assign(Source : TPersistent); override;
Published
Property DiscID : Integer Read FDiscID Write FDiscID;
Property Category : String Read FCategory Write FCategory;
Property Title : String Read FTitle Write FTitle;
Property Performer : String Read FPerformer Write FPerformer;
end;
{ TCDDBQueryMatches }
TCDDBQueryMatches = Class(TCollection)
private
function GetM(AIndex : Integer): TCDDBQueryMatch;
procedure SetM(AIndex : Integer; const AValue: TCDDBQueryMatch);
Public
Function AddMatch(Const ADiscID: Integer; Const ACategory,ATitle, APerformer : String) : TCDDBQueryMatch;
Function AddMatch(Const ADiscID,ACategory,ATitle, APerformer : String) : TCDDBQueryMatch;
Function AddMatch : TCDDBQueryMatch;
Property Match[AIndex : Integer] :TCDDBQueryMatch Read GetM Write SetM; default;
end;
{ TCDDBParser }
TCDDBParser = Class(TComponent)
private
FDisks: TCDDisks;
FDisk : TCDDisk;
function ParseExtraDiskData(AData: String): Boolean;
function ParseExtraTrackData(ATrack: TCDTrack; AData: String): Boolean;
procedure SetDisks(const AValue: TCDDisks);
procedure SplitQueryResponse(AResponse: String; var ACategory, ADiscID, ATitle, APerformer: String);
procedure SplitTitle(const ALine: String; var AArtist, ATitle: String;
PreferTitle: boolean);
function StdReplacements(S: String): String;
Protected
Procedure CheckDisk;
function CheckCDDBCmdResult(var S: String): Integer;
Function CreateDisks :TCDDisks; virtual;
Function IsComment(Const L : String) : Boolean;
Function GetTrack(Const TrackNo : Integer) : TCDTrack;
Property Disk : TCDDisk Read FDisk;
Public
Constructor Create(AOwner : TComponent); override;
Destructor Destroy; override;
Function ParseCDDBReadResponse(Response : TStrings; WithHeader : Boolean = True) : Integer;
Function ParseCDDBReadResponse(Response : TStream; WithHeader : Boolean = True) : Integer;
Function ParseCDDBQueryResponse(Response : TStrings; Matches : TCDDBQueryMatches; WithHeader : Boolean = True) : Integer;
Function ParseCDDBQueryResponse(Response : TStream; Matches : TCDDBQueryMatches; WithHeader : Boolean = True) : Integer;
Published
Property Disks : TCDDisks Read FDisks Write SetDisks;
end;
ECDDBParser = Class(Exception);
Function DiscIDToStr(ID : Integer) : String;
Function StrToDiscID(S : String) : Integer;
implementation
Resourcestring
SErrNoDisk = 'No disk active';
SErrInvalidTrackNo = 'Invalid track number: %d';
SErrParsingLine = 'An error occured while parsing line %d of the response: %s';
SErrCDDBResponse = 'CDDB error in command response: %s';
function DiscIDToStr(ID: Integer): String;
begin
Result:=LowerCase(Format('%.8x',[ID]));
end;
function StrToDiscID(S: String): Integer;
begin
Result:=StrToIntDef('$'+S,-1);
end;
{ TCDTrack }
function TCDTrack.GetPerformer: String;
begin
Result:=FPerformer;
If (Result='') and Assigned(Collection) and (Collection is TCDTracks) then
If Assigned(TCDTracks(Collection).CDDisk) then
Result:=TCDTracks(Collection).CDDisk.Performer;
end;
procedure TCDTrack.Assign(Source: TPersistent);
Var
T : TCDTrack;
begin
if (Source is TCDTrack) then
begin
T:=Source as TCDTrack;
FTitle:=T.FTitle;
FExtra:=T.FExtra;
FPerformer:=T.FPerformer;
FDuration:=T.FDuration;
end
else
inherited Assign(Source);
end;
{ TCDDisk }
procedure TCDDisk.SetTracks(const AValue: TCDTracks);
begin
if FTracks=AValue then exit;
FTracks.Assign(AValue);
end;
function TCDDisk.GetDiskID: String;
begin
Result:=DiscIDToStr(FdiskID);
end;
procedure TCDDisk.SetDiskID(const AValue: String);
begin
FDiskID:=StrToDiscID(AValue);
end;
function TCDDisk.CreateTracks: TCDTracks;
begin
Result:=TCDTracks.Create(TCDTrack);
end;
constructor TCDDisk.Create(ADiskID: Integer);
begin
FDiskID:=ADiskID;
Create(Nil);
end;
constructor TCDDisk.Create(ACollection: TCollection);
begin
FTracks:=CreateTracks;
FTracks.FCDDisk:=Self;
inherited Create(ACollection);
end;
procedure TCDDisk.Assign(Source: TPersistent);
Var
D : TCDDisk;
begin
if Source is TCDDisk then
begin
D:=Source as TCDDisk;
FTitle:=D.FTitle;
FExtra:=D.FExtra;
FPerformer:=D.FPerformer;
FYear:=D.FYear;
FTracks.Assign(D.FTracks);
FPLayOrder:=D.FPlayOrder;
end
else
inherited Assign(Source);
end;
{ TCDTracks }
function TCDTracks.GetT(AIndex : Integer): TCDTrack;
begin
Result:=Items[AIndex] as TCDTrack;
end;
procedure TCDTracks.SetT(AIndex : Integer; const AValue: TCDTrack);
begin
Items[AIndex]:=AValue;
end;
function TCDTracks.AddTrack(const ATitle, AExtra: String; ADuration: TDateTime
): TCDTrack;
begin
Result:=Add as TCDTrack;
Result.Title:=ATitle;
Result.Extra:=AExtra;
Result.Duration:=ADuration;
end;
function TCDTracks.AddTrack(const ATitle, AExtra: String): TCDTrack;
begin
Result:=AddTrack(ATitle,AExtra,0);
end;
function TCDTracks.AddTrack(const ATitle: String): TCDTrack;
begin
Result:=AddTrack(ATitle,'',0);
end;
{ TCDDisks }
function TCDDisks.GetD(AIndex : Integer): TCDDisk;
begin
Result:=Items[AIndex] as TCDDisk;
end;
procedure TCDDisks.SetD(AIndex : Integer; const AValue: TCDDisk);
begin
Items[AIndex]:=AValue;
end;
function TCDDisks.AddDisk(ADiscID: String): TCDDisk;
begin
Result:=Self.AddDisk();
Result.DiscID:=ADiscID;
end;
function TCDDisks.AddDisk: TCDDisk;
begin
Result:=Add as TCDDisk;
end;
{ TCDDBParser }
procedure TCDDBParser.SetDisks(const AValue: TCDDisks);
begin
if FDisks=AValue then exit;
FDisks.Assign(AValue);
end;
procedure TCDDBParser.CheckDisk;
begin
If (FDisk=Nil) then
Raise ECDDBParser.Create(SErrNoDisk)
end;
function TCDDBParser.CreateDisks: TCDDisks;
begin
Result:=TCDDisks.Create(TCDDisk);
end;
function TCDDBParser.IsComment(const L: String): Boolean;
begin
Result:=(Length(L)=0) or (L[1]='#');
end;
function TCDDBParser.GetTrack(const TrackNo: Integer): TCDTrack;
begin
If (TrackNo<0) then
Raise ECDDBParser.CreateFmt(SErrInvalidTrackNo,[TrackNo]);
CheckDisk;
If (TrackNo>FDisk.Tracks.Count) then
Raise ECDDBParser.CreateFmt(SErrInvalidTrackNo,[TrackNo]);
If (TrackNo=FDisk.Tracks.Count) then
Result:=FDisk.Tracks.AddTrack('')
else
Result:=FDisk.Tracks[TrackNo]
end;
constructor TCDDBParser.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDisks:=CreateDisks;
end;
destructor TCDDBParser.Destroy;
begin
FreeAndNil(FDisks);
inherited Destroy;
end;
Function TCDDBParser.StdReplacements(S : String) : String;
begin
Result:=StringReplace(S,'\n',sLineBreak,[rfReplaceAll]);
end;
Function TCDDBParser.ParseExtraDiskData(AData : String) : Boolean;
begin
FDisk.Extra:=FDisk.Extra+StdReplacements(AData);
end;
Function TCDDBParser.ParseExtraTrackData(ATrack : TCDTrack; AData : String) : Boolean;
begin
ATrack.Extra:=ATrack.Extra+StdReplacements(AData);
end;
Procedure TCDDBParser.SplitTitle(Const ALine: String; Var AArtist, ATitle : String; PreferTitle : boolean);
Var
P,L : Integer;
begin
// Artist / Title
L:=Length(ALine);
P:=Pos('/',ALine);
If (P=0) and Not PreferTitle then
P:=L+1;
AArtist:=Trim(Copy(ALine,1,P-1));
ATitle:=Trim(Copy(ALine,P+1,L-P));
end;
Function TCDDBParser.ParseCDDBReadResponse(Response: TStrings; WithHeader : Boolean = True) : Integer;
Var
I,P : Integer;
L,Args,A,T : String;
TrackID : Integer;
Track : TCDTrack;
begin
Result:=-1;
FDisks.Clear;
If WithHeader and (Response.Count>0) then
begin
L:=Response[0];
If Not (CheckCDDBCmdResult(L) in [200,210]) then
Raise ECDDBParser.CreateFmt(SErrCDDBResponse,[L]);
end;
FDisk:=Nil;
Result:=0;
Try
Try
I:=Ord(WithHeader);
While (I<Response.Count) do
begin
L:=Response[i];
If Not IsComment(L) then
begin
P:=Pos('=',L);
Args:=Copy(L,P+1,Length(L)-P);
L:=Uppercase(Copy(L,1,P-1));
If (L='DISCID') then
FDisk:=FDisks.AddDisk(Args)
else
begin
CheckDisk;
If (L='DTITLE') then
begin
SplitTitle(Args,A,T,True);
FDisk.Title:=T;
FDisk.Performer:=A;
end
else if (L='EXTD') then
ParseExtraDiskData(Args)
else if (Copy(L,1,6)='TTITLE') then
begin
Delete(L,1,6);
TrackID:=StrToIntDef(L,-1);
Track:=GetTrack(TrackID);
SplitTitle(Args,A,T,True);
Track.Title:=T;
Track.Performer:=A;
end
else if (Copy(L,1,6)='EXTT') then
begin
Delete(L,1,6);
TrackID:=StrToIntDef(L,-1);
Track:=GetTrack(TrackID);
ParseExtraTrackData(Track,Args);
end
else if (Copy(L,1,9)='PLAYORDER') then
begin
FDisk.PlayOrder:=Trim(Args);
end;
end;
end;
Inc(I);
end;
except
On E : Exception do
begin
E.Message:=Format(SErrParsingLine,[I,E.MEssage]);
Raise;
end;
end;
Result:=FDisks.Count;
Finally
FDisk:=Nil;
end;
end;
Function TCDDBParser.ParseCDDBReadResponse(Response: TStream; WithHeader : Boolean = True) : Integer;
Var
L : TStringList;
begin
L:=TStringList.Create;
try
L.LoadFromStream(Response);
Result:=ParseCDDBReadResponse(L,WithHeader);
finally
L.Free;
end;
end;
function TCDDBParser.ParseCDDBQueryResponse(Response: TStrings;
Matches: TCDDBQueryMatches; WithHeader: Boolean): Integer;
Var
I,CmdRes : Integer;
L : String;
D,C,T,P : String;
begin
Matches.Clear;
Result:=-1;
If WithHeader and (Response.Count>0) then
begin
L:=Response[0];
CmdRes:=CheckCDDBCmdResult(L);
If (CmdRes=200) then
begin
SplitQueryResponse(L,C,D,T,P);
Matches.AddMatch(D,C,T,P);
Result:=1;
Exit;
end
else if (CmdRes<>210) then
Raise ECDDBParser.CreateFmt(SerrCDDBResponse,[L]);
end;
For I:=Ord(WithHeader) to Response.Count-1 do
begin
SplitQueryResponse(Response[i],C,D,T,P);
Matches.AddMatch(D,C,T,P);
end;
Result:=Matches.Count;
end;
function TCDDBParser.ParseCDDBQueryResponse(Response: TStream;
Matches: TCDDBQueryMatches; WithHeader: Boolean): Integer;
Var
L : TStringList;
begin
L:=TStringList.Create;
try
L.LoadFromStream(Response);
Result:=ParseCDDBQueryResponse(L,Matches,WithHeader);
finally
L.Free;
end;
end;
Function TCDDBParser.CheckCDDBCmdResult(Var S : String) : Integer;
Var
P : integer;
begin
P:=Pos(' ',S);
If (P=0) then
P:=Length(S)+1;
Result:=StrToIntDef(Copy(S,1,P-1),0);
Delete(S,1,P);
end;
Procedure TCDDBParser.SplitQueryResponse(AResponse :String; Var ACategory, ADiscID, ATitle, APerformer : String);
Var
P : Integer;
begin
P:=Pos(' ',AResponse);
ACategory:=Copy(AResponse,1,P-1);
Delete(AResponse,1,P);
P:=Pos(' ',AResponse);
ADiscId:=Copy(AResponse,1,P-1);
Delete(AResponse,1,P);
SplitTitle(AResponse,APerformer,ATitle,True);
end;
{ TCDDBQueryMatches }
function TCDDBQueryMatches.GetM(AIndex : Integer): TCDDBQueryMatch;
begin
Result:=TCDDBQueryMatch(Items[AIndex]);
end;
procedure TCDDBQueryMatches.SetM(AIndex : Integer; const AValue: TCDDBQueryMatch
);
begin
Items[AIndex]:=AValue;
end;
function TCDDBQueryMatches.AddMatch(const ADiscID: Integer; const ACategory,
ATitle, APerformer: String): TCDDBQueryMatch;
begin
Result:=AddMatch();
Result.DiscID:=ADiscID;
Result.Category:=ACategory;
Result.Title:=ATitle;
Result.Performer:=APerformer;
end;
function TCDDBQueryMatches.AddMatch(const ADiscID, ACategory, ATitle, APerformer : String): TCDDBQueryMatch;
begin
Result:=AddMatch(StrToDiscID(ADiscID),ACategory,ATitle,APerformer);
end;
function TCDDBQueryMatches.AddMatch: TCDDBQueryMatch;
begin
Result:=Add as TCDDBQueryMatch;
end;
{ TCDDBQueryMatch }
procedure TCDDBQueryMatch.Assign(Source: TPersistent);
Var
M : TCDDBQueryMatch;
begin
if Source is TCDDBQueryMatch then
begin
M:=Source as TCDDBQueryMatch;
FDiscID:=M.FDiscID;
FCategory:=M.FCategory;
FPerformer:=M.FPerformer;
FTitle:=M.FTitle;
end
else
inherited Assign(Source);
end;
end.