// internal/media/prepare/prepare.go package prepare import ( "bytes" "fmt" "image" "image/gif" "image/jpeg" "image/png" "io" "net/http" "os" "path/filepath" "strings" "golang.org/x/image/webp" "go.uber.org/zap" ) // ImageDimensions represents the dimensions of an image type ImageDimensions struct { Width int Height int } // Manager handles media preparation and optimization type Manager struct { logger *zap.Logger } // NewManager creates a new preparation manager func NewManager(logger *zap.Logger) *Manager { if logger == nil { // Create a default logger if none is provided var err error logger, err = zap.NewProduction() if err != nil { // If we can't create a logger, use a no-op logger logger = zap.NewNop() } } return &Manager{ logger: logger, } } // GetMediaDimensions gets the dimensions of an image or video file func (m *Manager) GetMediaDimensions(filePath string) (*ImageDimensions, error) { // Open the file file, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("failed to open file: %w", err) } defer file.Close() // Get the file extension ext := strings.ToLower(filepath.Ext(filePath)) // Decode the image based on the file extension var img image.Image switch ext { case ".jpg", ".jpeg": img, err = jpeg.Decode(file) case ".png": img, err = png.Decode(file) case ".gif": img, err = gif.Decode(file) case ".webp": img, err = webp.Decode(file) default: return nil, fmt.Errorf("unsupported image format: %s", ext) } if err != nil { return nil, fmt.Errorf("failed to decode image: %w", err) } // Get the dimensions bounds := img.Bounds() return &ImageDimensions{ Width: bounds.Max.X - bounds.Min.X, Height: bounds.Max.Y - bounds.Min.Y, }, nil } // ResizeImage resizes an image to the specified dimensions // Returns the path to the resized image func (m *Manager) ResizeImage(filePath string, maxWidth, maxHeight int) (string, error) { // For now, just return the original image // In a future update, we can add actual resizing functionality return filePath, nil } // OptimizeImage optimizes an image for web usage // Returns the path to the optimized image func (m *Manager) OptimizeImage(filePath string) (string, error) { // For now, just return the original image // In a future update, we can add optimization functionality return filePath, nil } // GetMediaType gets the media type of a file func (m *Manager) GetMediaType(filePath string) (string, error) { // Open the file file, err := os.Open(filePath) if err != nil { return "", fmt.Errorf("failed to open file: %w", err) } defer file.Close() // Read the first 512 bytes to detect the content type buffer := make([]byte, 512) _, err = file.Read(buffer) if err != nil && err != io.EOF { return "", fmt.Errorf("failed to read file: %w", err) } // Detect the content type contentType := detectContentType(buffer) return contentType, nil } // ExtractMetadata extracts metadata from a media file func (m *Manager) ExtractMetadata(filePath string) (map[string]interface{}, error) { // Get the media type mediaType, err := m.GetMediaType(filePath) if err != nil { return nil, fmt.Errorf("failed to get media type: %w", err) } // Create the metadata map metadata := map[string]interface{}{ "media_type": mediaType, } // If it's an image, get the dimensions if strings.HasPrefix(mediaType, "image/") { dims, err := m.GetMediaDimensions(filePath) if err != nil { m.logger.Warn("Failed to get image dimensions", zap.String("filePath", filePath), zap.Error(err)) } else { metadata["width"] = dims.Width metadata["height"] = dims.Height metadata["dimensions"] = fmt.Sprintf("%dx%d", dims.Width, dims.Height) } } // Get the file size fileInfo, err := os.Stat(filePath) if err != nil { m.logger.Warn("Failed to get file size", zap.String("filePath", filePath), zap.Error(err)) } else { metadata["size"] = fileInfo.Size() } return metadata, nil } // detectContentType detects the content type of a file func detectContentType(buffer []byte) string { // Try to detect the content type contentType := http.DetectContentType(buffer) // Some additional checks for specific formats if contentType == "application/octet-stream" { // Check for WebP signature if bytes.HasPrefix(buffer, []byte("RIFF")) && bytes.Contains(buffer[8:12], []byte("WEBP")) { return "image/webp" } } return contentType }