Implement media storage and processing features: update configuration in .env.example and appsettings files, enhance Docker setup for media storage, and add media-related services and database entities. Include ffmpeg for media handling and adjust Kestrel settings for large file uploads.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
namespace TeleWave.Domain.Media;
|
||||
|
||||
/// <summary>
|
||||
/// Один видеофайл в библиотеке. После загрузки нормализуется ffmpeg'ом в HLS-сегменты по
|
||||
/// <see cref="SegmentSeconds"/> секунд; длительность добивается до кратности длине сегмента, чтобы
|
||||
/// эфирная математика (следующий этап) оставалась целочисленной. Rich model: приватные сеттеры,
|
||||
/// фабрика <see cref="Register"/> и поведенческие переходы статуса.
|
||||
/// </summary>
|
||||
public class MediaAsset
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
|
||||
/// <summary>Исходное имя файла (для отображения в админке).</summary>
|
||||
public string OriginalFileName { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>Расширение исходника с точкой, в нижнем регистре (например «.mkv»).</summary>
|
||||
public string OriginalExtension { get; private set; } = string.Empty;
|
||||
|
||||
public MediaSource Source { get; private set; }
|
||||
public MediaAssetStatus Status { get; private set; }
|
||||
|
||||
/// <summary>Точная длительность нарезанного ассета (кратна <see cref="SegmentSeconds"/>).</summary>
|
||||
public TimeSpan? Duration { get; private set; }
|
||||
|
||||
public int? SegmentSeconds { get; private set; }
|
||||
public int? SegmentCount { get; private set; }
|
||||
public int? Width { get; private set; }
|
||||
public int? Height { get; private set; }
|
||||
public string? VideoCodec { get; private set; }
|
||||
public string? AudioCodec { get; private set; }
|
||||
|
||||
/// <summary>Относительный путь каталога сегментов от корня хранилища («assets/{id}»).</summary>
|
||||
public string? RelativePath { get; private set; }
|
||||
|
||||
/// <summary>Текст ошибки, если <see cref="Status"/> == <see cref="MediaAssetStatus.Failed"/>.</summary>
|
||||
public string? ErrorMessage { get; private set; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; private set; }
|
||||
public DateTimeOffset UpdatedAt { get; private set; }
|
||||
|
||||
private MediaAsset() { }
|
||||
|
||||
public static MediaAsset Register(string originalFileName, string extension, MediaSource source)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
return new MediaAsset
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OriginalFileName = originalFileName,
|
||||
OriginalExtension = extension.ToLowerInvariant(),
|
||||
Source = source,
|
||||
Status = MediaAssetStatus.Pending,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now,
|
||||
};
|
||||
}
|
||||
|
||||
public void MarkProcessing()
|
||||
{
|
||||
Status = MediaAssetStatus.Processing;
|
||||
ErrorMessage = null;
|
||||
Touch();
|
||||
}
|
||||
|
||||
public void MarkReady(
|
||||
TimeSpan duration,
|
||||
int segmentSeconds,
|
||||
int segmentCount,
|
||||
int width,
|
||||
int height,
|
||||
string videoCodec,
|
||||
string audioCodec,
|
||||
string relativePath
|
||||
)
|
||||
{
|
||||
Status = MediaAssetStatus.Ready;
|
||||
Duration = duration;
|
||||
SegmentSeconds = segmentSeconds;
|
||||
SegmentCount = segmentCount;
|
||||
Width = width;
|
||||
Height = height;
|
||||
VideoCodec = videoCodec;
|
||||
AudioCodec = audioCodec;
|
||||
RelativePath = relativePath;
|
||||
ErrorMessage = null;
|
||||
Touch();
|
||||
}
|
||||
|
||||
public void MarkFailed(string error)
|
||||
{
|
||||
Status = MediaAssetStatus.Failed;
|
||||
ErrorMessage = error;
|
||||
Touch();
|
||||
}
|
||||
|
||||
/// <summary>Сбросить прерванную обработку (Processing после рестарта) обратно в очередь.</summary>
|
||||
public void ResetToPending()
|
||||
{
|
||||
Status = MediaAssetStatus.Pending;
|
||||
ErrorMessage = null;
|
||||
Touch();
|
||||
}
|
||||
|
||||
private void Touch() => UpdatedAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace TeleWave.Domain.Media;
|
||||
|
||||
/// <summary>Жизненный цикл медиа-ассета от загрузки до готовности к эфиру.</summary>
|
||||
public enum MediaAssetStatus
|
||||
{
|
||||
/// <summary>Файл загружен, ждёт обработки в очереди.</summary>
|
||||
Pending,
|
||||
|
||||
/// <summary>ffmpeg нарезает файл на HLS-сегменты.</summary>
|
||||
Processing,
|
||||
|
||||
/// <summary>Нарезка завершена, ассет готов к постановке в эфир.</summary>
|
||||
Ready,
|
||||
|
||||
/// <summary>Обработка провалилась (см. <see cref="MediaAsset.ErrorMessage"/>).</summary>
|
||||
Failed,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace TeleWave.Domain.Media;
|
||||
|
||||
/// <summary>Откуда файл попал в хранилище.</summary>
|
||||
public enum MediaSource
|
||||
{
|
||||
/// <summary>Загружен через админку (chunked/stream upload в uploads/).</summary>
|
||||
Upload,
|
||||
|
||||
/// <summary>Положен вручную в inbox/ и подобран сканером.</summary>
|
||||
Inbox,
|
||||
}
|
||||
Reference in New Issue
Block a user