diff options
Diffstat (limited to '')
-rw-r--r-- | src/base/UCovers.pas | 459 |
1 files changed, 0 insertions, 459 deletions
diff --git a/src/base/UCovers.pas b/src/base/UCovers.pas deleted file mode 100644 index 6c7c9e48..00000000 --- a/src/base/UCovers.pas +++ /dev/null @@ -1,459 +0,0 @@ -{* UltraStar Deluxe - Karaoke Game - * - * UltraStar Deluxe is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * 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. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - *} - -unit UCovers; - -{ - TODO: - - adjust database to new song-loading (e.g. use SongIDs) - - support for deletion of outdated covers - - support for update of changed covers - - use paths relative to the song for removable disks support - (a drive might have a different drive-name the next time it is connected, - so "H:/songs/..." will not match "I:/songs/...") -} - -interface - -{$IFDEF FPC} - {$MODE Delphi} -{$ENDIF} - -{$I switches.inc} - -uses - SDL, - SQLite3, - SQLiteTable3, - SysUtils, - Classes, - UImage, - UTexture, - UPath; - -type - ECoverDBException = class(Exception) - end; - - TCover = class - private - ID: int64; - Filename: IPath; - public - constructor Create(ID: int64; Filename: IPath); - function GetPreviewTexture(): TTexture; - function GetTexture(): TTexture; - end; - - TThumbnailInfo = record - CoverWidth: integer; // Original width of cover - CoverHeight: integer; // Original height of cover - PixelFormat: TImagePixelFmt; // Pixel-format of thumbnail - end; - - TCoverDatabase = class - private - DB: TSQLiteDatabase; - procedure InitCoverDatabase(); - function CreateThumbnail(const Filename: IPath; var Info: TThumbnailInfo): PSDL_Surface; - function LoadCover(CoverID: int64): TTexture; - procedure DeleteCover(CoverID: int64); - function FindCoverIntern(const Filename: IPath): int64; - procedure Open(); - function GetVersion(): integer; - procedure SetVersion(Version: integer); - public - constructor Create(); - destructor Destroy; override; - function AddCover(const Filename: IPath): TCover; - function FindCover(const Filename: IPath): TCover; - function CoverExists(const Filename: IPath): boolean; - function GetMaxCoverSize(): integer; - procedure SetMaxCoverSize(Size: integer); - end; - - TBlobWrapper = class(TCustomMemoryStream) - function Write(const Buffer; Count: Integer): Integer; override; - end; - -var - Covers: TCoverDatabase; - -implementation - -uses - UMain, - ULog, - UPlatform, - UIni, - Math, - DateUtils; - -const - COVERDB_FILENAME: UTF8String = 'cover.db'; - COVERDB_VERSION = 01; // 0.1 - COVER_TBL = 'Cover'; - COVER_THUMBNAIL_TBL = 'CoverThumbnail'; - COVER_IDX = 'Cover_Filename_IDX'; - -// Note: DateUtils.DateTimeToUnix() will throw an exception in FPC -function DateTimeToUnixTime(time: TDateTime): int64; -begin - Result := Round((time - UnixDateDelta) * SecsPerDay); -end; - -// Note: DateUtils.UnixToDateTime() will throw an exception in FPC -function UnixTimeToDateTime(timestamp: int64): TDateTime; -begin - Result := timestamp / SecsPerDay + UnixDateDelta; -end; - - -{ TBlobWrapper } - -function TBlobWrapper.Write(const Buffer; Count: Integer): Integer; -begin - SetPointer(Pointer(Buffer), Count); - Result := Count; -end; - - -{ TCover } - -constructor TCover.Create(ID: int64; Filename: IPath); -begin - Self.ID := ID; - Self.Filename := Filename; -end; - -function TCover.GetPreviewTexture(): TTexture; -begin - Result := Covers.LoadCover(ID); -end; - -function TCover.GetTexture(): TTexture; -begin - Result := Texture.LoadTexture(Filename); -end; - - -{ TCoverDatabase } - -constructor TCoverDatabase.Create(); -begin - inherited; - - Open(); - InitCoverDatabase(); -end; - -destructor TCoverDatabase.Destroy; -begin - DB.Free; - inherited; -end; - -function TCoverDatabase.GetVersion(): integer; -begin - Result := DB.GetTableValue('PRAGMA user_version'); -end; - -procedure TCoverDatabase.SetVersion(Version: integer); -begin - DB.ExecSQL(Format('PRAGMA user_version = %d', [Version])); -end; - -function TCoverDatabase.GetMaxCoverSize(): integer; -begin - Result := ITextureSizeVals[Ini.TextureSize]; -end; - -procedure TCoverDatabase.SetMaxCoverSize(Size: integer); -var - I: integer; -begin - // search for first valid cover-size > Size - for I := 0 to Length(ITextureSizeVals)-1 do - begin - if (Size <= ITextureSizeVals[I]) then - begin - Ini.TextureSize := I; - Exit; - end; - end; - - // fall-back to highest size - Ini.TextureSize := High(ITextureSizeVals); -end; - -procedure TCoverDatabase.Open(); -var - Version: integer; - Filename: IPath; -begin - Filename := Platform.GetGameUserPath().Append(COVERDB_FILENAME); - - DB := TSQLiteDatabase.Create(Filename.ToUTF8()); - Version := GetVersion(); - - // check version, if version is too old/new, delete database file - if ((Version <> 0) and (Version <> COVERDB_VERSION)) then - begin - Log.LogInfo('Outdated cover-database file found', 'TCoverDatabase.Open'); - // close and delete outdated file - DB.Free; - if (not Filename.DeleteFile()) then - raise ECoverDBException.Create('Could not delete ' + Filename.ToNative); - // reopen - DB := TSQLiteDatabase.Create(Filename.ToUTF8()); - Version := 0; - end; - - // set version number after creation - if (Version = 0) then - SetVersion(COVERDB_VERSION); - - // speed-up disk-writing. The default FULL-synchronous mode is too slow. - // With this option disk-writing is approx. 4 times faster but the database - // might be corrupted if the OS crashes, although this is very unlikely. - DB.ExecSQL('PRAGMA synchronous = OFF;'); - - // the next line rather gives a slow-down instead of a speed-up, so we do not use it - //DB.ExecSQL('PRAGMA temp_store = MEMORY;'); -end; - -procedure TCoverDatabase.InitCoverDatabase(); -begin - DB.ExecSQL('CREATE TABLE IF NOT EXISTS ['+COVER_TBL+'] (' + - '[ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' + - '[Filename] TEXT UNIQUE NOT NULL, ' + - '[Date] INTEGER NOT NULL, ' + - '[Width] INTEGER NOT NULL, ' + - '[Height] INTEGER NOT NULL ' + - ')'); - - DB.ExecSQL('CREATE INDEX IF NOT EXISTS ['+COVER_IDX+'] ON ['+COVER_TBL+'](' + - '[Filename] ASC' + - ')'); - - DB.ExecSQL('CREATE TABLE IF NOT EXISTS ['+COVER_THUMBNAIL_TBL+'] (' + - '[ID] INTEGER NOT NULL PRIMARY KEY, ' + - '[Format] INTEGER NOT NULL, ' + - '[Width] INTEGER NOT NULL, ' + - '[Height] INTEGER NOT NULL, ' + - '[Data] BLOB NULL' + - ')'); -end; - -function TCoverDatabase.FindCoverIntern(const Filename: IPath): int64; -begin - Result := DB.GetTableValue('SELECT [ID] FROM ['+COVER_TBL+'] ' + - 'WHERE [Filename] = ?', - [Filename.ToUTF8]); -end; - -function TCoverDatabase.FindCover(const Filename: IPath): TCover; -var - CoverID: int64; -begin - Result := nil; - try - CoverID := FindCoverIntern(Filename); - if (CoverID > 0) then - Result := TCover.Create(CoverID, Filename); - except on E: Exception do - Log.LogError(E.Message, 'TCoverDatabase.FindCover'); - end; -end; - -function TCoverDatabase.CoverExists(const Filename: IPath): boolean; -begin - Result := false; - try - Result := (FindCoverIntern(Filename) > 0); - except on E: Exception do - Log.LogError(E.Message, 'TCoverDatabase.CoverExists'); - end; -end; - -function TCoverDatabase.AddCover(const Filename: IPath): TCover; -var - CoverID: int64; - Thumbnail: PSDL_Surface; - CoverData: TBlobWrapper; - FileDate: TDateTime; - Info: TThumbnailInfo; -begin - Result := nil; - - //if (not FileExists(Filename)) then - // Exit; - - // TODO: replace '\' with '/' in filename - FileDate := Now(); //FileDateToDateTime(FileAge(Filename)); - - Thumbnail := CreateThumbnail(Filename, Info); - if (Thumbnail = nil) then - Exit; - - CoverData := TBlobWrapper.Create; - CoverData.Write(Thumbnail^.pixels, Thumbnail^.h * Thumbnail^.pitch); - - try - // Note: use a transaction to speed-up file-writing. - // Without data written by the first INSERT might be moved at the second INSERT. - DB.BeginTransaction(); - - // add general cover info - DB.ExecSQL('INSERT INTO ['+COVER_TBL+'] ' + - '([Filename], [Date], [Width], [Height]) VALUES' + - '(?, ?, ?, ?)', - [Filename.ToUTF8, DateTimeToUnixTime(FileDate), - Info.CoverWidth, Info.CoverHeight]); - - // get auto-generated cover ID - CoverID := DB.GetLastInsertRowID(); - - // add thumbnail info - DB.ExecSQL('INSERT INTO ['+COVER_THUMBNAIL_TBL+'] ' + - '([ID], [Format], [Width], [Height], [Data]) VALUES' + - '(?, ?, ?, ?, ?)', - [CoverID, Ord(Info.PixelFormat), - Thumbnail^.w, Thumbnail^.h, CoverData]); - - Result := TCover.Create(CoverID, Filename); - except on E: Exception do - Log.LogError(E.Message, 'TCoverDatabase.AddCover'); - end; - - DB.Commit(); - CoverData.Free; - SDL_FreeSurface(Thumbnail); -end; - -function TCoverDatabase.LoadCover(CoverID: int64): TTexture; -var - Width, Height: integer; - PixelFmt: TImagePixelFmt; - Data: PChar; - DataSize: integer; - Filename: IPath; - Table: TSQLiteUniTable; -begin - Table := nil; - - try - Table := DB.GetUniTable(Format( - 'SELECT C.[Filename], T.[Format], T.[Width], T.[Height], T.[Data] ' + - 'FROM ['+COVER_TBL+'] C ' + - 'INNER JOIN ['+COVER_THUMBNAIL_TBL+'] T ' + - 'USING(ID) ' + - 'WHERE [ID] = %d', [CoverID])); - - Filename := Path(Table.FieldAsString(0)); - PixelFmt := TImagePixelFmt(Table.FieldAsInteger(1)); - Width := Table.FieldAsInteger(2); - Height := Table.FieldAsInteger(3); - - Data := Table.FieldAsBlobPtr(4, DataSize); - if (Data <> nil) and - (PixelFmt = ipfRGB) then - begin - Result := Texture.CreateTexture(Data, Filename, Width, Height, 24) - end - else - begin - // FillChar() does not decrement the ref-count of ref-counted fields - // -> reset Name field manually - Result.Name := nil; - FillChar(Result, SizeOf(TTexture), 0); - end; - except on E: Exception do - Log.LogError(E.Message, 'TCoverDatabase.LoadCover'); - end; - - Table.Free; -end; - -procedure TCoverDatabase.DeleteCover(CoverID: int64); -begin - DB.ExecSQL(Format('DELETE FROM ['+COVER_TBL+'] WHERE [ID] = %d', [CoverID])); - DB.ExecSQL(Format('DELETE FROM ['+COVER_THUMBNAIL_TBL+'] WHERE [ID] = %d', [CoverID])); -end; - -(** - * Returns a pointer to an array of bytes containing the texture data in the - * requested size - *) -function TCoverDatabase.CreateThumbnail(const Filename: IPath; var Info: TThumbnailInfo): PSDL_Surface; -var - //TargetAspect, SourceAspect: double; - //TargetWidth, TargetHeight: integer; - Thumbnail: PSDL_Surface; - MaxSize: integer; -begin - Result := nil; - - MaxSize := GetMaxCoverSize(); - - Thumbnail := LoadImage(Filename); - if (not assigned(Thumbnail)) then - begin - Log.LogError('Could not load cover: "'+ Filename.ToNative +'"', 'TCoverDatabase.AddCover'); - Exit; - end; - - // Convert pixel format as needed - AdjustPixelFormat(Thumbnail, TEXTURE_TYPE_PLAIN); - - Info.CoverWidth := Thumbnail^.w; - Info.CoverHeight := Thumbnail^.h; - Info.PixelFormat := ipfRGB; - - (* TODO: keep aspect ratio - TargetAspect := Width / Height; - SourceAspect := TexSurface.w / TexSurface.h; - - // Scale texture to covers dimensions (keep aspect) - if (SourceAspect >= TargetAspect) then - begin - TargetWidth := Width; - TargetHeight := Trunc(Width / SourceAspect); - end - else - begin - TargetHeight := Height; - TargetWidth := Trunc(Height * SourceAspect); - end; - *) - - // TODO: do not scale if image is smaller - ScaleImage(Thumbnail, MaxSize, MaxSize); - - Result := Thumbnail; -end; - -end. - |