Update README.md with project details, features, requirements, architecture, and data management for PLib video library manager.

This commit is contained in:
Leonid Pershin
2026-08-08 07:08:43 +03:00
parent e767e4a48c
commit ac05ea6b7f
59 changed files with 3010 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>PLib.Domain</RootNamespace>
</PropertyGroup>
</Project>
+103
View File
@@ -0,0 +1,103 @@
namespace PLib.Domain.Videos;
/// <summary>
/// A single video file that belongs to the library.
/// </summary>
/// <remarks>
/// The absolute path is the natural identity of a video: the library is a view over the
/// file system, so two entries pointing at the same path are the same video. Mutation goes
/// through explicit methods so that the entity can never end up half-updated.
/// </remarks>
public sealed class VideoItem
{
/// <summary>Required by EF Core materialization; do not use from application code.</summary>
private VideoItem()
{
FullPath = null!;
Title = null!;
}
public VideoItem(string fullPath, string title, long sizeInBytes, DateTimeOffset fileModifiedAt)
{
ArgumentException.ThrowIfNullOrWhiteSpace(fullPath);
ArgumentException.ThrowIfNullOrWhiteSpace(title);
ArgumentOutOfRangeException.ThrowIfNegative(sizeInBytes);
Id = Guid.CreateVersion7();
FullPath = fullPath;
Title = title;
SizeInBytes = sizeInBytes;
FileModifiedAt = fileModifiedAt;
AddedAt = DateTimeOffset.UtcNow;
}
public Guid Id { get; private set; }
/// <summary>Absolute path of the file on disk. Unique within the library.</summary>
public string FullPath { get; private set; }
/// <summary>Human readable name; defaults to the file name without extension.</summary>
public string Title { get; private set; }
public long SizeInBytes { get; private set; }
public TimeSpan? Duration { get; private set; }
public int? Width { get; private set; }
public int? Height { get; private set; }
public string? VideoCodec { get; private set; }
/// <summary>Absolute path of the generated poster frame, or <c>null</c> if none exists yet.</summary>
public string? ThumbnailPath { get; private set; }
/// <summary>Last write time of the file when it was last indexed.</summary>
public DateTimeOffset FileModifiedAt { get; private set; }
public DateTimeOffset AddedAt { get; private set; }
/// <summary>True once the file has been probed and a poster frame produced.</summary>
public bool IsIndexed => Duration is not null && ThumbnailPath is not null;
public void Rename(string title)
{
ArgumentException.ThrowIfNullOrWhiteSpace(title);
Title = title;
}
public void ApplyTechnicalInfo(VideoTechnicalInfo info)
{
Duration = info.Duration;
Width = info.Width;
Height = info.Height;
VideoCodec = info.VideoCodec;
}
public void AttachThumbnail(string thumbnailPath)
{
ArgumentException.ThrowIfNullOrWhiteSpace(thumbnailPath);
ThumbnailPath = thumbnailPath;
}
public void DetachThumbnail() => ThumbnailPath = null;
/// <summary>
/// Refreshes the file system facts after the file changed on disk, and invalidates
/// everything that was derived from the previous revision of the file.
/// </summary>
public void RefreshFileFacts(long sizeInBytes, DateTimeOffset fileModifiedAt)
{
ArgumentOutOfRangeException.ThrowIfNegative(sizeInBytes);
if (SizeInBytes == sizeInBytes && FileModifiedAt == fileModifiedAt)
{
return;
}
SizeInBytes = sizeInBytes;
FileModifiedAt = fileModifiedAt;
ApplyTechnicalInfo(VideoTechnicalInfo.Unknown);
DetachThumbnail();
}
}
@@ -0,0 +1,18 @@
namespace PLib.Domain.Videos;
/// <summary>
/// Technical facts about a media file that are discovered by probing it,
/// as opposed to facts we already know from the file system entry itself.
/// </summary>
/// <param name="Duration">Playback duration, or <c>null</c> if the container did not report one.</param>
/// <param name="Width">Width of the primary video stream in pixels.</param>
/// <param name="Height">Height of the primary video stream in pixels.</param>
/// <param name="VideoCodec">Short codec name of the primary video stream, e.g. <c>h264</c>.</param>
public readonly record struct VideoTechnicalInfo(
TimeSpan? Duration,
int? Width,
int? Height,
string? VideoCodec)
{
public static VideoTechnicalInfo Unknown => default;
}