Browse Source

use blurhash for song backgrounds (#526)

* generate blurhash for all local artwork

* use blurhash song background if available

* only write blur hash to disk once

* use multiple blur hashes

* update changelog

* fix song detail outline

* reset song metadata (artwork)
pull/527/head
Jason Dove 4 years ago committed by GitHub
parent
commit
3773bbec19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 3
      ErsatzTV.Core/Domain/Metadata/Artwork.cs
  3. 16
      ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs
  4. 6
      ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs
  5. 13
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  6. 63
      ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs
  7. 4
      ErsatzTV.Core/FFmpeg/SubtitleBuilder.cs
  8. 2
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  9. 3
      ErsatzTV.Core/Interfaces/Images/IImageCache.cs
  10. 8
      ErsatzTV.Core/Metadata/LocalFolderScanner.cs
  11. 34
      ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs
  12. 1
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  13. 28
      ErsatzTV.Infrastructure/Images/ImageCache.cs
  14. 3846
      ErsatzTV.Infrastructure/Migrations/20211203040447_Add_ArtworkBlurHash.Designer.cs
  15. 25
      ErsatzTV.Infrastructure/Migrations/20211203040447_Add_ArtworkBlurHash.cs
  16. 3852
      ErsatzTV.Infrastructure/Migrations/20211203174753_Add_MoreArtworkBlurHashes.Designer.cs
  17. 45
      ErsatzTV.Infrastructure/Migrations/20211203174753_Add_MoreArtworkBlurHashes.cs
  18. 3852
      ErsatzTV.Infrastructure/Migrations/20211203182334_Reset_SongMetadataBlurHash.Designer.cs
  19. 31
      ErsatzTV.Infrastructure/Migrations/20211203182334_Reset_SongMetadataBlurHash.cs
  20. 9
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

2
CHANGELOG.md

@ -9,9 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -9,9 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix unicode song metadata on Windows
- Fix unicode console output on Windows
- Fix TV Show NFO metadata processing when `year` is missing
- Fix song detail outline to help legibility on white backgrounds
### Changed
- Use custom log database backend which should be more portable (i.e. work in osx-arm64)
- Use cover art blurhashes for song backgrounds instead of solid colors or box blur
## [0.3.1-alpha] - 2021-11-30
### Fixed

3
ErsatzTV.Core/Domain/Metadata/Artwork.cs

@ -6,6 +6,9 @@ namespace ErsatzTV.Core.Domain @@ -6,6 +6,9 @@ namespace ErsatzTV.Core.Domain
{
public int Id { get; set; }
public string Path { get; set; }
public string BlurHash43 { get; set; }
public string BlurHash54 { get; set; }
public string BlurHash64 { get; set; }
public ArtworkKind ArtworkKind { get; set; }
public DateTime DateAdded { get; set; }
public DateTime DateUpdated { get; set; }

16
ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs

@ -27,7 +27,6 @@ namespace ErsatzTV.Core.FFmpeg @@ -27,7 +27,6 @@ namespace ErsatzTV.Core.FFmpeg
private string _videoEncoder;
private Option<string> _subtitle;
private bool _boxBlur;
private Option<int> _randomColor;
public FFmpegComplexFilterBuilder WithHardwareAcceleration(HardwareAccelerationKind hardwareAccelerationKind)
{
@ -102,12 +101,6 @@ namespace ErsatzTV.Core.FFmpeg @@ -102,12 +101,6 @@ namespace ErsatzTV.Core.FFmpeg
return this;
}
public FFmpegComplexFilterBuilder WithRandomColor(Option<int> randomColor)
{
_randomColor = randomColor;
return this;
}
public FFmpegComplexFilterBuilder WithSubtitleFile(Option<string> subtitleFile)
{
foreach (string file in subtitleFile)
@ -250,7 +243,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -250,7 +243,7 @@ namespace ErsatzTV.Core.FFmpeg
_ => $"scale={size.Width}:{size.Height}:flags=fast_bilinear"
};
if (_randomColor.IsNone && !string.IsNullOrWhiteSpace(filter))
if (!string.IsNullOrWhiteSpace(filter))
{
videoFilterQueue.Add(filter);
}
@ -276,7 +269,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -276,7 +269,7 @@ namespace ErsatzTV.Core.FFmpeg
videoFilterQueue.Add(format);
}
if (scaleOrPad && _boxBlur == false && _randomColor.IsNone)
if (scaleOrPad && _boxBlur == false)
{
videoFilterQueue.Add("setsar=1");
}
@ -286,10 +279,9 @@ namespace ErsatzTV.Core.FFmpeg @@ -286,10 +279,9 @@ namespace ErsatzTV.Core.FFmpeg
videoFilterQueue.Add("boxblur=40");
}
foreach (int color in _randomColor)
if (videoOnly)
{
videoFilterQueue.Add(
$"palettegen=max_colors=8,crop=1:1:{color}:0,scale={_resolution.Width}:{_resolution.Height},setsar=1");
videoFilterQueue.Add("deband");
}
foreach (ChannelWatermark watermark in _watermark)

6
ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs

@ -285,8 +285,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -285,8 +285,7 @@ namespace ErsatzTV.Core.FFmpeg
string videoPath,
Option<string> codec,
Option<string> pixelFormat,
bool boxBlur,
Option<int> randomColor)
bool boxBlur)
{
_noAutoScale = true;
_outputFramerate = 30;
@ -294,8 +293,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -294,8 +293,7 @@ namespace ErsatzTV.Core.FFmpeg
_complexFilterBuilder = _complexFilterBuilder
.WithInputCodec(codec)
.WithInputPixelFormat(pixelFormat)
.WithBoxBlur(boxBlur)
.WithRandomColor(randomColor);
.WithBoxBlur(boxBlur);
_arguments.Add("-i");
_arguments.Add(videoPath);

13
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -68,7 +68,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -68,7 +68,7 @@ namespace ErsatzTV.Core.FFmpeg
outPoint);
Option<WatermarkOptions> watermarkOptions =
await GetWatermarkOptions(channel, globalWatermark, videoVersion, None);
await GetWatermarkOptions(channel, globalWatermark, videoVersion, None, None);
FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath, saveReports, _logger)
.WithThreads(playbackSettings.ThreadCount)
@ -276,7 +276,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -276,7 +276,7 @@ namespace ErsatzTV.Core.FFmpeg
MediaVersion videoVersion,
string videoPath,
bool boxBlur,
Option<int> randomColor,
Option<string> watermarkPath,
ChannelWatermarkLocation watermarkLocation,
int horizontalMarginPercent,
int verticalMarginPercent,
@ -303,7 +303,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -303,7 +303,7 @@ namespace ErsatzTV.Core.FFmpeg
: None;
Option<WatermarkOptions> watermarkOptions =
await GetWatermarkOptions(channel, globalWatermark, videoVersion, watermarkOverride);
await GetWatermarkOptions(channel, globalWatermark, videoVersion, watermarkOverride, watermarkPath);
FFmpegPlaybackSettings playbackSettings =
_playbackSettingsCalculator.CalculateErrorSettings(channel.FFmpegProfile);
@ -323,7 +323,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -323,7 +323,7 @@ namespace ErsatzTV.Core.FFmpeg
.WithThreads(1)
.WithQuiet()
.WithFormatFlags(playbackSettings.FormatFlags)
.WithSongInput(videoPath, videoStream.Codec, videoStream.PixelFormat, boxBlur, randomColor)
.WithSongInput(videoPath, videoStream.Codec, videoStream.PixelFormat, boxBlur)
.WithWatermark(watermarkOptions, channel.FFmpegProfile.Resolution)
.WithSubtitleFile(subtitleFile);
@ -370,7 +370,8 @@ namespace ErsatzTV.Core.FFmpeg @@ -370,7 +370,8 @@ namespace ErsatzTV.Core.FFmpeg
Channel channel,
Option<ChannelWatermark> globalWatermark,
MediaVersion videoVersion,
Option<ChannelWatermark> watermarkOverride)
Option<ChannelWatermark> watermarkOverride,
Option<string> watermarkPath)
{
if (videoVersion is BackgroundImageMediaVersion)
{
@ -384,7 +385,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -384,7 +385,7 @@ namespace ErsatzTV.Core.FFmpeg
{
return new WatermarkOptions(
watermarkOverride,
videoVersion.MediaFiles.Head().Path,
await watermarkPath.IfNoneAsync(videoVersion.MediaFiles.Head().Path),
0,
false);
}

63
ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
@ -50,7 +51,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -50,7 +51,7 @@ namespace ErsatzTV.Core.FFmpeg
new() { MediaStreamKind = MediaStreamKind.Video, Index = 0 }
}
};
string[] backgrounds =
{
"background_blank.png",
@ -60,12 +61,13 @@ namespace ErsatzTV.Core.FFmpeg @@ -60,12 +61,13 @@ namespace ErsatzTV.Core.FFmpeg
};
// use random ETV color by default
string artworkPath = Path.Combine(
string backgroundPath = Path.Combine(
FileSystemLayout.ResourcesCacheFolder,
backgrounds[NextRandom(backgrounds.Length)]);
Option<string> watermarkPath = None;
var boxBlur = false;
Option<int> randomColor = None;
const int HORIZONTAL_MARGIN_PERCENT = 3;
const int VERTICAL_MARGIN_PERCENT = 5;
@ -135,7 +137,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -135,7 +137,7 @@ namespace ErsatzTV.Core.FFmpeg
.WithFontName("OPTIKabel-Heavy")
.WithFontSize(fontSize)
.WithPrimaryColor("&HFFFFFF")
.WithOutlineColor("&H555555")
.WithOutlineColor("&H444444")
.WithAlignment(0)
.WithMarginRight(rightMargin)
.WithMarginLeft(leftMargin)
@ -149,23 +151,6 @@ namespace ErsatzTV.Core.FFmpeg @@ -149,23 +151,6 @@ namespace ErsatzTV.Core.FFmpeg
foreach (Artwork artwork in Optional(
metadata.Artwork.Find(a => a.ArtworkKind == ArtworkKind.Thumbnail)))
{
int backgroundRoll = NextRandom(16);
if (backgroundRoll < 8)
{
randomColor = backgroundRoll;
}
else
{
boxBlur = true;
}
string customPath = _imageCache.GetPathForImage(
artwork.Path,
ArtworkKind.Thumbnail,
Option<int>.None);
artworkPath = customPath;
// signal that we want to use cover art as watermark
videoVersion = new CoverArtMediaVersion
{
@ -179,10 +164,42 @@ namespace ErsatzTV.Core.FFmpeg @@ -179,10 +164,42 @@ namespace ErsatzTV.Core.FFmpeg
new() { MediaStreamKind = MediaStreamKind.Video, Index = 0 }
}
};
string customPath = _imageCache.GetPathForImage(
artwork.Path,
ArtworkKind.Thumbnail,
Option<int>.None);
watermarkPath = customPath;
// randomize selected blur hash
var hashes = new List<string>
{
artwork.BlurHash43,
artwork.BlurHash54,
artwork.BlurHash64
}.Filter(s => !string.IsNullOrWhiteSpace(s)).ToList();
if (hashes.Any())
{
string hash = hashes[NextRandom(hashes.Count)];
backgroundPath = await _imageCache.WriteBlurHash(
hash,
channel.FFmpegProfile.Resolution);
videoVersion.Height = channel.FFmpegProfile.Resolution.Height;
videoVersion.Width = channel.FFmpegProfile.Resolution.Width;
}
else
{
backgroundPath = customPath;
boxBlur = true;
}
}
}
string videoPath = artworkPath;
string videoPath = backgroundPath;
videoVersion.MediaFiles = new List<MediaFile>
{
@ -197,7 +214,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -197,7 +214,7 @@ namespace ErsatzTV.Core.FFmpeg
videoVersion,
videoPath,
boxBlur,
randomColor,
watermarkPath,
watermarkLocation,
HORIZONTAL_MARGIN_PERCENT,
VERTICAL_MARGIN_PERCENT,

4
ErsatzTV.Core/FFmpeg/SubtitleBuilder.cs

@ -118,8 +118,8 @@ namespace ErsatzTV.Core.FFmpeg @@ -118,8 +118,8 @@ namespace ErsatzTV.Core.FFmpeg
}
sb.AppendLine("[V4+ Styles]");
sb.AppendLine("Format: Name, Fontname, Fontsize, PrimaryColour, OutlineColour, BorderStyle, Shadow, Alignment, Encoding");
sb.AppendLine($"Style: Default,{await _fontName.IfNoneAsync("")},{await _fontSize.IfNoneAsync(32)},{await _primaryColor.IfNoneAsync("")},{await _outlineColor.IfNoneAsync("")},{await _borderStyle.IfNoneAsync(0)},{await _shadow.IfNoneAsync(0)}, {await _alignment.IfNoneAsync(0)},1");
sb.AppendLine("Format: Name, Fontname, Fontsize, PrimaryColour, OutlineColour, BorderStyle, Outline, Shadow, Alignment, Encoding");
sb.AppendLine($"Style: Default,{await _fontName.IfNoneAsync("")},{await _fontSize.IfNoneAsync(32)},{await _primaryColor.IfNoneAsync("")},{await _outlineColor.IfNoneAsync("")},{await _borderStyle.IfNoneAsync(0)},1,{await _shadow.IfNoneAsync(0)},{await _alignment.IfNoneAsync(0)},1");
sb.AppendLine("[Events]");
sb.AppendLine("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");

2
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs

@ -50,7 +50,7 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg @@ -50,7 +50,7 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg
MediaVersion videoVersion,
string videoPath,
bool boxBlur,
Option<int> randomColor,
Option<string> watermarkPath,
ChannelWatermarkLocation watermarkLocation,
int horizontalMarginPercent,
int verticalMarginPercent,

3
ErsatzTV.Core/Interfaces/Images/IImageCache.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using System.IO;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
using LanguageExt;
namespace ErsatzTV.Core.Interfaces.Images
@ -12,5 +13,7 @@ namespace ErsatzTV.Core.Interfaces.Images @@ -12,5 +13,7 @@ namespace ErsatzTV.Core.Interfaces.Images
Task<Either<BaseError, string>> CopyArtworkToCache(string path, ArtworkKind artworkKind);
string GetPathForImage(string fileName, ArtworkKind artworkKind, Option<int> maybeMaxHeight);
Task<bool> IsAnimated(string fileName);
Task<string> CalculateBlurHash(string fileName, ArtworkKind artworkKind, int x, int y);
Task<string> WriteBlurHash(string blurHash, IDisplaySize targetSize);
}
}

8
ErsatzTV.Core/Metadata/LocalFolderScanner.cs

@ -180,6 +180,9 @@ namespace ErsatzTV.Core.Metadata @@ -180,6 +180,9 @@ namespace ErsatzTV.Core.Metadata
{
artwork.Path = cacheName;
artwork.DateUpdated = lastWriteTime;
artwork.BlurHash43 = await _imageCache.CalculateBlurHash(cacheName, artworkKind, 4, 3);
artwork.BlurHash54 = await _imageCache.CalculateBlurHash(cacheName, artworkKind, 5, 4);
artwork.BlurHash64 = await _imageCache.CalculateBlurHash(cacheName, artworkKind, 6, 4);
await _metadataRepository.UpdateArtworkPath(artwork);
},
async () =>
@ -189,7 +192,10 @@ namespace ErsatzTV.Core.Metadata @@ -189,7 +192,10 @@ namespace ErsatzTV.Core.Metadata
Path = cacheName,
DateAdded = DateTime.UtcNow,
DateUpdated = lastWriteTime,
ArtworkKind = artworkKind
ArtworkKind = artworkKind,
BlurHash43 = await _imageCache.CalculateBlurHash(cacheName, artworkKind, 4, 3),
BlurHash54 = await _imageCache.CalculateBlurHash(cacheName, artworkKind, 5, 4),
BlurHash64 = await _imageCache.CalculateBlurHash(cacheName, artworkKind, 6, 4)
};
metadata.Artwork.Add(artwork);
await _metadataRepository.AddArtwork(metadata, artwork);

34
ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs

@ -209,51 +209,51 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -209,51 +209,51 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public Task<Unit> UpdateArtworkPath(Artwork artwork) =>
_dbConnection.ExecuteAsync(
"UPDATE Artwork SET Path = @Path, DateUpdated = @DateUpdated WHERE Id = @Id",
new { artwork.Path, artwork.DateUpdated, artwork.Id }).ToUnit();
"UPDATE Artwork SET Path = @Path, DateUpdated = @DateUpdated, BlurHash43 = @BlurHash43, BlurHash43 = @BlurHash54, BlurHash43 = @BlurHash64 WHERE Id = @Id",
new { artwork.Path, artwork.DateUpdated, artwork.BlurHash43, artwork.BlurHash54, artwork.BlurHash64, artwork.Id }).ToUnit();
public Task<Unit> AddArtwork(Metadata metadata, Artwork artwork)
{
var parameters = new
{
artwork.ArtworkKind, metadata.Id, artwork.DateAdded, artwork.DateUpdated, artwork.Path
artwork.ArtworkKind, metadata.Id, artwork.DateAdded, artwork.DateUpdated, artwork.Path, artwork.BlurHash43, artwork.BlurHash54, artwork.BlurHash64
};
return metadata switch
{
MovieMetadata => _dbConnection.ExecuteAsync(
@"INSERT INTO Artwork (ArtworkKind, MovieMetadataId, DateAdded, DateUpdated, Path)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
@"INSERT INTO Artwork (ArtworkKind, MovieMetadataId, DateAdded, DateUpdated, Path, BlurHash43, BlurHash54, BlurHash64)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @BlurHash43, @BlurHash54, @BlurHash64)",
parameters)
.ToUnit(),
ShowMetadata => _dbConnection.ExecuteAsync(
@"INSERT INTO Artwork (ArtworkKind, ShowMetadataId, DateAdded, DateUpdated, Path)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
@"INSERT INTO Artwork (ArtworkKind, ShowMetadataId, DateAdded, DateUpdated, Path, BlurHash43, BlurHash54, BlurHash64)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @BlurHash43, @BlurHash54, @BlurHash64)",
parameters)
.ToUnit(),
SeasonMetadata => _dbConnection.ExecuteAsync(
@"INSERT INTO Artwork (ArtworkKind, SeasonMetadataId, DateAdded, DateUpdated, Path)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
@"INSERT INTO Artwork (ArtworkKind, SeasonMetadataId, DateAdded, DateUpdated, Path, BlurHash43, BlurHash54, BlurHash64)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @BlurHash43, @BlurHash54, @BlurHash64)",
parameters)
.ToUnit(),
EpisodeMetadata => _dbConnection.ExecuteAsync(
@"INSERT INTO Artwork (ArtworkKind, EpisodeMetadataId, DateAdded, DateUpdated, Path)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
@"INSERT INTO Artwork (ArtworkKind, EpisodeMetadataId, DateAdded, DateUpdated, Path, BlurHash43, BlurHash54, BlurHash64)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @BlurHash43, @BlurHash54, @BlurHash64)",
parameters)
.ToUnit(),
ArtistMetadata => _dbConnection.ExecuteAsync(
@"INSERT INTO Artwork (ArtworkKind, ArtistMetadataId, DateAdded, DateUpdated, Path)
Values (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
@"INSERT INTO Artwork (ArtworkKind, ArtistMetadataId, DateAdded, DateUpdated, Path, BlurHash43, BlurHash54, BlurHash64)
Values (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @BlurHash43, @BlurHash54, @BlurHash64)",
parameters)
.ToUnit(),
MusicVideoMetadata => _dbConnection.ExecuteAsync(
@"INSERT INTO Artwork (ArtworkKind, MusicVideoMetadataId, DateAdded, DateUpdated, Path)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
@"INSERT INTO Artwork (ArtworkKind, MusicVideoMetadataId, DateAdded, DateUpdated, Path, BlurHash43, BlurHash54, BlurHash64)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @BlurHash43, @BlurHash54, @BlurHash64)",
parameters)
.ToUnit(),
SongMetadata => _dbConnection.ExecuteAsync(
@"INSERT INTO Artwork (ArtworkKind, SongMetadataId, DateAdded, DateUpdated, Path)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path)",
@"INSERT INTO Artwork (ArtworkKind, SongMetadataId, DateAdded, DateUpdated, Path, BlurHash43, BlurHash54, BlurHash64)
VALUES (@ArtworkKind, @Id, @DateAdded, @DateUpdated, @Path, @BlurHash43, @BlurHash54, @BlurHash64)",
parameters)
.ToUnit(),
_ => Task.FromResult(Unit.Default)

1
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Blurhash.ImageSharp" Version="1.1.1" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00015" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00015" />

28
ErsatzTV.Infrastructure/Images/ImageCache.cs

@ -14,6 +14,7 @@ using Microsoft.Extensions.Caching.Memory; @@ -14,6 +14,7 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace ErsatzTV.Infrastructure.Images
@ -181,5 +182,32 @@ namespace ErsatzTV.Infrastructure.Images @@ -181,5 +182,32 @@ namespace ErsatzTV.Infrastructure.Images
return false;
}
}
public async Task<string> CalculateBlurHash(string fileName, ArtworkKind artworkKind, int x, int y)
{
var encoder = new Blurhash.ImageSharp.Encoder();
string targetFile = GetPathForImage(fileName, artworkKind, Option<int>.None);
await using var fs = new FileStream(targetFile, FileMode.Open, FileAccess.Read);
using var image = await Image.LoadAsync<Rgb24>(fs);
return encoder.Encode(image, x, y);
}
public async Task<string> WriteBlurHash(string blurHash, IDisplaySize targetSize)
{
byte[] bytes = Encoding.UTF8.GetBytes(blurHash);
string base64 = Convert.ToBase64String(bytes).Replace("+", "_").Replace("/", "-").Replace("=", "");
string targetFile = GetPathForImage(base64, ArtworkKind.Poster, targetSize.Height);
if (!_localFileSystem.FileExists(targetFile))
{
string folder = Path.GetDirectoryName(targetFile);
_localFileSystem.EnsureFolderExists(folder);
var decoder = new Blurhash.ImageSharp.Decoder();
using Image<Rgb24> image = decoder.Decode(blurHash, targetSize.Width, targetSize.Height);
await image.SaveAsPngAsync(targetFile);
}
return targetFile;
}
}
}

3846
ErsatzTV.Infrastructure/Migrations/20211203040447_Add_ArtworkBlurHash.Designer.cs generated

File diff suppressed because it is too large Load Diff

25
ErsatzTV.Infrastructure/Migrations/20211203040447_Add_ArtworkBlurHash.cs

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_ArtworkBlurHash : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "BlurHash",
table: "Artwork",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BlurHash",
table: "Artwork");
}
}
}

3852
ErsatzTV.Infrastructure/Migrations/20211203174753_Add_MoreArtworkBlurHashes.Designer.cs generated

File diff suppressed because it is too large Load Diff

45
ErsatzTV.Infrastructure/Migrations/20211203174753_Add_MoreArtworkBlurHashes.cs

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_MoreArtworkBlurHashes : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "BlurHash",
table: "Artwork",
newName: "BlurHash64");
migrationBuilder.AddColumn<string>(
name: "BlurHash43",
table: "Artwork",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "BlurHash54",
table: "Artwork",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BlurHash43",
table: "Artwork");
migrationBuilder.DropColumn(
name: "BlurHash54",
table: "Artwork");
migrationBuilder.RenameColumn(
name: "BlurHash64",
table: "Artwork",
newName: "BlurHash");
}
}
}

3852
ErsatzTV.Infrastructure/Migrations/20211203182334_Reset_SongMetadataBlurHash.Designer.cs generated

File diff suppressed because it is too large Load Diff

31
ErsatzTV.Infrastructure/Migrations/20211203182334_Reset_SongMetadataBlurHash.cs

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Reset_SongMetadataBlurHash : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"UPDATE LibraryPath SET LastScan = '0001-01-01 00:00:00' WHERE Id IN
(SELECT LP.Id FROM LibraryPath LP INNER JOIN Library L on L.Id = LP.LibraryId WHERE MediaKind = 5)");
migrationBuilder.Sql(
@"UPDATE Library SET LastScan = '0001-01-01 00:00:00' WHERE MediaKind = 5");
migrationBuilder.Sql(
@"UPDATE Artwork SET DateUpdated = '0001-01-01 00:00:00' WHERE SongMetadataId IS NOT NULL");
migrationBuilder.Sql(
@"UPDATE LibraryFolder SET Etag = NULL WHERE Id IN
(SELECT LF.Id FROM LibraryFolder LF INNER JOIN LibraryPath LP on LF.LibraryPathId = LP.Id INNER JOIN Library L on LP.LibraryId = L.Id WHERE MediaKind = 5)");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

9
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -144,6 +144,15 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -144,6 +144,15 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<int>("ArtworkKind")
.HasColumnType("INTEGER");
b.Property<string>("BlurHash43")
.HasColumnType("TEXT");
b.Property<string>("BlurHash54")
.HasColumnType("TEXT");
b.Property<string>("BlurHash64")
.HasColumnType("TEXT");
b.Property<int?>("ChannelId")
.HasColumnType("INTEGER");

Loading…
Cancel
Save