aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--unicode/src/base/UCovers.pas6
-rw-r--r--unicode/src/base/UFiles.pas136
-rw-r--r--unicode/src/base/UPath.pas325
-rw-r--r--unicode/src/base/UPlaylist.pas4
-rw-r--r--unicode/src/base/USong.pas499
-rw-r--r--unicode/src/base/UXMLSong.pas37
-rw-r--r--unicode/src/media/UMediaCore_FFmpeg.pas111
7 files changed, 800 insertions, 318 deletions
diff --git a/unicode/src/base/UCovers.pas b/unicode/src/base/UCovers.pas
index 0dbe672a..228a0783 100644
--- a/unicode/src/base/UCovers.pas
+++ b/unicode/src/base/UCovers.pas
@@ -211,9 +211,11 @@ end;
procedure TCoverDatabase.Open();
var
Version: integer;
- Filename: IPath;
+ Filename, TmpPath: IPath;
begin
- Filename := Platform.GetGameUserPath().Append(COVERDB_FILENAME);
+ // TODO: remove fpc refcount workaround
+ TmpPath := Platform.GetGameUserPath();
+ Filename := TmpPath.Append(COVERDB_FILENAME);
DB := TSQLiteDatabase.Create(Filename.ToUTF8());
Version := GetVersion();
diff --git a/unicode/src/base/UFiles.pas b/unicode/src/base/UFiles.pas
index 9996eeb1..a46d4e0d 100644
--- a/unicode/src/base/UFiles.pas
+++ b/unicode/src/base/UFiles.pas
@@ -105,84 +105,84 @@ var
begin
// Relative := true; // override (idea - use shift+S to save with relative)
Result := ssrOK;
- SongFile := nil;
try
- SongFile := TTextFileStream.Create(Name, fmCreate);
-
- if (Song.Encoding = encUTF8) then
- SongFile.Write(UTF8_BOM);
-
- SongFile.WriteLine('#ENCODING:' + EncodingName(Song.Encoding));
- SongFile.WriteLine('#TITLE:' + EncodeToken(Song.Title));
- SongFile.WriteLine('#ARTIST:' + EncodeToken(Song.Artist));
-
- if Song.Creator <> '' then SongFile.WriteLine('#CREATOR:' + EncodeToken(Song.Creator));
- if Song.Edition <> 'Unknown' then SongFile.WriteLine('#EDITION:' + EncodeToken(Song.Edition));
- if Song.Genre <> 'Unknown' then SongFile.WriteLine('#GENRE:' + EncodeToken(Song.Genre));
- if Song.Language <> 'Unknown' then SongFile.WriteLine('#LANGUAGE:' + EncodeToken(Song.Language));
-
- SongFile.WriteLine('#MP3:' + EncodeToken(Song.Mp3.ToUTF8));
- if Song.Cover.IsSet then SongFile.WriteLine('#COVER:' + EncodeToken(Song.Cover.ToUTF8));
- if Song.Background.IsSet then SongFile.WriteLine('#BACKGROUND:' + EncodeToken(Song.Background.ToUTF8));
- if Song.Video.IsSet then SongFile.WriteLine('#VIDEO:' + EncodeToken(Song.Video.ToUTF8));
-
- if Song.VideoGAP <> 0 then SongFile.WriteLine('#VIDEOGAP:' + FloatToStr(Song.VideoGAP));
- if Song.Resolution <> 4 then SongFile.WriteLine('#RESOLUTION:' + IntToStr(Song.Resolution));
- if Song.NotesGAP <> 0 then SongFile.WriteLine('#NOTESGAP:' + IntToStr(Song.NotesGAP));
- if Song.Start <> 0 then SongFile.WriteLine('#START:' + FloatToStr(Song.Start));
- if Song.Finish <> 0 then SongFile.WriteLine('#END:' + IntToStr(Song.Finish));
- if Relative then SongFile.WriteLine('#RELATIVE:yes');
-
- SongFile.WriteLine('#BPM:' + FloatToStr(Song.BPM[0].BPM / 4));
- SongFile.WriteLine('#GAP:' + FloatToStr(Song.GAP));
-
- RelativeSubTime := 0;
- for B := 1 to High(Song.BPM) do
- SongFile.WriteLine('B ' + FloatToStr(Song.BPM[B].StartBeat) + ' '
- + FloatToStr(Song.BPM[B].BPM/4));
-
- for C := 0 to Lines.High do
- begin
- for N := 0 to Lines.Line[C].HighNote do
+ SongFile := TMemTextFileStream.Create(Name, fmCreate);
+ try
+ if (Song.Encoding = encUTF8) then
+ SongFile.WriteString(UTF8_BOM);
+
+ SongFile.WriteLine('#ENCODING:' + EncodingName(Song.Encoding));
+ SongFile.WriteLine('#TITLE:' + EncodeToken(Song.Title));
+ SongFile.WriteLine('#ARTIST:' + EncodeToken(Song.Artist));
+
+ if Song.Creator <> '' then SongFile.WriteLine('#CREATOR:' + EncodeToken(Song.Creator));
+ if Song.Edition <> 'Unknown' then SongFile.WriteLine('#EDITION:' + EncodeToken(Song.Edition));
+ if Song.Genre <> 'Unknown' then SongFile.WriteLine('#GENRE:' + EncodeToken(Song.Genre));
+ if Song.Language <> 'Unknown' then SongFile.WriteLine('#LANGUAGE:' + EncodeToken(Song.Language));
+
+ SongFile.WriteLine('#MP3:' + EncodeToken(Song.Mp3.ToUTF8));
+ if Song.Cover.IsSet then SongFile.WriteLine('#COVER:' + EncodeToken(Song.Cover.ToUTF8));
+ if Song.Background.IsSet then SongFile.WriteLine('#BACKGROUND:' + EncodeToken(Song.Background.ToUTF8));
+ if Song.Video.IsSet then SongFile.WriteLine('#VIDEO:' + EncodeToken(Song.Video.ToUTF8));
+
+ if Song.VideoGAP <> 0 then SongFile.WriteLine('#VIDEOGAP:' + FloatToStr(Song.VideoGAP));
+ if Song.Resolution <> 4 then SongFile.WriteLine('#RESOLUTION:' + IntToStr(Song.Resolution));
+ if Song.NotesGAP <> 0 then SongFile.WriteLine('#NOTESGAP:' + IntToStr(Song.NotesGAP));
+ if Song.Start <> 0 then SongFile.WriteLine('#START:' + FloatToStr(Song.Start));
+ if Song.Finish <> 0 then SongFile.WriteLine('#END:' + IntToStr(Song.Finish));
+ if Relative then SongFile.WriteLine('#RELATIVE:yes');
+
+ SongFile.WriteLine('#BPM:' + FloatToStr(Song.BPM[0].BPM / 4));
+ SongFile.WriteLine('#GAP:' + FloatToStr(Song.GAP));
+
+ RelativeSubTime := 0;
+ for B := 1 to High(Song.BPM) do
+ SongFile.WriteLine('B ' + FloatToStr(Song.BPM[B].StartBeat) + ' '
+ + FloatToStr(Song.BPM[B].BPM/4));
+
+ for C := 0 to Lines.High do
begin
- with Lines.Line[C].Note[N] do
+ for N := 0 to Lines.Line[C].HighNote do
begin
- //Golden + Freestyle Note Patch
- case Lines.Line[C].Note[N].NoteType of
- ntFreestyle: NoteState := 'F ';
- ntNormal: NoteState := ': ';
- ntGolden: NoteState := '* ';
- end; // case
- S := NoteState + IntToStr(Start-RelativeSubTime) + ' '
- + IntToStr(Length) + ' '
- + IntToStr(Tone) + ' '
- + EncodeToken(Text);
-
- SongFile.WriteLine(S);
- end; // with
- end; // N
-
- if C < Lines.High then // don't write end of last sentence
- begin
- if not Relative then
- S := '- ' + IntToStr(Lines.Line[C+1].Start)
- else
+ with Lines.Line[C].Note[N] do
+ begin
+ //Golden + Freestyle Note Patch
+ case Lines.Line[C].Note[N].NoteType of
+ ntFreestyle: NoteState := 'F ';
+ ntNormal: NoteState := ': ';
+ ntGolden: NoteState := '* ';
+ end; // case
+ S := NoteState + IntToStr(Start-RelativeSubTime) + ' '
+ + IntToStr(Length) + ' '
+ + IntToStr(Tone) + ' '
+ + EncodeToken(Text);
+
+ SongFile.WriteLine(S);
+ end; // with
+ end; // N
+
+ if C < Lines.High then // don't write end of last sentence
begin
- S := '- ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime) +
- ' ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime);
- RelativeSubTime := Lines.Line[C+1].Start;
+ if not Relative then
+ S := '- ' + IntToStr(Lines.Line[C+1].Start)
+ else
+ begin
+ S := '- ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime) +
+ ' ' + IntToStr(Lines.Line[C+1].Start - RelativeSubTime);
+ RelativeSubTime := Lines.Line[C+1].Start;
+ end;
+ SongFile.WriteLine(S);
end;
- SongFile.WriteLine(S);
- end;
- end; // C
+ end; // C
- SongFile.WriteLine('E');
+ SongFile.WriteLine('E');
+ finally
+ SongFile.Free;
+ end;
except
Result := ssrFileError;
end;
-
- SongFile.Free;
end;
end.
diff --git a/unicode/src/base/UPath.pas b/unicode/src/base/UPath.pas
index fc8c028e..c74d1a16 100644
--- a/unicode/src/base/UPath.pas
+++ b/unicode/src/base/UPath.pas
@@ -45,6 +45,15 @@ type
IPath = interface;
{**
+ * TUnicodeMemoryStream
+ *}
+ TUnicodeMemoryStream = class(TMemoryStream)
+ public
+ procedure LoadFromFile(const FileName: IPath);
+ procedure SaveToFile(const FileName: IPath);
+ end;
+
+ {**
* TBinaryFileStream (inherited from THandleStream)
*}
{$IFDEF MSWINDOWS}
@@ -60,22 +69,61 @@ type
end;
{**
- * TUnicodeMemoryStream
+ * TTextFileStream
*}
- TUnicodeMemoryStream = class(TMemoryStream)
+ TTextFileStream = class(TStream)
+ protected
+ fLineBreak: RawByteString;
+ fFilename: IPath;
+ fMode: word;
+
+ function ReadLine(var Success: boolean): RawByteString; overload; virtual; abstract;
public
- procedure LoadFromFile(const FileName: IPath);
- procedure SaveToFile(const FileName: IPath);
- end;
+ constructor Create(Filename: IPath; Mode: Word);
- TTextFileStream = class(TBinaryFileStream)
+ function ReadString(): RawByteString; virtual; abstract;
function ReadLine(var Line: UTF8String): boolean; overload;
function ReadLine(var Line: AnsiString): boolean; overload;
- procedure Write(Str: RawByteString);
- procedure WriteLine(Line: RawByteString = '');
+
+ procedure WriteString(const Str: RawByteString); virtual;
+ procedure WriteLine(const Line: RawByteString); virtual;
+
+ property LineBreak: RawByteString read fLineBreak write fLineBreak;
+ property Filename: IPath read fFilename;
end;
{**
+ * TMemTextStream
+ *}
+ TMemTextFileStream = class(TTextFileStream)
+ private
+ fStream: TMemoryStream;
+ protected
+ function GetSize: Int64; override;
+
+ {**
+ * Copies fStream.Memory from StartPos to EndPos-1 to the result string;
+ *}
+ function CopyMemString(StartPos: int64; EndPos: int64): RawByteString;
+ public
+ constructor Create(Filename: IPath; Mode: Word);
+ destructor Destroy(); override;
+
+ function Read(var Buffer; Count: Longint): Longint; override;
+ function Write(const Buffer; Count: Longint): Longint; override;
+ function Seek(Offset: Longint; Origin: Word): Longint; override;
+ function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
+
+ function ReadLine(var Success: boolean): RawByteString; override;
+ function ReadString(): RawByteString; override;
+ end;
+
+ {**
+ TUnicodeIniStream = class()
+ end;
+ *}
+
+ {**
* pdKeep: Keep path as is, neither remove or append a delimiter
* pdAppend: Append a delimiter if path does not have a trailing one
* pdRemove: Remove a trailing delimiter from the path
@@ -85,8 +133,44 @@ type
IPathDynArray = array of IPath;
{**
- * IPath
- * The Path's pathname is immutable and cannot be changed after creation.
+ * An IPath represents a filename, a directory or a filesystem path in general.
+ * It hides some of the operating system's specifics like path delimiters
+ * and encodings and provides an easy to use interface to handle them.
+ * Internally all paths are stored with the same path delimiter (PathDelim)
+ * and encoding (UTF-8). The transformation is already done AT THE CREATION of
+ * the IPath and hence calls to e.g. IPath.Equal() will not distinguish between
+ * Unix and Windows style paths.
+ *
+ * Create new paths with one of the Path() functions.
+ * If you need a string representation use IPath.ToNative/ToUTF8/ToWide.
+ * Note that due to the path-delimiter and encoding transformation the string
+ * might have changed. Path('one\test/path').ToUTF8() might return 'one/test/path'.
+ *
+ * It is recommended to use an IPath as long as possible without a string
+ * conversion (IPath.To...()). The whole Delphi (< 2009) and FPC RTL is ANSI
+ * only on Windows. If you would use for example FileExists(MyPath.ToNative)
+ * it would not find a file which contains characters that are not in the
+ * current locale. Same applies to AssignFile(), TFileStream.Create() and
+ * everything else in the RTL that expects a filename.
+ * As a rule of thumb: NEVER use any of the Delphi/FPC RTL filename functions
+ * if the filename parameter is not of a UTF8String or WideString type.
+ *
+ * If you need to open a file use TBinaryStream or TFileStream instead. Many
+ * of the RTL classes offer a LoadFromStream() method so ANSI Open() methods
+ * can be workaround.
+ *
+ * If there is only a ANSI and no IPath/UTF-8/WideString version and you cannot
+ * even pass a stream instead of a filename be aware that even if you know that
+ * a filename is ASCII only, subdirectories in an absolute path might contain
+ * some non-ASCII characters (for example the user's name) and hence might
+ * fail (if the characters are not in the current locale).
+ * It is rare but it happens.
+ *
+ * IMPORTANT:
+ * This interface needs the cwstring unit on Unix (Max OS X / Linux) systems.
+ * Cwstring functions (WideUpperCase, ...) cannot be used by external threads
+ * as FPC uses Thread-Local-Storage for the implementation. As a result do not
+ * call IPath stuff by external threads (e.g. in C callbacks or by SDL-threads).
*}
IPath = interface
['{686BF103-CE43-4598-B85D-A2C3AF950897}']
@@ -271,6 +355,7 @@ function PATH_NONE(): IPath;
implementation
uses
+ RTLConsts,
UFilesystem;
type
@@ -426,6 +511,8 @@ end;
function TPathImpl.ToUTF8(UseNativeDelim: boolean): UTF8String;
begin
+ AssertRefCount;
+
if (UseNativeDelim) then
Result := fName
else
@@ -583,20 +670,26 @@ end;
function TPathImpl.Append(const Child: RawByteString; DelimOption: TPathDelimOption): IPath;
begin
+ AssertRefCount;
Result := Append(Path(Child), DelimOption);
end;
function TPathImpl.Append(const Child: WideString; DelimOption: TPathDelimOption): IPath;
begin
+ AssertRefCount;
Result := Append(Path(Child), DelimOption);
end;
function TPathImpl.Equals(const Other: IPath; IgnoreCase: boolean): boolean;
var
SelfPath, OtherPath: UTF8String;
+ TmpPath: IPath;
begin
- SelfPath := Self.GetAbsolutePath().RemovePathDelim().ToUTF8();
- OtherPath := Other.GetAbsolutePath().RemovePathDelim().ToUTF8();
+ // TODO: remove fpc refcount bug workaround
+ TmpPath := Self.GetAbsolutePath();
+ SelfPath := TmpPath.RemovePathDelim().ToUTF8();
+ TmpPath := Other.GetAbsolutePath();
+ OtherPath := TmpPath.RemovePathDelim().ToUTF8();
if (FileSystem.IsCaseSensitive() and not IgnoreCase) then
Result := (CompareStr(SelfPath, OtherPath) = 0)
else
@@ -894,26 +987,220 @@ begin
{$ENDIF}
end;
-{ TTextFileStream }
+{ TTextStream }
+
+constructor TTextFileStream.Create(Filename: IPath; Mode: Word);
+begin
+ inherited Create();
+ fMode := Mode;
+ fFilename := Filename;
+ fLineBreak := sLineBreak;
+end;
function TTextFileStream.ReadLine(var Line: UTF8String): boolean;
begin
- // TODO
+ Line := ReadLine(Result);
end;
function TTextFileStream.ReadLine(var Line: AnsiString): boolean;
begin
- // TODO
+ Line := ReadLine(Result);
end;
-procedure TTextFileStream.WriteLine(Line: RawByteString);
+procedure TTextFileStream.WriteString(const Str: RawByteString);
+begin
+ WriteBuffer(Str[1], Length(Str));
+end;
+
+procedure TTextFileStream.WriteLine(const Line: RawByteString);
+begin
+ WriteBuffer(Line[1], Length(Line));
+ WriteBuffer(fLineBreak[1], Length(fLineBreak));
+end;
+
+{ TMemTextStream }
+
+constructor TMemTextFileStream.Create(Filename: IPath; Mode: Word);
+var
+ FileStream: TBinaryFileStream;
begin
- Self.WriteBuffer(Line[1], Length(Line));
+ inherited Create(Filename, Mode);
+
+ fStream := TMemoryStream.Create();
+
+ // load data to memory in read mode
+ if ((Mode and 3) in [fmOpenRead, fmOpenReadWrite]) then
+ begin
+ FileStream := TBinaryFileStream.Create(Filename, fmOpenRead);
+ try
+ fStream.LoadFromStream(FileStream);
+ finally
+ FileStream.Free;
+ end;
+ end
+ // check if file exists for write-mode
+ else if ((Mode and 3) = fmOpenWrite) and (not Filename.IsFile) then
+ begin
+ raise EFOpenError.CreateResFmt(@SFOpenError,
+ [FileName.GetAbsolutePath.ToNative]);
+ end;
end;
-procedure TTextFileStream.Write(Str: RawByteString);
+destructor TMemTextFileStream.Destroy();
+var
+ FileStream: TBinaryFileStream;
+ SaveMode: word;
begin
- Self.WriteBuffer(Str[1], Length(Str));
+ // save changes in write mode (= not read-only mode)
+ if ((fMode and 3) <> fmOpenRead) then
+ begin
+ if (fMode = fmCreate) then
+ SaveMode := fmCreate
+ else
+ SaveMode := fmOpenWrite;
+ FileStream := TBinaryFileStream.Create(fFilename, SaveMode);
+ try
+ fStream.SaveToStream(FileStream);
+ finally
+ FileStream.Free;
+ end;
+ end;
+
+ fStream.Free;
+ inherited;
+end;
+
+function TMemTextFileStream.GetSize: Int64;
+begin
+ Result := fStream.Size;
+end;
+
+function TMemTextFileStream.Read(var Buffer; Count: Longint): Longint;
+begin
+ Result := fStream.Read(Buffer, Count);
+end;
+
+function TMemTextFileStream.Write(const Buffer; Count: Longint): Longint;
+begin
+ Result := fStream.Write(Buffer, Count);
+end;
+
+function TMemTextFileStream.Seek(Offset: Longint; Origin: Word): Longint;
+begin
+ Result := fStream.Seek(Offset, Origin);
+end;
+
+function TMemTextFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
+begin
+ Result := fStream.Seek(Offset, Origin);
+end;
+
+function TMemTextFileStream.CopyMemString(StartPos: int64; EndPos: int64): RawByteString;
+var
+ LineLength: cardinal;
+ Temp: RawByteString;
+begin
+ LineLength := EndPos - StartPos;
+ if (LineLength > 0) then
+ begin
+ // set string length to line-length (+ zero-terminator)
+ SetLength(Temp, LineLength);
+ StrLCopy(PAnsiChar(Temp),
+ @PAnsiChar(fStream.Memory)[StartPos],
+ LineLength);
+ Result := Temp;
+ end
+ else
+ begin
+ Result := '';
+ end;
+end;
+
+function TMemTextFileStream.ReadString(): RawByteString;
+var
+ TextPtr: PAnsiChar;
+ CurPos, StartPos, FileSize: Int64;
+begin
+ TextPtr := PAnsiChar(fStream.Memory);
+ CurPos := Position;
+ FileSize := Size;
+ StartPos := -1;
+
+ while (CurPos < FileSize) do
+ begin
+ // check for whitespace (tab, lf, cr, space)
+ if (TextPtr[CurPos] in [#9, #10, #13, ' ']) then
+ begin
+ // check if we are at the end of a string
+ if (StartPos > -1) then
+ Break;
+ end
+ else if (StartPos = -1) then // start of string found
+ begin
+ StartPos := CurPos;
+ end;
+ Inc(CurPos);
+ end;
+
+ if (StartPos = -1) then
+ Result := ''
+ else
+ begin
+ Result := CopyMemString(StartPos, CurPos);
+ fStream.Position := CurPos;
+ end;
+end;
+
+{*
+ * Implementation of ReadLine(). We need separate versions for UTF8String
+ * and AnsiString as "var" parameter types have to fit exactly.
+ * To avoid a var-parameter here, the internal version the Line parameter is
+ * used as return value.
+ *}
+function TMemTextFileStream.ReadLine(var Success: boolean): RawByteString;
+var
+ TextPtr: PAnsiChar;
+ CurPos, FileSize: int64;
+begin
+ TextPtr := PAnsiChar(fStream.Memory);
+ CurPos := fStream.Position;
+ FileSize := Size;
+
+ // check for EOF
+ if (CurPos >= FileSize) then
+ begin
+ Result := '';
+ Success := false;
+ Exit;
+ end;
+
+ Success := true;
+
+ while (CurPos < FileSize) do
+ begin
+ if (TextPtr[CurPos] in [#10, #13]) then
+ begin
+ // copy text line
+ Result := CopyMemString(fStream.Position, CurPos);
+
+ // handle windows style #13#10 (\r\n) newlines
+ if (TextPtr[CurPos] = #13) and
+ (CurPos+1 < FileSize) and
+ (TextPtr[CurPos+1] = #10) then
+ begin
+ Inc(CurPos);
+ end;
+
+ // update stream pos
+ fStream.Position := CurPos+1;
+
+ Exit;
+ end;
+ Inc(CurPos);
+ end;
+
+ Result := CopyMemString(fStream.Position, CurPos);
+ fStream.Position := FileSize;
end;
{ TUnicodeMemoryStream }
diff --git a/unicode/src/base/UPlaylist.pas b/unicode/src/base/UPlaylist.pas
index 09c196ef..03ae2ffb 100644
--- a/unicode/src/base/UPlaylist.pas
+++ b/unicode/src/base/UPlaylist.pas
@@ -184,7 +184,7 @@ begin
//Load File
try
FilenameAbs := PlaylistPath.Append(Filename);
- TextStream := TTextFileStream.Create(FilenameAbs, fmOpenRead);
+ TextStream := TMemTextFileStream.Create(FilenameAbs, fmOpenRead);
except
begin
Log.LogError('Could not load Playlist: ' + FilenameAbs.ToNative);
@@ -260,7 +260,7 @@ begin
Exit;
// open file for rewriting
- TextStream := TTextFileStream.Create(PlaylistFile, fmCreate);
+ TextStream := TMemTextFileStream.Create(PlaylistFile, fmCreate);
try
// Write version (not nessecary but helpful)
TextStream.WriteLine('######################################');
diff --git a/unicode/src/base/USong.pas b/unicode/src/base/USong.pas
index 203ff517..cf9e7cdf 100644
--- a/unicode/src/base/USong.pas
+++ b/unicode/src/base/USong.pas
@@ -57,6 +57,7 @@ uses
{$ENDIF}
UCatCovers,
UXMLSong,
+ UUnicodeUtils,
UTextEncoding,
UFilesystem,
UPath;
@@ -79,12 +80,18 @@ type
private
FileLineNo : integer; // line, which is read last, for error reporting
- function EncodeFilename(Filename: string): string;
+ function DecodeFilename(Filename: RawByteString): IPath;
function Solmizate(Note: integer; Type_: integer): string;
procedure ParseNote(LineNumber: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: UTF8String);
procedure NewSentence(LineNumberP: integer; Param1, Param2: integer);
- function ReadTXTHeader(const aFileName: IPath): boolean;
+ function ParseLyricStringParam(const Line: RawByteString; var LinePos: integer): RawByteString;
+ function ParseLyricIntParam(const Line: RawByteString; var LinePos: integer): integer;
+ function ParseLyricFloatParam(const Line: RawByteString; var LinePos: integer): real;
+ function ParseLyricCharParam(const Line: RawByteString; var LinePos: integer): AnsiChar;
+ function ParseLyricText(const Line: RawByteString; var LinePos: integer): RawByteString;
+
+ function ReadTXTHeader(SongFile: TTextFileStream): boolean;
function ReadXMLHeader(const aFileName: IPath): boolean;
function GetFolderCategory(const aFileName: IPath): UTF8String;
@@ -133,8 +140,6 @@ type
OrderTyp: integer; // type of sorting for this button (0=name)
CatNumber: integer; // Count of Songs in Category for Cats and Number of Song in Category for Songs
- //SongFile: TextFile; // all procedures in this unit operate on this file
-
Base : array[0..1] of integer;
Rel : array[0..1] of integer;
Mult : integer;
@@ -253,33 +258,146 @@ begin
Result := PATH_NONE;
end;
-function TSong.EncodeFilename(Filename: string): string;
+function TSong.DecodeFilename(Filename: RawByteString): IPath;
begin
- {$IFDEF UTF8_FILENAMES}
- Result := RecodeStringUTF8(Filename, Encoding);
- {$ELSE}
- // FIXME: just for compatibility, should be UTF-8 in general
- if (Encoding = encUTF8) then
- Result := UTF8ToAnsi(Filename)
+ Result := UPath.Path(DecodeStringUTF8(Filename, Encoding));
+end;
+
+type
+ EUSDXParseException = class(Exception);
+
+{**
+ * Parses the Line string starting from LinePos for a parameter.
+ * Leading whitespace is trimmed, same applies to the first trailing whitespace.
+ * After the call LinePos will point to the position after the first trailing
+ * whitespace.
+ *
+ * Raises an EUSDXParseException if no string was found.
+ *
+ * Example:
+ * ParseLyricParam(Line:'Param0 Param1 Param2', LinePos:8, ...)
+ * -> Param:'Param1', LinePos:16 (= start of 'Param2')
+ *}
+function TSong.ParseLyricStringParam(const Line: RawByteString; var LinePos: integer): RawByteString;
+var
+ Start: integer;
+ OldLinePos: integer;
+const
+ Whitespace = [#9, ' '];
+begin
+ OldLinePos := LinePos;
+
+ Start := 0;
+ while (LinePos <= Length(Line)) do
+ begin
+ if (Line[LinePos] in Whitespace) then
+ begin
+ // check for end of param
+ if (Start > 0) then
+ Break;
+ end
+ // check for beginning of param
+ else if (Start = 0) then
+ begin
+ Start := LinePos;
+ end;
+ Inc(LinePos);
+ end;
+
+ // check if param was found
+ if (Start = 0) then
+ begin
+ LinePos := OldLinePos;
+ raise EUSDXParseException.Create('String expected');
+ end
else
- Result := Filename;
- {$ENDIF}
+ begin
+ // copy param without trailing whitespace
+ Result := Copy(Line, Start, LinePos-Start);
+ // skip first trailing whitespace (if not at EOL)
+ if (LinePos <= Length(Line)) then
+ Inc(LinePos);
+ end;
+end;
+
+function TSong.ParseLyricIntParam(const Line: RawByteString; var LinePos: integer): integer;
+var
+ Str: RawByteString;
+ OldLinePos: integer;
+begin
+ OldLinePos := LinePos;
+ Str := ParseLyricStringParam(Line, LinePos);
+ try
+ Result := StrToInt(Str);
+ except // on EConvertError
+ LinePos := OldLinePos;
+ raise EUSDXParseException.Create('Integer expected');
+ end;
+end;
+
+function TSong.ParseLyricFloatParam(const Line: RawByteString; var LinePos: integer): real;
+var
+
+ Str: RawByteString;
+ OldLinePos: integer;
+begin
+ OldLinePos := LinePos;
+ Str := ParseLyricStringParam(Line, LinePos);
+ try
+ Result := StrToFloat(Str);
+ except // on EConvertError
+ LinePos := OldLinePos;
+ raise EUSDXParseException.Create('Float expected');
+ end;
+end;
+
+function TSong.ParseLyricCharParam(const Line: RawByteString; var LinePos: integer): AnsiChar;
+var
+ Str: RawByteString;
+ OldLinePos: integer;
+begin
+ OldLinePos := LinePos;
+ Str := ParseLyricStringParam(Line, LinePos);
+ if (Length(Str) <> 1) then
+ begin
+ LinePos := OldLinePos;
+ raise EUSDXParseException.Create('Character expected');
+ end;
+ Result := Str[1];
+end;
+
+{**
+ * Returns the rest of the line from LinePos as lyric text.
+ * Leading and trailing whitespace is not trimmed.
+ *}
+function TSong.ParseLyricText(const Line: RawByteString; var LinePos: integer): RawByteString;
+begin
+ if (LinePos > Length(Line)) then
+ Result := ''
+ else
+ begin
+ Result := Copy(Line, LinePos, Length(Line)-LinePos+1);
+ LinePos := Length(Line)+1;
+ end;
end;
//Load TXT Song
function TSong.LoadSong(): boolean;
var
- TempC: AnsiChar;
- Text: UTF8String;
+ CurLine: RawByteString;
+ LinePos: integer;
Count: integer;
Both: boolean;
- Param1: integer;
- Param2: integer;
- Param3: integer;
- ParamS: UTF8String;
+
+ Param0: AnsiChar;
+ Param1: integer;
+ Param2: integer;
+ Param3: integer;
+ ParamLyric: UTF8String;
+
I: integer;
NotesFound: boolean;
- TextStream: TTextFileStream;
+ SongFile: TTextFileStream;
FileNamePath: IPath;
begin
Result := false;
@@ -301,169 +419,159 @@ begin
if Length(Player) = 2 then
Both := true;
- {$MESSAGE warn 'TODO: TSong.LoadSong'}
-
- (*
try
// Open song file for reading.....
- TextStream := nil;
- TextStream := TBinaryFileStream.Create(FullFileName, fmOpenRead);
-
- //Clear old Song Header
- if (Self.Path.IsUnset) then
- Self.Path := FullFileName.GetPath();
-
- if (Self.FileName.IsUnset) then
- Self.Filename := FullFileName.GetName();
-
- //Search for Note Begining
- FileLineNo := 0;
- NotesFound := false;
- while (TextStream.ReadLine(Text)) do
- begin
- Inc(FileLineNo);
- if (Length(Text) > 0) and (Text[0] in [':', 'F', '*']) then
+ SongFile := TMemTextFileStream.Create(FileNamePath, fmOpenRead);
+ try
+ //Search for Note Beginning
+ FileLineNo := 0;
+ NotesFound := false;
+ while (SongFile.ReadLine(CurLine)) do
begin
- TempC := Text[0];
- NotesFound := true;
- Break;
+ Inc(FileLineNo);
+ if (Length(CurLine) > 0) and (CurLine[1] in [':', 'F', '*']) then
+ begin
+ NotesFound := true;
+ Break;
+ end;
end;
- end;
-
- if (not NotesFound) then
- begin //Song File Corrupted - No Notes
- TextStream.Free;
- Log.LogError('Could not load txt File, no Notes found: ' + FileName);
- LastError := 'ERROR_CORRUPT_SONG_NO_NOTES';
- Exit;
- end;
- SetLength(Lines, 2);
- for Count := 0 to High(Lines) do
- begin
- Lines[Count].High := 0;
- Lines[Count].Number := 1;
- Lines[Count].Current := 0;
- Lines[Count].Resolution := self.Resolution;
- Lines[Count].NotesGAP := self.NotesGAP;
- Lines[Count].ScoreValue := 0;
-
- //Add first line and set some standard values to fields
- //see procedure NewSentence for further explantation
- //concerning most of these values
- SetLength(Lines[Count].Line, 1);
- Lines[Count].Line[0].HighNote := -1;
- Lines[Count].Line[0].LastLine := false;
- Lines[Count].Line[0].BaseNote := High(Integer);
- Lines[Count].Line[0].TotalNotes := 0;
- end;
-
- while (TempC <> 'E') and (not EOF(SongFile)) do
- begin
+ if (not NotesFound) then
+ begin //Song File Corrupted - No Notes
+ Log.LogError('Could not load txt File, no Notes found: ' + FileNamePath.ToNative);
+ LastError := 'ERROR_CORRUPT_SONG_NO_NOTES';
+ Exit;
+ end;
- if (TempC in [':', '*', 'F']) then
+ SetLength(Lines, 2);
+ for Count := 0 to High(Lines) do
begin
- // read notes
- Read(SongFile, Param1);
- Read(SongFile, Param2);
- Read(SongFile, Param3);
- Read(SongFile, ParamS);
-
- //Check for ZeroNote
- if Param2 = 0 then
- Log.LogError('Found ZeroNote at "'+TempC+' '+IntToStr(Param1)+' '+IntToStr(Param2)+' '+IntToStr(Param3)+ParamS+'" -> Note ignored!')
- else
- begin
- // add notes
- if not Both then
- // P1
- ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS)
- else
- begin
- // P1 + P2
- ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS);
- ParseNote(1, TempC, (Param1+Rel[1]) * Mult, Param2 * Mult, Param3, ParamS);
- end;
- end; //Zeronote check
- end // if
-
- else if TempC = '-' then
- begin
- // reads sentence
- Read(SongFile, Param1);
- if self.Relative then
- Read(SongFile, Param2); // read one more data for relative system
-
- // new sentence
- if not Both then
- // P1
- NewSentence(0, (Param1 + Rel[0]) * Mult, Param2)
- else
- begin
- // P1 + P2
- NewSentence(0, (Param1 + Rel[0]) * Mult, Param2);
- NewSentence(1, (Param1 + Rel[1]) * Mult, Param2);
- end;
- end // if
+ Lines[Count].High := 0;
+ Lines[Count].Number := 1;
+ Lines[Count].Current := 0;
+ Lines[Count].Resolution := self.Resolution;
+ Lines[Count].NotesGAP := self.NotesGAP;
+ Lines[Count].ScoreValue := 0;
+
+ //Add first line and set some standard values to fields
+ //see procedure NewSentence for further explantation
+ //concerning most of these values
+ SetLength(Lines[Count].Line, 1);
+ Lines[Count].Line[0].HighNote := -1;
+ Lines[Count].Line[0].LastLine := false;
+ Lines[Count].Line[0].BaseNote := High(Integer);
+ Lines[Count].Line[0].TotalNotes := 0;
+ end;
- else if TempC = 'B' then
+ while true do
begin
- SetLength(self.BPM, Length(self.BPM) + 1);
- Read(SongFile, self.BPM[High(self.BPM)].StartBeat);
- self.BPM[High(self.BPM)].StartBeat := self.BPM[High(self.BPM)].StartBeat + Rel[0];
+ LinePos := 0;
- Read(SongFile, Text);
- self.BPM[High(self.BPM)].BPM := StrToFloat(Text);
- self.BPM[High(self.BPM)].BPM := self.BPM[High(self.BPM)].BPM * Mult * MultBPM;
- end;
-
- ReadLn(SongFile); //Jump to next line in File, otherwise the next Read would catch the linebreak(e.g. #13 #10 on win32)
+ Param0 := ParseLyricCharParam(CurLine, LinePos);
+ if (Param0 = 'E') then
+ begin
+ Break
+ end
+ else if (Param0 in [':', '*', 'F']) then
+ begin
+ // read notes
+ Param1 := ParseLyricIntParam(CurLine, LinePos);
+ Param2 := ParseLyricIntParam(CurLine, LinePos);
+ Param3 := ParseLyricIntParam(CurLine, LinePos);
+ ParamLyric := ParseLyricText(CurLine, LinePos);
+
+ //Check for ZeroNote
+ if Param2 = 0 then
+ Log.LogError('Found ZeroNote at "'+Param0+' '+IntToStr(Param1)+' '+IntToStr(Param2)+' '+IntToStr(Param3)+ParamLyric+'" -> Note ignored!')
+ else
+ begin
+ // add notes
+ if not Both then
+ // P1
+ ParseNote(0, Param0, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamLyric)
+ else
+ begin
+ // P1 + P2
+ ParseNote(0, Param0, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamLyric);
+ ParseNote(1, Param0, (Param1+Rel[1]) * Mult, Param2 * Mult, Param3, ParamLyric);
+ end;
+ end; //Zeronote check
+ end // if
+
+ else if Param0 = '-' then
+ begin
+ // reads sentence
+ Param1 := ParseLyricIntParam(CurLine, LinePos);
+ if self.Relative then
+ Param2 := ParseLyricIntParam(CurLine, LinePos); // read one more data for relative system
+
+ // new sentence
+ if not Both then
+ // P1
+ NewSentence(0, (Param1 + Rel[0]) * Mult, Param2)
+ else
+ begin
+ // P1 + P2
+ NewSentence(0, (Param1 + Rel[0]) * Mult, Param2);
+ NewSentence(1, (Param1 + Rel[1]) * Mult, Param2);
+ end;
+ end // if
+
+ else if Param0 = 'B' then
+ begin
+ SetLength(self.BPM, Length(self.BPM) + 1);
+ self.BPM[High(self.BPM)].StartBeat := ParseLyricFloatParam(CurLine, LinePos);
+ self.BPM[High(self.BPM)].StartBeat := self.BPM[High(self.BPM)].StartBeat + Rel[0];
- Read(SongFile, TempC);
- Inc(FileLineNo);
- end; // while
+ self.BPM[High(self.BPM)].BPM := ParseLyricFloatParam(CurLine, LinePos);
+ self.BPM[High(self.BPM)].BPM := self.BPM[High(self.BPM)].BPM * Mult * MultBPM;
+ end;
- CloseFile(SongFile);
+ // Read next line in File
+ if (not SongFile.ReadLine(CurLine)) then
+ Break;
- for I := 0 to High(Lines) do
+ Inc(FileLineNo);
+ end; // while
+ finally
+ SongFile.Free;
+ end;
+ except
+ on E: Exception do
begin
- if ((Both) or (I = 0)) then
- begin
- if (Length(Lines[I].Line) < 2) then
- begin
- LastError := 'ERROR_CORRUPT_SONG_NO_BREAKS';
- Log.LogError('Error Loading File, Can''t find any Linebreaks: "' + FullFileName + '"');
- exit;
- end;
-
- if (Lines[I].Line[Lines[I].High].HighNote < 0) then
- begin
- SetLength(Lines[I].Line, Lines[I].Number - 1);
- Lines[I].High := Lines[I].High - 1;
- Lines[I].Number := Lines[I].Number - 1;
- Log.LogError('Error loading Song, sentence w/o note found in last line before E: ' + Filename);
- end;
- end;
+ Log.LogError(Format('Error Loading File: "%s" in Line %d,%d: %s',
+ [FileNamePath.ToNative, FileLineNo, LinePos, E.Message]));
+ Exit;
end;
+ end;
- for Count := 0 to High(Lines) do
+ for I := 0 to High(Lines) do
+ begin
+ if ((Both) or (I = 0)) then
begin
- if (High(Lines[Count].Line) >= 0) then
- Lines[Count].Line[High(Lines[Count].Line)].LastLine := true;
- end;
- except
- try
- CloseFile(SongFile);
- except
+ if (Length(Lines[I].Line) < 2) then
+ begin
+ LastError := 'ERROR_CORRUPT_SONG_NO_BREAKS';
+ Log.LogError('Error Loading File, Can''t find any Linebreaks: "' + FileNamePath.ToNative + '"');
+ exit;
+ end;
+ if (Lines[I].Line[Lines[I].High].HighNote < 0) then
+ begin
+ SetLength(Lines[I].Line, Lines[I].Number - 1);
+ Lines[I].High := Lines[I].High - 1;
+ Lines[I].Number := Lines[I].Number - 1;
+ Log.LogError('Error loading Song, sentence w/o note found in last line before E: ' + FileNamePath.ToNative);
+ end;
end;
+ end;
- LastError := 'ERROR_CORRUPT_SONG_ERROR_IN_LINE';
- Log.LogError('Error Loading File: "' + FullFileName + '" in Line ' + inttostr(FileLineNo));
- exit;
+ for Count := 0 to High(Lines) do
+ begin
+ if (High(Lines[Count].Line) >= 0) then
+ Lines[Count].Line[High(Lines[Count].Line)].LastLine := true;
end;
- *)
-
+
Result := true;
end;
@@ -724,27 +832,25 @@ begin
Result := StrToFloatDef(TempValue, 0);
end;
-function TSong.ReadTXTHeader(const aFileName : IPath): boolean;
+function TSong.ReadTXTHeader(SongFile: TTextFileStream): boolean;
var
Line, Identifier: string;
Value: string;
SepPos: integer; // separator position
Done: byte; // bit-vector of mandatory fields
- EncFile: string; // encoded filename
+ EncFile: IPath; // encoded filename
+ FullFileName: string;
begin
Result := true;
Done := 0;
- {$message warn 'TSong.ReadTXTHeader'}
-
- (*
+ FullFileName := Path.Append(Filename).ToNative;
//Read first Line
- ReadLn (SongFile, Line);
-
+ SongFile.ReadLine(Line);
if (Length(Line) <= 0) then
begin
- Log.LogError('File Starts with Empty Line: ' + Path + PathDelim + aFileName,
+ Log.LogError('File Starts with Empty Line: ' + FullFileName,
'TSong.ReadTXTHeader');
Result := false;
Exit;
@@ -772,7 +878,7 @@ begin
//Check the Identifier (If Value is given)
if (Length(Value) = 0) then
begin
- Log.LogError('Empty field "'+Identifier+'" in file ' + Path + PathDelim + aFileName,
+ Log.LogError('Empty field "'+Identifier+'" in file ' + FullFileName,
'TSong.ReadTXTHeader');
end
else
@@ -799,8 +905,8 @@ begin
//MP3 File
else if (Identifier = 'MP3') then
begin
- EncFile := EncodeFilename(Value);
- if (FileExists(self.Path + EncFile)) then
+ EncFile := DecodeFilename(Value);
+ if (Self.Path.Append(EncFile).IsFile) then
begin
self.Mp3 := EncFile;
@@ -837,23 +943,23 @@ begin
//Cover Picture
else if (Identifier = 'COVER') then
begin
- self.Cover := EncodeFilename(Value);
+ self.Cover := DecodeFilename(Value);
end
//Background Picture
else if (Identifier = 'BACKGROUND') then
begin
- self.Background := EncodeFilename(Value);
+ self.Background := DecodeFilename(Value);
end
// Video File
else if (Identifier = 'VIDEO') then
begin
- EncFile := EncodeFilename(Value);
- if (FileExists(self.Path + EncFile)) then
+ EncFile := DecodeFilename(Value);
+ if (self.Path.Append(EncFile).IsFile) then
self.Video := EncFile
else
- Log.LogError('Can''t find Video File in Song: ' + aFileName);
+ Log.LogError('Can''t find Video File in Song: ' + FullFileName);
end
// Video Gap
@@ -925,37 +1031,33 @@ begin
end; // End check for non-empty Value
- // check for end of file
- if Eof(SongFile) then
+ // read next line
+ if (not SongFile.ReadLine(Line)) then
begin
Result := false;
- Log.LogError('File Incomplete or not Ultrastar TxT (A): ' + aFileName);
+ Log.LogError('File Incomplete or not Ultrastar TxT (A): ' + FullFileName);
Break;
end;
-
- // read next line
- ReadLn(SongFile, Line)
end; // while
- if self.Cover = '' then
- self.Cover := platform.FindSongFile(Path, '*[CO].jpg');
+ if self.Cover.IsUnset then
+ self.Cover := FindSongFile(Path, '*[CO].jpg');
//Check if all Required Values are given
if (Done <> 15) then
begin
Result := false;
if (Done and 8) = 0 then //No BPM Flag
- Log.LogError('BPM Tag Missing: ' + self.FileName)
+ Log.LogError('BPM Tag Missing: ' + FullFileName)
else if (Done and 4) = 0 then //No MP3 Flag
- Log.LogError('MP3 Tag/File Missing: ' + self.FileName)
+ Log.LogError('MP3 Tag/File Missing: ' + FullFileName)
else if (Done and 2) = 0 then //No Artist Flag
- Log.LogError('Artist Tag Missing: ' + self.FileName)
+ Log.LogError('Artist Tag Missing: ' + FullFileName)
else if (Done and 1) = 0 then //No Title Flag
- Log.LogError('Title Tag Missing: ' + self.FileName)
+ Log.LogError('Title Tag Missing: ' + FullFileName)
else //unknown Error
- Log.LogError('File Incomplete or not Ultrastar TxT (B - '+ inttostr(Done) +'): ' + aFileName);
+ Log.LogError('File Incomplete or not Ultrastar TxT (B - '+ inttostr(Done) +'): ' + FullFileName);
end;
- *)
end;
function TSong.GetErrorLineNo: integer;
@@ -1049,13 +1151,6 @@ begin
if Note[HighNote].Tone < BaseNote then
BaseNote := Note[HighNote].Tone;
- //delete the space that seperates the notes pitch from its lyrics
- //it is left in the LyricS string because Read("some ordinal type") will
- //set the files pointer to the first whitespace character after the
- //ordinal string. Trim is no solution because it would cut the spaces
- //that seperate the words of the lyrics, too.
- Delete(LyricS, 1, 1);
-
DecodeStringUTF8(LyricS, Note[HighNote].Text, Encoding);
Lyric := Lyric + Note[HighNote].Text;
@@ -1148,15 +1243,13 @@ begin
FileLineNo := 0;
//Open File and set File Pointer to the beginning
- SongFile := TTextFileStream.Create(Self.Path.Append(Self.FileName), fmOpenRead);
+ SongFile := TMemTextFileStream.Create(Self.Path.Append(Self.FileName), fmOpenRead);
try
//Clear old Song Header
Self.clear;
//Read Header
- Result := self.ReadTxTHeader(FileName)
-
- //And Close File
+ Result := Self.ReadTxTHeader(SongFile)
finally
SongFile.Free;
end;
diff --git a/unicode/src/base/UXMLSong.pas b/unicode/src/base/UXMLSong.pas
index e8962539..e9751eba 100644
--- a/unicode/src/base/UXMLSong.pas
+++ b/unicode/src/base/UXMLSong.pas
@@ -134,18 +134,14 @@ var
I: Integer;
FileStream: TBinaryFileStream;
begin
- {$message warn 'TODO: TParser.ParseSong'}
-
-(*
Result := False;
if Filename.IsFile() then
begin
- SSFile := TStringList.Create;
-
ErrorMessage := 'Can''t open melody.xml file';
+ SSFile := TStringList.Create;
+ FileStream := TBinaryFileStream.Create(Filename, fmOpenRead);
try
- FileStream := TBinaryFileStream.Create(Filename);
SSFile.LoadFromStream(FileStream);
ErrorMessage := '';
@@ -176,25 +172,24 @@ begin
finally
SSFile.Free;
+ FileStream.Free;
end;
end;
- *)
end;
function TParser.ParseSongHeader (const Filename: IPath): Boolean;
-var I: Integer;
+var
+ I: Integer;
+ Stream: TBinaryFileStream;
begin
Result := False;
- {$message warn 'TODO: TParser.ParseSong'}
- (*
- if FileExists(Filename) then
+ if Filename.IsFile() then
begin
SSFile := TStringList.Create;
- SSFile.Clear;
-
+ Stream := TBinaryFileStream.Create(Filename, fmOpenRead);
try
- SSFile.LoadFromFile(Filename);
+ SSFile.LoadFromStream(Stream);
If (SSFile.Count > 0) then
begin
@@ -225,11 +220,11 @@ begin
finally
SSFile.Free;
+ Stream.Free;
end;
end
else
ErrorMessage := 'Can''t find melody.xml file';
- *)
end;
Function TParser.ParseLine(Line: String): Boolean;
@@ -591,19 +586,17 @@ end;
Function TParser.ParseConfigForEdition(const Filename: IPath): String;
var
txt: TStringlist;
+ Stream: TBinaryFileStream;
I: Integer;
J, K: Integer;
S: String;
begin
Result := '';
- txt := TStringlist.Create;
- {$message warn 'TODO: TParser.ParseConfigForEdition'}
-
- (*
+ Stream := TBinaryFileStream.Create(Filename, fmOpenRead);
try
- txt.LoadFromFile(Filename);
-
+ txt := TStringlist.Create;
+ txt.LoadFromStream(Stream);
For I := 0 to txt.Count-1 do
begin
S := Trim(txt.Strings[I]);
@@ -623,8 +616,8 @@ begin
Edition := Result;
finally
txt.Free;
+ Stream.Free;
end;
- *)
end;
end.
diff --git a/unicode/src/media/UMediaCore_FFmpeg.pas b/unicode/src/media/UMediaCore_FFmpeg.pas
index 9ad19a5b..5f56b467 100644
--- a/unicode/src/media/UMediaCore_FFmpeg.pas
+++ b/unicode/src/media/UMediaCore_FFmpeg.pas
@@ -34,12 +34,16 @@ interface
{$I switches.inc}
uses
- UMusic,
+ Classes,
+ ctypes,
+ sdl,
avcodec,
avformat,
avutil,
+ avio,
+ UMusic,
ULog,
- sdl;
+ UPath;
type
PPacketQueue = ^TPacketQueue;
@@ -220,6 +224,109 @@ begin
Result := true;
end;
+function FFmpegStreamRead(Opaque: Pointer; Buffer: PByteArray; BufSize: cint): cint; cdecl;
+var
+ Stream: TStream;
+begin
+ Stream := TStream(Opaque);
+ if (Stream = nil) then
+ raise EInvalidContainer.Create('FFmpegStreamRead on nil');
+ try
+ Result := Stream.Read(Buffer[0], BufSize);
+ except
+ Result := -1;
+ end;
+end;
+
+function FFmpegStreamWrite(Opaque: Pointer; Buffer: PByteArray; BufSize: cint): cint; cdecl;
+var
+ Stream: TStream;
+begin
+ Stream := TStream(Opaque);
+ if (Stream = nil) then
+ raise EInvalidContainer.Create('FFmpegStreamWrite on nil');
+ try
+ Result := Stream.Write(Buffer[0], BufSize);
+ except
+ Result := -1;
+ end;
+end;
+
+function FFmpegStreamSeek(Opaque: Pointer; Offset: cint64; Whence: cint): cint64; cdecl;
+var
+ Stream : TStream;
+ Origin : Word;
+begin
+ Stream := TStream(opaque);
+ if (Stream = nil) then
+ raise EInvalidContainer.Create('FFmpegStreamSeek on nil');
+ case whence of
+ 0 : Origin := soFromBeginning;
+ 1 : Origin := soFromCurrent;
+ 2 : Origin := soFromEnd;
+ else
+ Origin := soFromBeginning;
+ end;
+ Result := Stream.Seek(offset,origin);
+end;
+
+const
+ AVFileBufSize = 32768;
+
+type
+ PAVFile = ^TAVFile;
+ TAVFile = record
+ ByteIOCtx: TByteIOContext;
+ Buffer: array [0 .. AVFileBufSize-1] of byte;
+ end;
+
+function AVOpenInputFile(var ic_ptr: PAVFormatContext; filename: IPath;
+ fmt: PAVInputFormat; buf_size: cint;
+ ap: PAVFormatParameters): PAVFile;
+var
+ ProbeData: TAVProbeData;
+ FilenameStr: UTF8String;
+ FileStream: TBinaryFileStream;
+ AVFile: PAVFile;
+begin
+ Result := nil;
+ New(AVFile);
+
+ // copy UTF-8 string so ProbeData.filename will be valid at av_probe_input_format()
+ FilenameStr := Filename.ToUTF8;
+
+ if (fmt = nil) then
+ begin
+ ProbeData.filename := PChar(FilenameStr);
+ ProbeData.buf := @AVFile.Buffer;
+ ProbeData.buf_size := AVFileBufSize;
+
+ fmt := av_probe_input_format(@ProbeData, 1);
+ if (fmt = nil) then
+ begin
+ Dispose(AVFile);
+ Exit;
+ end;
+ end;
+
+ FileStream := TBinaryFileStream.Create(Filename, fmOpenReadWrite);
+ if (init_put_byte(@AVFile.ByteIOCtx, @AVFile.Buffer, AVFileBufSize, 0, FileStream,
+ FFmpegStreamRead, FFmpegStreamWrite, FFmpegStreamSeek) < 0) then
+ begin
+ Dispose(AVFile);
+ Exit;
+ end;
+
+ if (av_open_input_stream(ic_ptr, @AVFile.ByteIOCtx, PChar(FilenameStr), fmt, ap) < 0) then
+ begin
+ Dispose(AVFile);
+ Exit;
+ end;
+
+ Result := AVFile;
+end;
+
+
{ TPacketQueue }
constructor TPacketQueue.Create();