像365din一样的欧美视频,一个Go语言爱好者的追剧手记
- NBA
- 2026-06-29 13:39:30
- 244
说实话,我第一次接触到“像365din一样的欧美视频”这个说法时,还以为是哪个新出的流媒体平台,后来搞明白了,365din是一种视频文件命名规范的代称——那种带编号、带分辨率、甚至带压制组信息的文件命名方式,我这些年用Go写了不少视频处理的小工具,从文件重命名到元数据提取,走过一些弯路,也攒了一些经验,今天就把这些心得摊开来聊聊。
为什么视频文件命名这么重要?
你肯定遇到过这种情况:下载了一堆欧美电影或剧集,结果文件名是一堆乱码,或者干脆就是“video_001.mp4”这种毫无信息量的名字,这时候你想找某部特定剧集的某一集,基本等于大海捞针。
我刚开始做视频整理时,用的还是最笨的办法——手动改名,几十个文件,一个一个改名,手指都按酸了,后来我琢磨着,能不能用Go写个批量重命名工具?
一个失败的尝试
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func main() {
files, _ := filepath.Glob("*.mp4")
for i, file := range files {
newName := fmt.Sprintf("video_%03d.mp4", i+1)
os.Rename(file, newName)
}
}
这段代码看似简单,但问题大了——它把所有文件都改成了“video_001.mp4”这种毫无区分度的名字,如果要找《权力的游戏》第三季第四集,根本无从下手。
像365din一样的命名规范
后来我研究了365din这套命名逻辑,发现它其实很科学,一个典型的365din风格文件名长这样:
Game.of.Thrones.S03E04.1080p.BluRay.x264-DIMENSION
看懂了吗?分解开就是:
- 剧名:Game.of.Thrones
- 季数与集数:S03E04
- 分辨率:1080p
- 来源:BluRay
- 编码格式:x264
- 压制组:DIMENSION
这套命名规范的好处是:一眼就能知道视频的基本信息,而且方便搜索和排序。
用Go实现智能重命名
我第二个版本的工具,用Go的正则表达式从原始文件中提取信息,这过程说起来轻松,写起来可把我折腾得够呛。
第一步:解析文件名
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)
type VideoInfo struct {
OriginalName string
NewName string
Season int
Episode int string
Resolution string
Codec string
Source string
}
func parseFileName(filename string) (VideoInfo, error) {
// 匹配像 "show.name.s01e02.1080p.bluray.x264-group" 这样的格式
re := regexp.MustCompile(`(?i)(.+?)\.s(\d+)e(\d+)\.(\d+p)\.(bluray|web|hdtv)\.(x264|x265)`)
matches := re.FindStringSubmatch(filename)
if len(matches) < 7 {
return VideoInfo{}, fmt.Errorf("无法解析文件名: %s", filename)
}
info := VideoInfo{
OriginalName: filename,
Title: strings.Replace(matches[1], ".", " ", -1),
Season: parseInt(matches[2]),
Episode: parseInt(matches[3]),
Resolution: matches[4],
Source: strings.ToUpper(matches[5]),
Codec: matches[6],
}
return info, nil
}
这个正则表达式花了我一个周末才调通,刚开始总是匹配不上,后来发现是因为文件名中有时候用下划线,有时候用点号,还有时候用空格,我干脆把所有分隔符都转成点号再统一处理。
第二步:输出标准化文件名
func generateStandardName(info VideoInfo) string {
return fmt.Sprintf("%s.S%02dE%02d.%s.%s.%s",
strings.Replace(info.Title, " ", ".", -1),
info.Season,
info.Episode,
info.Resolution,
info.Source,
info.Codec)
}
这样输出出来的文件名就是标准格式了,比如原文件是“Thrones_S3_E4_1080p_BluRay_x264.mp4”,就会被重命名为“Game.Of.Thrones.S03E04.1080p.BluRay.x264.mp4”。
处理不同来源的欧美视频
欧美视频的来源五花八门,最让我头疼的是处理不同压制组命名的差异,我整理了一个表格,算是这些年踩坑的总结:
| 来源类型 | 常见命名特征 | Go正则匹配示例 | 处理建议 |
|---|---|---|---|
| 蓝光原盘 | 包含BluRay字样,通常带REMUX标记 | (?i)(bluray|remux) |
推荐保留原画质信息 |
| WEB-DL | 带WEB-DL或NF、AMZN等平台代码 | (?i)(web-dl|amzn|netflix) |
注意区分HDR和SDR版本 |
| HDTV录制 | 通常带HDTV字样,有些带电视台标志 | (?i)(hdtv|pdtv) |
音轨质量需要留意 |
| 压缩组作品 | 文件名末尾有压制组缩写 | (?i)-([a-z0-9]+)$ |
影视爱好者通常要保留压制组信息 |
我用Go写了个函数,能自动识别这些来源:
func detectSource(filename string) string {
lower := strings.ToLower(filename)
switches := map[string]string{
"bluray": "BluRay",
"web-dl": "WEB-DL",
"webdl": "WEB-DL",
"hdtv": "HDTV",
"remux": "REMUX",
}
for key, value := range switches {
if strings.Contains(lower, key) {
return value
}
}
return "Unknown"
}
元数据提取的坑与经验
重命名只是第一步,真正让我体会到编程乐趣(和痛苦)的是提取视频文件内部的元数据。
有一次我下载了一批纪录片,文件名全都正常,但打开看发现很多集的内容完全对不上——原来是元数据写错了,我当时的工具依赖文件名来排序,结果排序乱七八糟。
用FFmpeg解析元数据
Go语言本身没有直接解析视频元数据的库,但我们可以调用FFmpeg命令行工具:
package main
import (
"encoding/json"
"fmt"
"os/exec"
"strings"
)
type FFProbeOutput struct {
Format struct {
Duration string `json:"duration"`
Size string `json:"size"`
BitRate string `json:"bit_rate"`
Tags map[string]string `json:"tags"`
} `json:"format"`
}
func getVideoMetadata(videoPath string) (*FFProbeOutput, error) {
cmd := exec.Command("ffprobe",
"-v", "quiet",
"-print_format", "json",
"-show_format",
videoPath)
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("ffprobe执行失败: %v", err)
}
var metadata FFProbeOutput
if err := json.Unmarshal(output, &metadata); err != nil {
return nil, fmt.Errorf("JSON解析失败: %v", err)
}
return &metadata, nil
}
这段代码能提取出视频的时长、文件大小、码率等信息,但最有用的是Tags字段——里面包含了标题、艺术家、专辑等信息。
一个让我印象深刻的bug
有一次我写了个批量处理脚本,跑完发现名全改乱了,排查后发现是文件名中包含了特殊字符,比如和,它们在文件名中是非法的,Go的os.Rename函数会直接报错,而我的代码里没有处理这个异常。
后来我加了个清理函数:
func sanitizeFilename(name string) string {
// 替换不合法字符
illegal := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"}
for _, char := range illegal {
name = strings.ReplaceAll(name, char, "")
}
return name
}
这个小改动,让我少了好几个运维电话。
实践中的经验与教训
写了这么多工具,我最大的感悟是:做这个事情的最终目的是为了更好观看,不是为了整理而整理。
有些时候,文件名已经够清楚了,就别再瞎改,比如你的文件本来就是按照某种规范命名,乱改反而会把元数据搞丢。
一个小技巧
我发现用Go的goroutine做并发处理时,能大幅提升大规模整理的速度,但要注意不要同时对同一个文件做读写操作,否则会出乱子。
func processFilesConcurrently(files []string) {
results := make(chan string, len(files))
sem := make(chan struct{}, 4) // 限制并发数,别把CPU跑满
for _, file := range files {
go func(f string) {
sem <- struct{}{}
defer func() { <-sem }()
// 处理逻辑
info, err := parseFileName(f)
if err != nil {
results <- fmt.Sprintf("错误: %s - %v", f, err)
return
}
results <- fmt.Sprintf("成功: %s -> %s", f, generateStandardName(info))
}(file)
}
for i := 0; i < len(files); i++ {
fmt.Println(<-results)
}
}
这样写,处理一百个文件也就是几秒钟的事,不过要注意,不要在并发里修改同一个map,不然会panic。
始终没有完美的解决方案
到现在,我那个Go工具已经迭代了十几次,每次觉得完善了,总会遇到新的情况——比如有人分享的视频文件名完全是随机字符串,根本提取不出任何信息,这时候我只好手动补信息,然后在程序里打上TODO标记。
我也试过用机器学习识别画面内容来提取信息,但效果不太好,电影片头识别准确率还行,电视剧就一塌糊涂——因为很多片头是反复出现的,模型容易混淆。
所以最后我放弃了完美主义,现在的策略是:能自动处理的就自动处理,处理不了的标记出来,我来手动补,这样既节约时间,又不会出错。
眼下这个工具还在我的GitHub上躺着,只有我一个人在用,偶尔朋友问起来,我会告诉他们怎么用Go写个视频整理工具,然后看着他们也被正则表达式和文件名混乱折磨的样子,嘿嘿笑两声,毕竟,每个折腾过这些的人,都懂那种——以为写完了却发现还有一堆边界情况没处理的感觉,但话说回来,当你的剧集整齐排列在文件夹里,用电视一路播下去的时候,那感觉又挺好的。
