media-asset-management

Media Asset Management

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "media-asset-management" with this command: npx skills add melodic-software/claude-code-plugins/melodic-software-claude-code-plugins-media-asset-management

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&#x3C;string> UploadAsync(Stream stream, string path, string contentType);
Task&#x3C;Stream> DownloadAsync(string path);
Task DeleteAsync(string path);
Task&#x3C;bool> ExistsAsync(string path);
string GetPublicUrl(string path);

}

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

public async Task&#x3C;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&#x3C;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&#x3C;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&#x3C;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 &#x26;&#x26;
        !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&#x3C;List&#x3C;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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

design-thinking

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

plantuml-syntax

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

system-prompt-engineering

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

resume-optimization

No summary provided by upstream source.

Repository SourceNeeds Review