feat(media): repurpose GIFs as video and serve converted mp4 when available

- Classify .gif as EAssetType.Video via MimeTypes (moved image/gif to the Video registry)
- Add MediaRepository.GetConvertedData, served by the existing media/{id} endpoint
  when a converted file exists on disk (with a fallback + warning log if missing)
- Expose the converted MIME type on AssetPreviewDto/AlbumAssetPreviewDto
  so the frontend can detect video assets from a single MimeType field
This commit is contained in:
2026-08-20 23:58:48 +02:00
parent 7fd294043e
commit 5b4e2b9afc
10 changed files with 71 additions and 7 deletions
+1
View File
@@ -177,6 +177,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
ResolutionHeight = a.ResolutionHeight,
HasThumbnail = a.ThumbnailPath != null && a.ThumbnailPath != "",
HasPreview = a.PreviewPath != null && a.PreviewPath != "",
MimeType = a.ConvertedMimeType != null && a.ConvertedMimeType != "" ? a.ConvertedMimeType : a.MimeType,
FileName = accessLevel >= EAccessLevel.Curator ? a.OriginalFilename : null,
Visibility = accessLevel >= EAccessLevel.Maintainer ? a.Visibility : null,
DeletedAt = accessLevel >= EAccessLevel.Admin || a.UploadedBy == userId ? a.DeletedAt : null
+1 -2
View File
@@ -317,7 +317,7 @@ public class AssetRepository(LactoseDbContext context, ILogger<AssetRepository>
.Where(a => pageIds.Contains(a.Id))
.Select(a => new AssetPreviewDto {
Id = a.Id,
MimeType = a.MimeType,
MimeType = a.ConvertedMimeType != null && a.ConvertedMimeType != "" ? a.ConvertedMimeType : a.MimeType,
ResolutionWidth = a.ResolutionWidth,
ResolutionHeight = a.ResolutionHeight,
HasThumbnail = a.ThumbnailPath != null && a.ThumbnailPath != "",
@@ -459,7 +459,6 @@ public class AssetRepository(LactoseDbContext context, ILogger<AssetRepository>
return new Butter.Dtos.Asset.AssetBrowseResultDto {
Directories = dirs,
Assets = assetDtos,
CurrentPath = currentPath,
TotalAssetCount = totalAtLevel
};
}
+9
View File
@@ -33,4 +33,13 @@ public interface IMediaRepository : IDisposable {
/// <param name="accessLevel">The requesting user's access level.</param>
/// <returns>A <see cref="MediaDto"/> containing the media data, or null if not found or unauthorized.</returns>
public MediaDto? GetPreviewData(Guid id, Guid? userId, EAccessLevel accessLevel = EAccessLevel.User);
/// <summary>
/// Retrieves the converted media data (e.g. the mp4 for an animated GIF).
/// </summary>
/// <param name="id">The ID of the media.</param>
/// <param name="userId">The ID of the user requesting the media.</param>
/// <param name="accessLevel">The requesting user's access level.</param>
/// <returns>A <see cref="MediaDto"/> containing the converted media data, or null if the asset has no converted file or is not visible.</returns>
public MediaDto? GetConvertedData(Guid id, Guid? userId, EAccessLevel accessLevel = EAccessLevel.User);
}
+14
View File
@@ -93,6 +93,20 @@ public class MediaRepository(LactoseDbContext context, IPersonRepository personR
};
}
/// <inheritdoc />
public MediaDto? GetConvertedData(Guid id, Guid? userId, EAccessLevel accessLevel = EAccessLevel.User) {
var media = GetVisibleAsset(id, userId, accessLevel);
if (media == null) return null;
if (string.IsNullOrEmpty(media.ConvertedPath) || string.IsNullOrEmpty(media.ConvertedMimeType)) return null;
return new MediaDto {
MimeType = media.ConvertedMimeType,
Path = media.ConvertedPath
};
}
/// <inheritdoc />
public void Dispose() {
context.Dispose();