Skip to content
media-asset-management logo

Media Asset Management

media-asset-management

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.

SKILL.md

Full skill instructions

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

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

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

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

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; } = PathStrategy.DateBased;
    public long MaxFileSizeBytes { get; set; } = 10 * 1024 * 1024; // 10MB
    public List<string>? AllowedMimeTypes { get; set; }
    public List<string>? BlockedExtensions { get; set; }
    public bool ExtractMetadata { get; set; } = true;
    public ImageProcessingOptions? ImageOptions { get; set; }
}

Metadata Extraction

public class MetadataExtractor
{
    public async Task<MediaMetadata> ExtractAsync(Stream stream, string contentType)
    {
        var metadata = new MediaMetadata();

        if (contentType.StartsWith("image/"))
        {
            await ExtractImageMetadataAsync(stream, metadata);
        }
        else if (contentType.StartsWith("video/"))
        {
            await ExtractVideoMetadataAsync(stream, metadata);
        }
        else if (contentType == "application/​pdf")
        {
            await ExtractPdfMetadataAsync(stream, metadata);
        }

        return metadata;
    }

    private async Task ExtractImageMetadataAsync(Stream stream, MediaMetadata metadata)
    {
        using var image = await Image.LoadAsync(stream);

        metadata.Width = image.Width;
        metadata.Height = image.Height;

        // Extract EXIF
        if (image.Metadata.ExifProfile != null)
        {
            foreach (var value in image.Metadata.ExifProfile.Values)
            {
                metadata.ExifData[value.Tag.ToString()] = value.GetValue()?.ToString() ?? "";
            }
        }
    }
}

Media Library Features

Folder Management

public class MediaFolderService
{
    public async Task<MediaFolder> CreateFolderAsync(string name, Guid? parentId = null)
    {
        var folder = new MediaFolder
        {
            Id = Guid.NewGuid(),
            Name = name,
            ParentId = parentId,
            Path = await BuildPathAsync(name, parentId)
        };

        await _repository.AddAsync(folder);
        return folder;
    }

    public async Task<List<MediaFolder>> GetFolderTreeAsync()
    {
        var folders = await _repository.GetAllAsync();
        return BuildTree(folders.Where(f => f.ParentId == null));
    }
}

Media Search

public class MediaSearchService
{
    public async Task<PagedResult<MediaItem>> SearchAsync(MediaSearchQuery query)
    {
        var queryable = _context.MediaItems.AsQueryable();

        // Filter by folder
        if (query.FolderId.HasValue)
        {
            queryable = queryable.Where(m => m.FolderId == query.FolderId);
        }

        // Filter by type
        if (!string.IsNullOrEmpty(query.MediaType))
        {
            queryable = query.MediaType switch
            {
                "image" => queryable.Where(m => m.MimeType.StartsWith("image/")),
                "video" => queryable.Where(m => m.MimeType.StartsWith("video/")),
                "document" => queryable.Where(m =>
                    m.MimeType == "application/​pdf" ||
                    m.MimeType.Contains("document")),
                _ => queryable
            };
        }

        // Filter by tags
        if (query.Tags?.Any() == true)
        {
            queryable = queryable.Where(m =>
                query.Tags.All(t => m.Tags.Contains(t)));
        }

        // Search text
        if (!string.IsNullOrEmpty(query.SearchText))
        {
            var search = query.SearchText.ToLower();
            queryable = queryable.Where(m =>
                m.FileName.ToLower().Contains(search) ||
                m.Metadata.Title!.ToLower().Contains(search) ||
                m.Metadata.Description!.ToLower().Contains(search));
        }

        // Apply sorting
        queryable = query.SortBy switch
        {
            "name" => queryable.OrderBy(m => m.FileName),
            "date" => queryable.OrderByDescending(m => m.UploadedUtc),
            "size" => queryable.OrderByDescending(m => m.SizeBytes),
            _ => queryable.OrderByDescending(m => m.UploadedUtc)
        };

        return await queryable.ToPagedResultAsync(query.Page, query.PageSize);
    }
}

public class MediaSearchQuery
{
    public Guid? FolderId { get; set; }
    public string? MediaType { get; set; }
    public List<string>? Tags { get; set; }
    public string? SearchText { get; set; }
    public string? SortBy { get; set; }
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 20;
}

Media API

Endpoints

POST   /​api/​media/​upload              # Upload single file
POST   /​api/​media/​upload/​bulk         # Bulk upload
GET    /​api/​media                     # List/​search media
GET    /​api/​media/​{id}                # Get media item
DELETE /​api/​media/​{id}                # Delete media
PATCH  /​api/​media/​{id}                # Update metadata

# Folders
GET    /​api/​media/​folders             # Get folder tree
POST   /​api/​media/​folders             # Create folder
DELETE /​api/​media/​folders/​{id}        # Delete folder

Media Response

{
  "data": {
    "id": "media-123",
    "fileName": "hero-image.jpg",
    "mimeType": "image/​jpeg",
    "sizeBytes": 245678,
    "url": "https://cdn.example.com/​media/​2025/​01/​15/​hero-image-abc123.jpg",
    "metadata": {
      "title": "Homepage Hero",
      "alt": "Team working together",
      "width": 1920,
      "height": 1080
    },
    "folder": {
      "id": "folder-456",
      "name": "Homepage",
      "path": "/​Marketing/​Homepage"
    },
    "tags": ["hero", "homepage", "team"],
    "uploadedBy": "user-789",
    "uploadedUtc": "2025-01-15T10:30:00Z"
  }
}

Related Skills

  • image-optimization - Image processing and optimization
  • cdn-media-delivery - CDN configuration and delivery
  • content-type-modeling - Media fields in content types

More skills from melodic-software

gemini-explore logo
melodic-software/claude-code-plugins

gemini-explore

Explore entire codebase with Gemini's large context window. Use when starting on unfamiliar codebases or mapping dependencies across large projects.

22 0
View
document-extraction logo
melodic-software/claude-code-plugins

document-extraction

Extract requirements from existing documents including PDFs, Word docs, meeting transcripts, specifications, and web content. Identifies requirement candidates, categorizes them, and outputs in pre-canonical format.

22 0
View
etl-elt-patterns logo
melodic-software/claude-code-plugins

etl-elt-patterns

Use when designing data pipelines, choosing between ETL and ELT approaches, or implementing data transformation patterns. Covers modern data pipeline architecture.

22 0
View
ship logo
melodic-software/claude-code-plugins

ship

Validate state and merge branch to main for production deployment. Use as final ZTE step after all checks pass.

22 0
View
agent-sdk-development logo
melodic-software/claude-code-plugins

agent-sdk-development

Central authority for Claude Agent SDK (TypeScript and Python SDKs). Covers SDK installation, authentication (Anthropic key, Bedrock, Vertex), sessions and resumption, forking sessions, streaming vs single mode, custom tools, permissions (allowedTools, disallowedTools, permissionMode), MCP integr...

22 0
View
ba-orchestration logo
melodic-software/claude-code-plugins

ba-orchestration

Multi-technique business analysis orchestration using BABOK. Coordinates 14 techniques across strategic analysis, problem solving, planning, and design into comprehensive analysis packages.

22 0
View
system-diagnostics logo
melodic-software/claude-code-plugins

system-diagnostics

Comprehensive Windows 11 system diagnostics via PowerShell. Diagnoses crashes, freezes, reboots, BSOD, disk health, memory issues, hardware errors, and performance problems. Use when troubleshooting Windows stability issues, analyzing Event Viewer logs, checking disk/memory health, investigating ...

22 0
View
commit-with-agent logo
melodic-software/claude-code-plugins

commit-with-agent

Create a Git commit with agent attribution for ADW workflows. Use after implementing changes to create semantically formatted commits.

22 0
View
cqrs-architecture logo
melodic-software/claude-code-plugins

cqrs-architecture

CQRS pattern implementation and query optimization

22 0
View
vector-databases logo
melodic-software/claude-code-plugins

vector-databases

Vector database selection, embedding storage, approximate nearest neighbor (ANN) algorithms, and vector search optimization. Use when choosing vector stores, designing semantic search, or optimizing similarity search performance.

22 0
View
test-case-design logo
melodic-software/claude-code-plugins

test-case-design

Apply systematic test case design techniques including equivalence partitioning, boundary value analysis, decision tables, and state transition testing.

22 0
View
analyze-transcript logo
melodic-software/claude-code-plugins

analyze-transcript

Analyze meeting transcripts to extract requirements, decisions, and action items. Specialized extraction for conversational content.

22 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View