xiaohongshu
xiaohongshu
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.
Full skill instructions
Guidance for designing digital asset management systems, media libraries, and upload pipelines for headless CMS.
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();
}
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;
}
}
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
}
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; }
}
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() ?? "";
}
}
}
}
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));
}
}
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;
}
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
{
"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"
}
}
image-optimization - Image processing and optimizationcdn-media-delivery - CDN configuration and deliverycontent-type-modeling - Media fields in content typesxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience