Files
kenaito-media-picker/internal/classify/classify.go
T

156 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package classify 负责给媒体文件定"分辨率档位",决定它该进哪个分类文件夹。
//
// 大白话理解,就是定分类命名规则:
// - 画面是横的还是竖的?(横屏 / 竖屏 / 正方形)
// - 清晰度算哪个档?(720P / 1080P / 4K…;不标准的按长边大小给个约数,如 5K)
// - 图片还要看是暗调还是亮调(dark / mid / bright),作为子目录
//
// 最终拼出一个文件夹名,比如:
// - 「图片_横屏_1080P/bright_20」 一张横向、1080P、偏亮的图片
// - 「视频_竖屏_4k」 一个竖向、4K 的视频
//
// 这个包全是"纯函数"——不依赖任何状态,给什么算什么,好理解也好测。
package classify
import (
"fmt"
"strings"
)
// ============================ 画面方向 ============================
// Orientation 画面方向。
type Orientation int
const (
OrientationSquare Orientation = iota // 正方形(宽 = 高)
OrientationHorizontal // 横屏(宽 > 高)
OrientationVertical // 竖屏(宽 < 高)
)
// OrientationOf 根据宽高判断画面是横是竖还是正方。
func OrientationOf(width, height int) Orientation {
switch {
case width > height:
return OrientationHorizontal
case width < height:
return OrientationVertical
default:
return OrientationSquare
}
}
// String 把方向转成中文名,方便拼进文件夹名和前端展示。
func (o Orientation) String() string {
switch o {
case OrientationHorizontal:
return "横屏"
case OrientationVertical:
return "竖屏"
default:
return "等比"
}
}
// ============================ 归类目录名 ============================
// ImageDir 返回一张图片该进的文件夹路径,比如:
// - 「图片_GIF」:GIF 动图单独放(不做明暗分类)
// - 「图片_横屏_1080P/bright_20」:普通图片 = 方向 + 清晰度,明暗作为其子目录
func ImageDir(ext string, width, height int, darkScore, lightScore float64) string {
if strings.EqualFold(ext, ".gif") {
return "图片_GIF"
}
dir := resolutionDir("图片", ext, width, height)
return dir + "/" + BrightnessName(darkScore, lightScore)
}
// VideoDir 返回一个视频该进的文件夹名,比如「视频_竖屏_4k」「视频_横屏_GIF」。
func VideoDir(ext string, width, height int) string {
return resolutionDir("视频", ext, width, height)
}
// resolutionDir 拼出"类型 + 方向 + 清晰度"这段目录名。
// 步骤:先看宽高是横是竖;再匹配标准清晰度(720P/1080P…),
// 匹配不上就按长边大小给个约数(普通 / 5K / 原画质)。
func resolutionDir(kind, ext string, width, height int) string {
ori := OrientationOf(width, height).String()
// GIF 特殊:不管多清楚,都归「_GIF」
if strings.EqualFold(ext, ".gif") {
return kind + "_" + ori + "_GIF"
}
// 先看是不是标准清晰度(精确匹配宽高)
if suffix := matchStandard(width, height); suffix != "" {
return kind + "_" + ori + "_" + suffix
}
// 不是标准清晰度:按长边给约数。图片叫"原图",视频叫"原画质"(超出档位时用)
overflow := "原图"
if kind == "视频" {
overflow = "原画质"
}
return kind + "_" + ori + "_" + levelName(longEdge(width, height), overflow)
}
// longEdge 返回宽和高中较长的那条边(像素)。
func longEdge(w, h int) int {
if w > h {
return w
}
return h
}
// ============================ 分辨率档位(给前端筛选/统计用) ============================
// ResLabel 把一个宽高翻译成"分辨率档位标签",例如:
// - 1920×1080 → "1080P"(标准档直接叫标准名)
// - 2560×1440 → "2D5KP"
// - 1024×768 → "普通"
// - 5120×2880 → "5K"(约数)
// - 20000×10000 → "原图"(超出档位上限)
//
// 图片、视频统一用"原图"作为超出档位的标签。宽高无效时返回空串。
func ResLabel(width, height int) string {
if width <= 0 || height <= 0 {
return ""
}
if suffix := matchStandard(width, height); suffix != "" {
return suffix
}
return levelName(longEdge(width, height), "原图")
}
// ResRange 把"分辨率档位标签"翻译回具体的尺寸范围,给数据库查 SQL 用。
// 返回四样东西:
// - minLong:长边至少多少像素(-1 表示这个标签不认得)
// - maxLong:长边小于多少像素(-1 表示没有上限)
// - exactW / exactH:标准分辨率的精确宽高(非零表示要精确匹配,不用管范围)
//
// 规则和归类逻辑必须保持一致,否则筛选出来的和实际分类会对不上。
func ResRange(label string) (minLong, maxLong, exactW, exactH int) {
if label == "" {
return -1, -1, 0, 0
}
// 标准档:精确匹配宽高
for _, r := range standardResolutions {
if r.suffix == label {
return 0, 0, r.width, r.height
}
}
// 「普通」:长边不到 1 档(<1024px)
if label == "普通" {
return 0, bucketStep, 0, 0
}
// 「原图」:长边超出最大档(≥20480px),没有上限
if label == "原图" {
return (maxLevel + 1) * bucketStep, -1, 0, 0
}
// 「NK」档(如 5K):长边在第 N 档到第 N+1 档之间
if len(label) > 1 && label[len(label)-1] == 'K' {
var level int
if _, err := fmt.Sscanf(label, "%dK", &level); err == nil && level >= 1 && level <= maxLevel {
return level * bucketStep, (level + 1) * bucketStep, 0, 0
}
}
return -1, -1, 0, 0
}