Claude
Skills
Sign in
Back

media-asset-management

Included with Lifetime
$97 forever

Use when designing digital asset management systems, media libraries, upload pipelines, or asset metadata schemas. Covers media storage patterns, file organization, metadata extraction, and media APIs for headless CMS.

General

What this skill does


# Media Asset Management

Guidance for designing digital asset management systems, media libraries, and upload pipelines for headless CMS.

## When to Use This Skill

- Designing media library architecture
- Implementing file upload pipelines
- Planning asset metadata schemas
- Configuring storage providers
- Building media search and filtering

## Media Asset Model

### Core Entity

```csharp
public class MediaItem
{
    public Guid Id { get; set; }

    // File information
    public string FileName { get; set; } = string.Empty;
    public string Extension { get; set; } = string.Empty;
    public string MimeType { get; set; } = string.Empty;
    public long SizeBytes { get; set; }

    // Storage
    public string StorageProvider { get; set; } = string.Empty;
    public string StoragePath { get; set; } = string.Empty;
    public string PublicUrl { get; set; } = string.Empty;

    // Organization
    public Guid? FolderId { get; set; }
    public MediaFolder? Folder { get; set; }
    public List<string> Tags { get; set; } = new();

    // Metadata
    public MediaMetadata Metadata { get; set; } = new();

    // Audit
    public string UploadedBy { get; set; } = string.Empty;
    public DateTime UploadedUtc { get; set; }
    public DateTime? ModifiedUtc { get; set; }
}

public class MediaMetadata
{
    // Common
    public string? Title { get; set; }
    public string? Description { get; set; }
    public string? Alt { get; set; }
    public string? Caption { get; set; }
    public string? Credit { get; set; }

    // Image-specific
    public int? Width { get; set; }
    public int? Height { get; set; }
    public string? ColorSpace { get; set; }

    // Document-specific
    public int? PageCount { get; set; }
    public string? Author { get; set; }

    // Video-specific
    public TimeSpan? Duration { get; set; }
    public string? Codec { get; set; }
    public int? Bitrate { get; set; }

    // EXIF/XMP
    public Dictionary<string, string> ExifData { get; set; } = new();
}

public class MediaFolder
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Path { get; set; } = string.Empty;
    public Guid? ParentId { get; set; }
    public List<MediaFolder> Children { get; set; } = new();
}
```

## Storage Architecture

### Storage Provider Abstraction

```csharp
public interface IMediaStorageProvider
{
    string ProviderName { get; }

    Task<string> UploadAsync(Stream stream, string path, string contentType);
    Task<Stream> DownloadAsync(string path);
    Task DeleteAsync(string path);
    Task<bool> ExistsAsync(string path);
    string GetPublicUrl(string path);
}

// Azure Blob Storage
public class AzureBlobStorageProvider : IMediaStorageProvider
{
    public string ProviderName => "AzureBlob";

    public async Task<string> UploadAsync(
        Stream stream, string path, string contentType)
    {
        var blobClient = _containerClient.GetBlobClient(path);

        await blobClient.UploadAsync(stream, new BlobHttpHeaders
        {
            ContentType = contentType,
            CacheControl = "public, max-age=31536000"
        });

        return path;
    }

    public string GetPublicUrl(string path)
    {
        return $"{_containerClient.Uri}/{path}";
    }
}

// AWS S3
public class S3StorageProvider : IMediaStorageProvider
{
    public string ProviderName => "S3";

    public async Task<string> UploadAsync(
        Stream stream, string path, string contentType)
    {
        var request = new PutObjectRequest
        {
            BucketName = _bucketName,
            Key = path,
            InputStream = stream,
            ContentType = contentType,
            CannedACL = S3CannedACL.PublicRead
        };

        await _s3Client.PutObjectAsync(request);
        return path;
    }
}

// Local file system
public class LocalStorageProvider : IMediaStorageProvider
{
    public string ProviderName => "Local";

    public async Task<string> UploadAsync(
        Stream stream, string path, string contentType)
    {
        var fullPath = Path.Combine(_basePath, path);
        Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);

        await using var fileStream = File.Create(fullPath);
        await stream.CopyToAsync(fileStream);

        return path;
    }
}
```

### Path Generation

```csharp
public class MediaPathGenerator
{
    public string GeneratePath(string fileName, PathStrategy strategy)
    {
        var ext = Path.GetExtension(fileName);
        var name = Path.GetFileNameWithoutExtension(fileName);
        var safeName = Slugify(name);

        return strategy switch
        {
            PathStrategy.DateBased => $"{DateTime.UtcNow:yyyy/MM/dd}/{safeName}-{Guid.NewGuid():N}{ext}",
            PathStrategy.HashBased => $"{ComputeHash(fileName)[..2]}/{ComputeHash(fileName)[2..4]}/{Guid.NewGuid():N}{ext}",
            PathStrategy.Flat => $"{Guid.NewGuid():N}{ext}",
            PathStrategy.OriginalName => $"{safeName}-{DateTime.UtcNow:yyyyMMddHHmmss}{ext}",
            _ => throw new ArgumentOutOfRangeException()
        };
    }
}

public enum PathStrategy
{
    DateBased,      // 2025/01/15/image-abc123.jpg
    HashBased,      // ab/cd/abc123.jpg
    Flat,           // abc123.jpg
    OriginalName    // my-image-20250115103045.jpg
}
```

## Upload Pipeline

### Upload Service

```csharp
public class MediaUploadService
{
    public async Task<MediaItem> UploadAsync(
        Stream stream,
        string fileName,
        string contentType,
        UploadOptions? options = null)
    {
        options ??= new UploadOptions();

        // Validate
        ValidateFile(fileName, contentType, stream.Length, options);

        // Generate path
        var path = _pathGenerator.GeneratePath(fileName, options.PathStrategy);

        // Process (resize, optimize)
        var processedStream = await ProcessMediaAsync(stream, contentType, options);

        // Upload to storage
        var storagePath = await _storageProvider.UploadAsync(
            processedStream, path, contentType);

        // Extract metadata
        var metadata = await ExtractMetadataAsync(processedStream, contentType);

        // Create record
        var mediaItem = new MediaItem
        {
            Id = Guid.NewGuid(),
            FileName = fileName,
            Extension = Path.GetExtension(fileName),
            MimeType = contentType,
            SizeBytes = processedStream.Length,
            StorageProvider = _storageProvider.ProviderName,
            StoragePath = storagePath,
            PublicUrl = _storageProvider.GetPublicUrl(storagePath),
            FolderId = options.FolderId,
            Tags = options.Tags ?? new List<string>(),
            Metadata = metadata,
            UploadedBy = _currentUser.UserId,
            UploadedUtc = DateTime.UtcNow
        };

        await _repository.AddAsync(mediaItem);

        // Raise event
        await _mediator.Publish(new MediaUploadedEvent(mediaItem));

        return mediaItem;
    }

    private void ValidateFile(
        string fileName, string contentType, long size, UploadOptions options)
    {
        // Check file size
        if (size > options.MaxFileSizeBytes)
            throw new MediaValidationException($"File exceeds maximum size of {options.MaxFileSizeBytes} bytes");

        // Check allowed types
        if (options.AllowedMimeTypes?.Any() == true &&
            !options.AllowedMimeTypes.Contains(contentType))
            throw new MediaValidationException($"File type {contentType} is not allowed");

        // Check extension
        var ext = Path.GetExtension(fileName).ToLowerInvariant();
        if (options.BlockedExtensions?.Contains(ext) == true)
            throw new MediaValidationException($"File extension {ext} is blocked");
    }
}

public class UploadOptions
{
    public Guid? FolderId { get; set; }
    public List<string>? Tags { get; set; }
    public PathStrategy PathStrategy { get; set; } = 

Related in General