83 lines
2.2 KiB
Go
83 lines
2.2 KiB
Go
package media
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
maxSeriesBytes = 90
|
|
maxEpisodeBytes = 140
|
|
maxEpisodeIDBytes = 64
|
|
maxFilenameBytes = 255
|
|
)
|
|
|
|
// RelativeVideoPath は worker と web が共有する保存先の相対パスを返す。
|
|
func RelativeVideoPath(seriesTitle, episodeTitle, episodeID string) (string, error) {
|
|
series := sanitizeComponent(seriesTitle, maxSeriesBytes)
|
|
episode := sanitizeComponent(episodeTitle, maxEpisodeBytes)
|
|
id := sanitizeComponent(episodeID, -1)
|
|
if len(id) > maxEpisodeIDBytes {
|
|
return "", fmt.Errorf("エピソードIDはサニタイズ後%dバイト以下である必要があります: %dバイト", maxEpisodeIDBytes, len(id))
|
|
}
|
|
|
|
// A directory entry must fit within the NFS namlen limit. Keep the episode
|
|
// ID intact and use the remaining bytes for the episode title.
|
|
const separators = " [].mp4"
|
|
seriesInFilename := series
|
|
titleBytes := maxFilenameBytes - len(seriesInFilename) - len(id) - len(separators)
|
|
if titleBytes < 0 {
|
|
titleBytes = 0
|
|
}
|
|
episode = truncateComponent(episode, titleBytes, false)
|
|
return filepath.Join(series, seriesInFilename+" "+episode+" ["+id+"].mp4"), nil
|
|
}
|
|
|
|
func sanitizeComponent(value string, maxBytes int) string {
|
|
var result []rune
|
|
lastReplacement := false
|
|
for _, r := range value {
|
|
invalid := unicode.IsControl(r) || strings.ContainsRune(`<>:"/\|?*`, r)
|
|
if invalid {
|
|
if !lastReplacement {
|
|
result = append(result, '_')
|
|
lastReplacement = true
|
|
}
|
|
continue
|
|
}
|
|
if unicode.IsSpace(r) {
|
|
r = ' '
|
|
}
|
|
result = append(result, r)
|
|
lastReplacement = false
|
|
}
|
|
clean := strings.Trim(string(result), " .")
|
|
if clean == "" || clean == "." || clean == ".." {
|
|
return "名称未設定"
|
|
}
|
|
return truncateComponent(clean, maxBytes, true)
|
|
}
|
|
|
|
func truncateComponent(value string, maxBytes int, fallback bool) string {
|
|
if maxBytes < 0 {
|
|
return value
|
|
}
|
|
var limited []rune
|
|
bytes := 0
|
|
for _, r := range value {
|
|
size := len(string(r))
|
|
if bytes+size > maxBytes {
|
|
break
|
|
}
|
|
limited = append(limited, r)
|
|
bytes += size
|
|
}
|
|
clean := strings.Trim(string(limited), " .")
|
|
if fallback && (clean == "" || clean == "." || clean == "..") {
|
|
return "名称未設定"
|
|
}
|
|
return clean
|
|
}
|