181 lines
4.4 KiB
Go

// 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
}