package bucket import ( "errors" "fmt" "mime" "net/url" "sort" "strings" ) type Preview string const previewPrefix = "preview/" var ErrorInvalidPreview = errors.New("invalid preview") func (p Preview) Validate() error { if !strings.HasPrefix(string(p), previewPrefix) { return fmt.Errorf("%w: %v", ErrorInvalidPreview, p) } return nil } func (p Preview) String() string { return string(p) } func (p Preview) Path() string { u := url.URL{ Path: string(p), } return u.String() } func (p Preview) Format() PhotoFormat { extIndex := strings.LastIndex(string(p), ".") return PhotoFormat(mime.TypeByExtension(string(p)[extIndex:])) } type PreviewOption struct { Height int //Width int Format PhotoFormat Quality int } type PhotoFormat string const ( PhotoFormatJPEG PhotoFormat = "image/jpeg" PhotoFormatWEBP PhotoFormat = "image/webp" PhotoFormatHEIF PhotoFormat = "image/heif" PhotoFormatPNG PhotoFormat = "image/png" PhotoFormatGIF PhotoFormat = "image/gif" PhotoFormatTIFF PhotoFormat = "image/tiff" ) func (f PhotoFormat) ContentType() string { return string(f) } var SupportedFormats = []PhotoFormat{ PhotoFormatJPEG, PhotoFormatWEBP, PhotoFormatHEIF, PhotoFormatPNG, PhotoFormatGIF, PhotoFormatTIFF, } type PhotoQualityLevel int const ( PhotoQualityLevelThumbnail PhotoQualityLevel = 0 PhotoQualityLevelBasic PhotoQualityLevel = 1 PhotoQualityLevelNormal PhotoQualityLevel = 2 PhotoQualityLevelExtreme PhotoQualityLevel = 3 ) func (f PhotoFormat) Quality(level PhotoQualityLevel) int { if f == PhotoFormatJPEG || f == PhotoFormatWEBP { return 34 + int(level)*18 } if f == PhotoFormatHEIF { return 10 + int(level)*10 } return 0 } func (p Photo) GetPreview(option PreviewOption) Preview { objectBase := strings.Replace(string(p), photoPrefix, previewPrefix, 1) extIndex := strings.LastIndex(objectBase, ".") base := objectBase[:extIndex] res := fmt.Sprintf("_h%dq%d", option.Height, option.Quality) exts, err := mime.ExtensionsByType(option.Format.ContentType()) if err != nil { panic(err) } sort.Strings(exts) if len(exts) < 1 { panic("missing MIME type to extension mapping, please configure within your operating system") } return Preview(base + res + exts[len(exts)-1]) } var defaultSizes = []int{640, 320, 160, 80} var defaultSizesThumb = []int{60, 30} var defaultFormats = []PhotoFormat{ PhotoFormatWEBP, PhotoFormatJPEG, } func DefaultPreviewOptions() []PreviewOption { options := make([]PreviewOption, 0) for _, size := range defaultSizes { for _, format := range defaultFormats { options = append(options, PreviewOption{ Height: size, Format: format, Quality: format.Quality(PhotoQualityLevelNormal), }) } } for _, size := range defaultSizesThumb { for _, format := range defaultFormats { options = append(options, PreviewOption{ Height: size, Format: format, Quality: format.Quality(PhotoQualityLevelThumbnail), }) } } return options }