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.
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
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.