Browse Source

allow decimal image duration (#1619)

* add missing mysql migration

* allow decimal image duration
pull/1621/head
Jason Dove 2 years ago committed by GitHub
parent
commit
f4520a5520
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/Images/Commands/UpdateImageFolderDuration.cs
  3. 16
      ErsatzTV.Application/Images/Commands/UpdateImageFolderDurationHandler.cs
  4. 2
      ErsatzTV.Application/Images/ImageFolderViewModel.cs
  5. 2
      ErsatzTV.Application/Images/Mapper.cs
  6. 4
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  7. 2
      ErsatzTV.Core/Domain/MediaItem/ImageFolderDuration.cs
  8. 2
      ErsatzTV.Core/Domain/Metadata/ImageMetadata.cs
  9. 5168
      ErsatzTV.Infrastructure.MySql/Migrations/20240217013246_Add_ImageFolderDuration.Designer.cs
  10. 49
      ErsatzTV.Infrastructure.MySql/Migrations/20240217013246_Add_ImageFolderDuration.cs
  11. 5168
      ErsatzTV.Infrastructure.MySql/Migrations/20240217015859_Update_ImageFolderDuration.Designer.cs
  12. 52
      ErsatzTV.Infrastructure.MySql/Migrations/20240217015859_Update_ImageFolderDuration.cs
  13. 36
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  14. 5166
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240217013847_Update_ImageFolderDuration.Designer.cs
  15. 52
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240217013847_Update_ImageFolderDuration.cs
  16. 8
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  17. 2
      ErsatzTV.Scanner/Core/Interfaces/Metadata/ILocalMetadataProvider.cs
  18. 20
      ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs
  19. 4
      ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs
  20. 2
      ErsatzTV/Pages/ImageBrowser.razor
  21. 11
      ErsatzTV/Shared/EditImageFolderDurationDialog.razor
  22. 12
      ErsatzTV/ViewModels/ImageTreeItemViewModel.cs

2
CHANGELOG.md

@ -38,7 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -38,7 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix timestamp continuity in `HLS Segmenter` sessions
- This should make *some* clients happier
- Fix `Other Video`, `Song` and `Image` fallback metadata tags to always include parent folder (folder added to library)
- Allow playback of items with a duration of 1 second
- Allow playback of items with any positive duration, including less than one second
### Changed
- Log search index updates under scanner category at debug level, to indicate a potential cause for the UI being out of date

2
ErsatzTV.Application/Images/Commands/UpdateImageFolderDuration.cs

@ -1,3 +1,3 @@ @@ -1,3 +1,3 @@
namespace ErsatzTV.Application.Images;
public record UpdateImageFolderDuration(int LibraryFolderId, int? ImageFolderDuration) : IRequest<int?>;
public record UpdateImageFolderDuration(int LibraryFolderId, double? ImageFolderDuration) : IRequest<double?>;

16
ErsatzTV.Application/Images/Commands/UpdateImageFolderDurationHandler.cs

@ -6,15 +6,15 @@ using Microsoft.EntityFrameworkCore; @@ -6,15 +6,15 @@ using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Images;
public class UpdateImageFolderDurationHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<UpdateImageFolderDuration, int?>
: IRequestHandler<UpdateImageFolderDuration, double?>
{
public async Task<int?> Handle(UpdateImageFolderDuration request, CancellationToken cancellationToken)
public async Task<double?> Handle(UpdateImageFolderDuration request, CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
if (request.ImageFolderDuration.IfNone(1) < 1)
if (request.ImageFolderDuration.IfNone(1) < 0.01)
{
request = request with { ImageFolderDuration = 1 };
request = request with { ImageFolderDuration = 0.01 };
}
// delete entry if null
@ -61,7 +61,7 @@ public class UpdateImageFolderDurationHandler(IDbContextFactory<TvContext> dbCon @@ -61,7 +61,7 @@ public class UpdateImageFolderDurationHandler(IDbContextFactory<TvContext> dbCon
LibraryFolder currentFolder = libraryFolder;
// walk up to get duration, if needed
int? durationSeconds = currentFolder.ImageFolderDuration?.DurationSeconds;
double? durationSeconds = currentFolder.ImageFolderDuration?.DurationSeconds;
while (durationSeconds is null && currentFolder?.ParentId is not null)
{
Option<LibraryFolder> maybeParent = await dbContext.LibraryFolders
@ -86,8 +86,8 @@ public class UpdateImageFolderDurationHandler(IDbContextFactory<TvContext> dbCon @@ -86,8 +86,8 @@ public class UpdateImageFolderDurationHandler(IDbContextFactory<TvContext> dbCon
while (queue.Count > 0)
{
(LibraryFolder currentFolder, int? parentDuration) = queue.Dequeue();
int? effectiveDuration = currentFolder.ImageFolderDuration?.DurationSeconds ?? parentDuration;
(LibraryFolder currentFolder, double? parentDuration) = queue.Dequeue();
double? effectiveDuration = currentFolder.ImageFolderDuration?.DurationSeconds ?? parentDuration;
// Serilog.Log.Logger.Information(
// "Updating folder {Id} with parent duration {ParentDuration}, effective duration {EffectiveDuration}",
@ -120,5 +120,5 @@ public class UpdateImageFolderDurationHandler(IDbContextFactory<TvContext> dbCon @@ -120,5 +120,5 @@ public class UpdateImageFolderDurationHandler(IDbContextFactory<TvContext> dbCon
return request.ImageFolderDuration;
}
private sealed record FolderWithParentDuration(LibraryFolder LibraryFolder, int? ParentDuration);
private sealed record FolderWithParentDuration(LibraryFolder LibraryFolder, double? ParentDuration);
}

2
ErsatzTV.Application/Images/ImageFolderViewModel.cs

@ -6,4 +6,4 @@ public record ImageFolderViewModel( @@ -6,4 +6,4 @@ public record ImageFolderViewModel(
string FullPath,
int SubfolderCount,
int ImageCount,
Option<int> DurationSeconds);
Option<double> DurationSeconds);

2
ErsatzTV.Application/Images/Mapper.cs

@ -14,5 +14,5 @@ public static class Mapper @@ -14,5 +14,5 @@ public static class Mapper
libraryFolder.Path,
childCount,
imageCount,
libraryFolder.ImageFolderDuration?.DurationSeconds ?? Option<int>.None);
libraryFolder.ImageFolderDuration?.DurationSeconds ?? Option<double>.None);
}

4
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -342,9 +342,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -342,9 +342,7 @@ public class HlsSessionWorker : IHlsSessionWorker
_logger.LogDebug("HLS session state: {State}", _state);
DateTimeOffset now = _state is HlsSessionState.SeekAndWorkAhead
? DateTimeOffset.Now
: _transcodedUntil.AddSeconds(_state is HlsSessionState.SeekAndRealtime ? 0 : 0.5);
DateTimeOffset now = _state is HlsSessionState.SeekAndWorkAhead ? DateTimeOffset.Now : _transcodedUntil;
bool startAtZero = _state is HlsSessionState.ZeroAndWorkAhead or HlsSessionState.ZeroAndRealtime;
var request = new GetPlayoutItemProcessByChannelNumber(

2
ErsatzTV.Core/Domain/MediaItem/ImageFolderDuration.cs

@ -5,5 +5,5 @@ public class ImageFolderDuration @@ -5,5 +5,5 @@ public class ImageFolderDuration
public int Id { get; set; }
public int LibraryFolderId { get; set; }
public LibraryFolder LibraryFolder { get; set; }
public int DurationSeconds { get; set; }
public double DurationSeconds { get; set; }
}

2
ErsatzTV.Core/Domain/Metadata/ImageMetadata.cs

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
public class ImageMetadata : Metadata
{
public int? DurationSeconds { get; set; }
public double? DurationSeconds { get; set; }
public int ImageId { get; set; }
public Image Image { get; set; }
}

5168
ErsatzTV.Infrastructure.MySql/Migrations/20240217013246_Add_ImageFolderDuration.Designer.cs generated

File diff suppressed because it is too large Load Diff

49
ErsatzTV.Infrastructure.MySql/Migrations/20240217013246_Add_ImageFolderDuration.cs

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_ImageFolderDuration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ImageFolderDuration",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
LibraryFolderId = table.Column<int>(type: "int", nullable: false),
DurationSeconds = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ImageFolderDuration", x => x.Id);
table.ForeignKey(
name: "FK_ImageFolderDuration_LibraryFolder_LibraryFolderId",
column: x => x.LibraryFolderId,
principalTable: "LibraryFolder",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_ImageFolderDuration_LibraryFolderId",
table: "ImageFolderDuration",
column: "LibraryFolderId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ImageFolderDuration");
}
}
}

5168
ErsatzTV.Infrastructure.MySql/Migrations/20240217015859_Update_ImageFolderDuration.Designer.cs generated

File diff suppressed because it is too large Load Diff

52
ErsatzTV.Infrastructure.MySql/Migrations/20240217015859_Update_ImageFolderDuration.cs

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Update_ImageFolderDuration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<double>(
name: "DurationSeconds",
table: "ImageMetadata",
type: "double",
nullable: true,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AlterColumn<double>(
name: "DurationSeconds",
table: "ImageFolderDuration",
type: "double",
nullable: false,
oldClrType: typeof(int),
oldType: "int");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "DurationSeconds",
table: "ImageMetadata",
type: "int",
nullable: true,
oldClrType: typeof(double),
oldType: "double",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "DurationSeconds",
table: "ImageFolderDuration",
type: "int",
nullable: false,
oldClrType: typeof(double),
oldType: "double");
}
}
}

36
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -735,6 +735,26 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -735,6 +735,26 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.ToTable("Genre");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ImageFolderDuration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<double>("DurationSeconds")
.HasColumnType("double");
b.Property<int>("LibraryFolderId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("LibraryFolderId")
.IsUnique();
b.ToTable("ImageFolderDuration", (string)null);
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ImageMetadata", b =>
{
b.Property<int>("Id")
@ -747,8 +767,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -747,8 +767,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<DateTime>("DateUpdated")
.HasColumnType("datetime(6)");
b.Property<int?>("DurationSeconds")
.HasColumnType("int");
b.Property<double?>("DurationSeconds")
.HasColumnType("double");
b.Property<int>("ImageId")
.HasColumnType("int");
@ -3428,6 +3448,16 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -3428,6 +3448,16 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ImageFolderDuration", b =>
{
b.HasOne("ErsatzTV.Core.Domain.LibraryFolder", "LibraryFolder")
.WithOne("ImageFolderDuration")
.HasForeignKey("ErsatzTV.Core.Domain.ImageFolderDuration", "LibraryFolderId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("LibraryFolder");
});
modelBuilder.Entity("ErsatzTV.Core.Domain.ImageMetadata", b =>
{
b.HasOne("ErsatzTV.Core.Domain.Image", "Image")
@ -4817,6 +4847,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -4817,6 +4847,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
{
b.Navigation("Children");
b.Navigation("ImageFolderDuration");
b.Navigation("MediaFiles");
});

5166
ErsatzTV.Infrastructure.Sqlite/Migrations/20240217013847_Update_ImageFolderDuration.Designer.cs generated

File diff suppressed because it is too large Load Diff

52
ErsatzTV.Infrastructure.Sqlite/Migrations/20240217013847_Update_ImageFolderDuration.cs

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Update_ImageFolderDuration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<double>(
name: "DurationSeconds",
table: "ImageMetadata",
type: "REAL",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER",
oldNullable: true);
migrationBuilder.AlterColumn<double>(
name: "DurationSeconds",
table: "ImageFolderDuration",
type: "REAL",
nullable: false,
oldClrType: typeof(int),
oldType: "INTEGER");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "DurationSeconds",
table: "ImageMetadata",
type: "INTEGER",
nullable: true,
oldClrType: typeof(double),
oldType: "REAL",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "DurationSeconds",
table: "ImageFolderDuration",
type: "INTEGER",
nullable: false,
oldClrType: typeof(double),
oldType: "REAL");
}
}
}

8
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -739,8 +739,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -739,8 +739,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("DurationSeconds")
.HasColumnType("INTEGER");
b.Property<double>("DurationSeconds")
.HasColumnType("REAL");
b.Property<int>("LibraryFolderId")
.HasColumnType("INTEGER");
@ -765,8 +765,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -765,8 +765,8 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<DateTime>("DateUpdated")
.HasColumnType("TEXT");
b.Property<int?>("DurationSeconds")
.HasColumnType("INTEGER");
b.Property<double?>("DurationSeconds")
.HasColumnType("REAL");
b.Property<int>("ImageId")
.HasColumnType("INTEGER");

2
ErsatzTV.Scanner/Core/Interfaces/Metadata/ILocalMetadataProvider.cs

@ -13,7 +13,7 @@ public interface ILocalMetadataProvider @@ -13,7 +13,7 @@ public interface ILocalMetadataProvider
Task<bool> RefreshSidecarMetadata(MusicVideo musicVideo, string nfoFileName);
Task<bool> RefreshSidecarMetadata(OtherVideo otherVideo, string nfoFileName);
Task<bool> RefreshTagMetadata(Song song);
Task<bool> RefreshTagMetadata(Image image, int? durationSeconds);
Task<bool> RefreshTagMetadata(Image image, double? durationSeconds);
Task<bool> RefreshFallbackMetadata(Movie movie);
Task<bool> RefreshFallbackMetadata(Episode episode);
Task<bool> RefreshFallbackMetadata(Artist artist, string artistFolder);

20
ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs

@ -147,7 +147,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner @@ -147,7 +147,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner
// walk up to get duration, if needed
LibraryFolder? currentFolder = knownFolder;
int? durationSeconds = currentFolder.ImageFolderDuration?.DurationSeconds;
double? durationSeconds = currentFolder.ImageFolderDuration?.DurationSeconds;
while (durationSeconds is null && currentFolder?.ParentId is not null)
{
Option<LibraryFolder> maybeParent = libraryPath.LibraryFolders
@ -265,18 +265,24 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner @@ -265,18 +265,24 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner
private async Task<Either<BaseError, MediaItemScanResult<Image>>> UpdateMetadata(
MediaItemScanResult<Image> result,
int? durationSeconds)
double? durationSeconds)
{
try
{
Image image = result.Item;
string path = image.GetHeadVersion().MediaFiles.Head().Path;
bool shouldUpdate = true;
bool shouldUpdate = Optional(image.ImageMetadata).Flatten().HeadOrNone().Match(
m => m.MetadataKind == MetadataKind.Fallback ||
m.DateUpdated != _localFileSystem.GetLastWriteTime(path) ||
m.DurationSeconds != durationSeconds,
true);
foreach (ImageMetadata imageMetadata in Optional(image.ImageMetadata).Flatten().HeadOrNone())
{
bool durationsAreDifferent =
imageMetadata.DurationSeconds.HasValue != durationSeconds.HasValue ||
Math.Abs(imageMetadata.DurationSeconds.IfNone(1) - durationSeconds.IfNone(1)) > 0.01;
shouldUpdate = imageMetadata.MetadataKind == MetadataKind.Fallback ||
imageMetadata.DateUpdated != _localFileSystem.GetLastWriteTime(path) ||
durationsAreDifferent;
}
if (shouldUpdate)
{

4
ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs

@ -210,7 +210,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -210,7 +210,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
return await RefreshFallbackMetadata(song);
}
public async Task<bool> RefreshTagMetadata(Image image, int? durationSeconds)
public async Task<bool> RefreshTagMetadata(Image image, double? durationSeconds)
{
Option<ImageMetadata> maybeMetadata = LoadImageMetadata(image, durationSeconds);
foreach (ImageMetadata metadata in maybeMetadata)
@ -406,7 +406,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider @@ -406,7 +406,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
}
}
private Option<ImageMetadata> LoadImageMetadata(Image image, int? durationSeconds)
private Option<ImageMetadata> LoadImageMetadata(Image image, double? durationSeconds)
{
string path = image.GetHeadVersion().MediaFiles.Head().Path;

2
ErsatzTV/Pages/ImageBrowser.razor

@ -93,7 +93,7 @@ @@ -93,7 +93,7 @@
DialogResult result = await dialog.Result;
if (!result.Canceled)
{
int? duration = await Mediator.Send(new UpdateImageFolderDuration(item.LibraryFolderId, result.Data as int?), _cts.Token);
double? duration = await Mediator.Send(new UpdateImageFolderDuration(item.LibraryFolderId, result.Data as double?), _cts.Token);
item.UpdateDuration(duration);
await InvokeAsync(StateHasChanged);
}

11
ErsatzTV/Shared/EditImageFolderDurationDialog.razor

@ -7,15 +7,16 @@ @@ -7,15 +7,16 @@
Edit the image folder duration
</MudText>
</MudContainer>
<MudTextField Label="Duration" @bind-Value="@_imageDurationSeconds"
For="@(() => _imageDurationSeconds)"
<MudNumericField T="double?" Label="Duration" @bind-Value="@_imageDurationSeconds"
Adornment="Adornment.End"
AdornmentText="seconds"
Min="0.01"
Step="0.01"
Immediate="true"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel" ButtonType="ButtonType.Reset">Cancel</MudButton>
<MudButton Color="Color.Primary" Variant="Variant.Filled" Disabled="@(_imageDurationSeconds == ImageFolderDuration)" OnClick="Submit">
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="Submit">
Save Changes
</MudButton>
</DialogActions>
@ -25,12 +26,12 @@ @@ -25,12 +26,12 @@
private readonly CancellationTokenSource _cts = new();
[Parameter]
public int? ImageFolderDuration { get; set; }
public double? ImageFolderDuration { get; set; }
[CascadingParameter]
MudDialogInstance MudDialog { get; set; }
private int? _imageDurationSeconds;
private double? _imageDurationSeconds;
public void Dispose()
{

12
ErsatzTV/ViewModels/ImageTreeItemViewModel.cs

@ -6,7 +6,7 @@ namespace ErsatzTV.ViewModels; @@ -6,7 +6,7 @@ namespace ErsatzTV.ViewModels;
public class ImageTreeItemViewModel
{
private string _imageCount;
private readonly string _imageCount;
public ImageTreeItemViewModel(ImageFolderViewModel imageFolder)
{
@ -23,7 +23,7 @@ public class ImageTreeItemViewModel @@ -23,7 +23,7 @@ public class ImageTreeItemViewModel
_ => string.Empty
};
foreach (int durationSeconds in imageFolder.DurationSeconds)
foreach (double durationSeconds in imageFolder.DurationSeconds)
{
ImageFolderDuration = durationSeconds;
}
@ -42,24 +42,24 @@ public class ImageTreeItemViewModel @@ -42,24 +42,24 @@ public class ImageTreeItemViewModel
public string Icon { get; }
public int LibraryFolderId { get; }
public int? ImageFolderDuration { get; private set; }
public double? ImageFolderDuration { get; private set; }
public bool CanExpand { get; }
public S.HashSet<ImageTreeItemViewModel> TreeItems { get; }
public void UpdateDuration(int? imageFolderDuration)
public void UpdateDuration(double? imageFolderDuration)
{
ImageFolderDuration = imageFolderDuration;
string duration = string.Empty;
foreach (int durationSeconds in Optional(imageFolderDuration))
foreach (double durationSeconds in Optional(imageFolderDuration))
{
duration = durationSeconds switch
{
> 1 => $"{durationSeconds} seconds",
1 => "1 second",
> 0 => $"{durationSeconds:0.##} seconds",
_ => string.Empty
};

Loading…
Cancel
Save