音视频文件名格式化程序

为了方便仓鼠党整理或者预览媒体资源文件,现将我写的格式化媒体文件名程序,打包成可执行文件分享给大家,这程序应该是有点用吧,支持win10x64、win11x64,其他环境不清楚。

原理:通过golang遍历填入路径下的全部文件,找出媒体文件并通过FFmpeg的ffprobe读取文件的媒体信息,拼接到文件名后。

效果:

图片[1]-音视频文件名格式化程序-死宅屋
图片[2]-音视频文件名格式化程序-死宅屋
图片[3]-音视频文件名格式化程序-死宅屋

要使用必须下载本体和依赖程序,程序本体可以放到任何地方。

打包后程序本体(源码打包后的exe):

d300231969162142.7z
7z文件
1.3M

本体依赖程序(ffmpeg):

直接从GitHub上下载最新的 ffmpeg-master-latest-win64-gpl-shared.zip 文件

https://github.com/BtbN/FFmpeg-Builds/releases

图片[4]-音视频文件名格式化程序-死宅屋

下载解压后目录名重命名成ffmpeg,放到C盘根目录下,对应如下。

图片[5]-音视频文件名格式化程序-死宅屋

然后你就可以执行exe文件,填入执行的目录开始格式化了

已知问题

  • 目录名存在空格闪退 (已修复 2023-8-29 16:21:26)

下面是程序源码,会golang的可以自己改进。

程序源码:

package main

/*
	媒体文件末尾添加描述
*/

import (
	"bufio"
	"encoding/json"
	"fmt"
	mapset "github.com/deckarep/golang-set"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

var ffmpegPath = "C:\\ffmpeg\\bin\\" // ffmpreg目录
var scanRootPath string              // 搜索的目录
// 定义视频文件格式
var videoExt = []interface{}{
	".mp4",
	".mpg",
	".mkv",
	".mpe",
	".mpeg",
	".qt",
	".mov",
	".m4v",
	".wmv",
	".avi",
	".webm",
	".flv",
	".ts",
}

// 定义音频文件格式
var audioExt = []interface{}{
	".mp3",
	".mid",
	".midi",
	".wav",
	".m3u",
	".m4a",
	".ogg",
	".ra",
}

// 判断文件夹或文件是否存在
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// 字节转人类可读
func ByteCountBinary(b int64) string {
	const unit = 1024
	if b < unit {
		return fmt.Sprintf("%dB", b)
	}
	div, exp := int64(unit), 0
	for n := b / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.2f%cB", float64(b)/float64(div), "KMGTPE"[exp])
}

type mediaInfoStruct struct {
	Programs []struct{} `json:"programs"`
	Streams  []struct {
		CodecName    string `json:"codec_name"`
		Width        int    `json:"width"`
		Height       int    `json:"height"`
		RFrameRate   string `json:"r_frame_rate"`
		AvgFrameRate string `json:"avg_frame_rate"`
		BitRate      string `json:"bit_rate"`
	} `json:"streams"`
	Format struct {
		Duration string `json:"duration"`
		Size     string `json:"size"`
		BitRate  string `json:"bit_rate"`
	} `json:"format"`
}

// ResolveTime 将秒转成时分秒格式
func ResolveTime(seconds int64) string {
	var h int
	var (
		m, s string
	)
	var day = seconds / (24 * 3600)
	hour := (seconds - day*3600*24) / 3600
	minute := (seconds - day*24*3600 - hour*3600) / 60
	second := seconds - day*24*3600 - hour*3600 - minute*60
	h = int(hour)

	m = strconv.Itoa(int(minute))
	if minute < 10 {
		m = "0" + strconv.Itoa(int(minute))
	}
	s = strconv.Itoa(int(second))
	if second < 10 {
		s = "0" + strconv.Itoa(int(second))
	}
	if h != 0 {
		return fmt.Sprintf("%d小时%s分%s秒", h, m, s)
	}
	return fmt.Sprintf("%s分%s秒", m, s)
}

// 替换文件名称
func replaceMediaName(oldName string, mediaInfo *mediaInfoStruct) {
	//log.Printf("%s %+v", oldName, mediaInfo)
	// 转换视频大小
	size, _ := strconv.ParseInt(mediaInfo.Format.Size, 10, 64)
	// 转换视频时长
	duration_time, _ := strconv.ParseFloat(mediaInfo.Format.Duration, 64)
	var n_duration_time int64
	n_duration_time = int64(duration_time)

	// 优先使用stream里的比特率
	nbitrate := ""
	if len(mediaInfo.Streams) == 0 {
		log.Println("失败:", oldName, "没有获取到媒体信息")
		return
	}
	if mediaInfo.Streams[0].BitRate != "" {
		nbitrate = mediaInfo.Streams[0].BitRate
	} else {
		nbitrate = mediaInfo.Format.BitRate
	}
	bitrate, _ := strconv.ParseInt(nbitrate, 10, 64)
	bitrate = bitrate / 1000

	// 格式化媒体名称
	var fbl string
	if mediaInfo.Streams[0].Width != 0 && mediaInfo.Streams[0].Height != 0 {
		fbl = fmt.Sprintf("%dx%d,", mediaInfo.Streams[0].Width, mediaInfo.Streams[0].Height)
	}
	// 最详细的名字
	//mediaName := fmt.Sprintf("「%s%s,%s,%sfps,%s,%dkbps」", fbl, ResolveTime(n_duration_time), ByteCountBinary(size), strings.Split(mediaInfo.Streams[0].RFrameRate, "/")[0], mediaInfo.Streams[0].CodecName, bitrate)
	// 简洁名称
	mediaName := fmt.Sprintf("「%s%s,%s」", fbl, ResolveTime(n_duration_time), ByteCountBinary(size))

	// 判断是否已命名
	if strings.Contains(oldName, mediaName) {
		log.Println("已命名跳过", oldName)
		return
	}
	oldName = strings.ReplaceAll(oldName, "\\", "/")
	lastname := path.Base(oldName)
	ext := path.Ext(lastname)
	filename := strings.TrimSuffix(lastname, ext)
	if strings.Contains(filename, "「") {
		filename = strings.Split(filename, "「")[0]
	}

	lastname = fmt.Sprintf("%s%s%s", filename, mediaName, ext)
	replacePath := fmt.Sprintf("%s%s%s", path.Dir(oldName), "/", lastname)
	renameErr := os.Rename(oldName, replacePath)
	if renameErr != nil {
		log.Println("Rename Error:", renameErr.Error())
	}
	log.Println("成功命名", oldName, ">", replacePath)
}

// 扫码媒体并执行信息
func mediaAddDesc(sourcePathname string) {
	sourcePathname = strings.ReplaceAll(sourcePathname, "\\", "/")
	err := filepath.Walk(sourcePathname, func(pathstr string, info os.FileInfo, err error) error {
		if info.IsDir() == false {
			ext := strings.ToLower(path.Ext(info.Name()))
			switch {
			case mapset.NewSetFromSlice(videoExt).Contains(ext):
				// 处理视频文件
				cmd := exec.Command(fmt.Sprintf("%s%s", ffmpegPath, "ffprobe.exe"), "-i", pathstr, "-show_entries", "format=duration,size,bit_rate:stream=codec_name,width,height,avg_frame_rate,r_frame_rate,bit_rate", "-select_streams", "v:0", "-v", "error", "-of", "json")
				out, err := cmd.CombinedOutput()
				if err != nil {
					log.Println(info.Name(), string(out), err.Error())
				}
				// 防止TS文件返回额外的信息,影响json序列号
				stringOut := string(out)
				if strings.Contains(stringOut, "[h264 @ ") {
					stringOut = strings.Split(stringOut, "[h264 @ ")[0]
					out = []byte(stringOut)
				}

				var mediaInfo mediaInfoStruct
				_ = json.Unmarshal(out, &mediaInfo)
				replaceMediaName(pathstr, &mediaInfo)
			case mapset.NewSetFromSlice(audioExt).Contains(ext):
				// 处理音频文件
				cmd := exec.Command(fmt.Sprintf("%s%s", ffmpegPath, "ffprobe.exe"), "-i", pathstr, "-show_entries", "format=duration,size,bit_rate:stream=codec_name,width,height,avg_frame_rate,r_frame_rate,bit_rate", "-select_streams", "a:0", "-v", "error", "-of", "json")
				out, err := cmd.CombinedOutput()
				if err != nil {
					log.Println(info.Name(), string(out), err.Error())
				}
				var mediaInfo mediaInfoStruct
				_ = json.Unmarshal(out, &mediaInfo)
				replaceMediaName(pathstr, &mediaInfo)
			default:
				// 其他未知文件不处理
			}
		}
		return err
	})
	if err != nil {
		log.Println("扫描文件列表错误", err.Error())
		return
	}
}

func haveError() {
	log.Println("结束:可 CTRL+C 或者 关闭窗口")
	sigChan := make(chan os.Signal)
	signal.Notify(sigChan)
	<-sigChan
}

func main() {
	fmt.Println("———— 媒体文件格式化文件名程序 (CTRL+C可随时终止)")
	fmt.Println("———— 注意:请勿直接输入盘符根目录,否则会扫描路径下的全部媒体进行文件名格式化。")
	// 判断ffprobe是否存在
	ffmpegPathFullPath := fmt.Sprintf("%s%s", ffmpegPath, "ffprobe.exe")
	if ffprobeExist, _ := PathExists(ffmpegPathFullPath); ffprobeExist == false {
		fmt.Println("错误:缺少依赖程序,请下载ffmpeg解压并放到 C盘 目录下,并把目录名改成ffmpeg,完整路径 C:\\ffmpeg")
		fmt.Println("ffmpeg下载地址:https://github.com/BtbN/FFmpeg-Builds/releases")
		fmt.Println("Assets 压缩包:ffmpeg-master-latest-win64-gpl-shared.zip ,解压到C盘根目录并后重命名为ffmpeg")
		haveError()
	}
	fmt.Println("———— 支持的媒体格式如下")
	fmt.Printf("视频格式:%v\n音频格式:%v \n\n", videoExt, audioExt)
	fmt.Println("请粘贴要执行的目录地址(回车后执行):")
	reader := bufio.NewReader(os.Stdin)       // 标准输入输出
	scanRootPath, _ = reader.ReadString('\n') // 回车结束
	scanRootPath = strings.TrimSpace(scanRootPath)

	scanRootPath = strings.Trim(scanRootPath, " ")
	if scanRootPath == "" {
		log.Println("错误:没有获取到你输入的目录")
		haveError()
	}

	// 判断扫描的目录是否存在
	if PathExist, _ := PathExists(scanRootPath); PathExist == false {
		log.Println("Error:扫描的目录不存在", scanRootPath)
		haveError()
	}

	mediaAddDesc(scanRootPath)

	log.Println("运行结束,请检查命名结果")
	haveError()
}
温馨提示:本文最后更新于2023-08-29 16:22:06,某些文章具有时效性,若有错误或已失效,请在下方留言或者右下角私信。
注意:为了节省大家的时间,不会使用百度云下载也不愿意看解压说明教程、或者遇到不会解压的问题也不右下角私聊站长解决就直接投诉订单的朋友,还是别开通会员了,既浪费了您的时间也浪费了您的精力,感谢理解。解压说明
© 版权声明
THE END
喜欢就支持一下吧
点赞0
留言 抢沙发

请登录后发表评论

    请登录后查看评论内容