media-picker/core/handle_video.go

656 lines
22 KiB
Go
Raw Permalink 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 core
import (
"OdMediaPicker/util"
"OdMediaPicker/vars"
"bytes"
_ "embed"
"fmt"
"github.com/redmask-hb/GoSimplePrint/goPrint"
"math"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
//go:embed files/ffprobe.exe
var ffprobeWin64 []byte
var videoTag = "[PickVideo]" // 标记文件已经被整理过
var ignoreVideoPathList []string // 忽略的文件路径
var readErrorVideoPathList []string // 读取信息异常的路径
var videoPath2WidthHeightMap = make(map[string]string) // 视频路径和宽高比
var videoPath2WidthHeightTagMap = make(map[string]string) // 视频路径和宽高比[640x480]
var videoPath2DurationMap = make(map[string]string) // 视频路径和时长
// 支持的视频格式
var supportVideoTypes = []string{
".ts",
".flv",
".rm",
".avi",
".mp4",
".mov",
".mpg",
".mkv",
".m4v",
".rmvb",
".3gp",
".3g2",
".webm",
".wmv",
}
// 水平视频
var horizontalNormalVideoList []string
var horizontalGifVideoList []string
var horizontal1KVideoList []string
var horizontal2KVideoList []string
var horizontal3KVideoList []string
var horizontal4KVideoList []string
var horizontal5KVideoList []string
var horizontal6KVideoList []string
var horizontal7KVideoList []string
var horizontal8KVideoList []string
var horizontal9KVideoList []string
var horizontalHKVideoList []string
// 标准横向视频
var horizontalStandard720PVideoList []string
var horizontalStandard1080PVideoList []string
var horizontalStandard4KVideoList []string
var horizontalStandard8KVideoList []string
// 垂直视频
var verticalNormalVideoList []string
var verticalGifVideoList []string
var vertical1KVideoList []string
var vertical2KVideoList []string
var vertical3KVideoList []string
var vertical4KVideoList []string
var vertical5KVideoList []string
var vertical6KVideoList []string
var vertical7KVideoList []string
var vertical8KVideoList []string
var vertical9KVideoList []string
var verticalHKVideoList []string
// 等比视频
var squareNormalVideoList []string
var squareGifVideoList []string
var square1KVideoList []string
var square2KVideoList []string
var square3KVideoList []string
var square4KVideoList []string
var square5KVideoList []string
var square6KVideoList []string
var square7KVideoList []string
var square8KVideoList []string
var square9KVideoList []string
var squareHKVideoList []string
var squareStandard720PVideoList []string
var squareStandard1080PVideoList []string
var squareStandard4KVideoList []string
var squareStandard8KVideoList []string
func DoHandleVideo(rootDir string) {
// 释放ffprobe
readerFileName := "./ffprobe.exe"
if util.CheckFileIsExist(readerFileName) {
_ = os.Remove(readerFileName)
}
err := util.WriteByteArraysToFile(ffprobeWin64, readerFileName)
if err != nil {
fmt.Println("=== 释放解码器失败, 5秒后将自动退出", err)
time.Sleep(time.Second * 5)
return
}
total := len(vars.GlobalVideoPathList) // 总数
successCount := 0 // 成功数
errorCount := 0 // 失败数
ignoreCount := 0 // 忽略数
for _, videoFilePath := range vars.GlobalVideoPathList {
suffix := vars.GlobalFilePath2FileExtMap[videoFilePath]
if isSupportVideo(suffix) {
width, height, err := readVideoWidthHeight(videoFilePath)
if err == nil {
successCount = successCount + 1
videoPath2WidthHeightMap[videoFilePath] = fmt.Sprintf("%d-%d", width, height)
videoPath2WidthHeightTagMap[videoFilePath] = fmt.Sprintf("[%dx%d]", width, height)
fmt.Printf("=== Video总数: %d, 已读取Info: %d, 成功数: %d, 失败数: %d \n", total, successCount+errorCount+ignoreCount, successCount, errorCount)
duration := readVideoDuration(videoFilePath)
if duration == 0 {
videoPath2DurationMap[videoFilePath] = "0H0M0S"
} else {
videoPath2DurationMap[videoFilePath] = util.SecondsToHms(duration)
}
} else {
errorCount = errorCount + 1
readErrorVideoPathList = append(readErrorVideoPathList, videoFilePath)
fmt.Printf("=== 异常视频: %s \n", videoFilePath)
}
continue
}
// 其他的直接先忽略吧, 爱改改, 不改拉倒
ignoreCount = ignoreCount + 1
ignoreVideoPathList = append(ignoreVideoPathList, videoFilePath)
}
//uuid := strings.ReplaceAll(uuid.NewV4().String(), "-", "")
if len(readErrorVideoPathList) > 0 {
readInfoErrorPath := rootDir + string(os.PathSeparator) + "读取异常"
if util.CreateDir(readInfoErrorPath) {
doMoveFileToDir(readErrorVideoPathList, readInfoErrorPath)
}
}
if len(ignoreVideoPathList) > 0 {
ignorePath := rootDir + string(os.PathSeparator) + "已忽略"
if util.CreateDir(ignorePath) {
doMoveFileToDir(ignoreVideoPathList, ignorePath)
}
}
doPickVideoFile(rootDir, videoPath2WidthHeightMap)
if util.CheckFileIsExist(readerFileName) {
_ = os.Remove(readerFileName)
}
fmt.Printf("=== 视频处理完毕 \n\n")
}
// getVideoDuration 使用ffprobe获取视频时长
func getVideoDuration(ffmpegExecPath string, videoPath string) (float64, error) {
// ffprobe命令-v error 用于减少输出信息,-show_entries format=duration -of compact=p=0,nk=1 用于只输出时长
cmd := exec.Command(ffmpegExecPath, "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", videoPath)
var stderr bytes.Buffer
cmd.Stderr = &stderr
output, err := cmd.Output()
if err != nil {
return 0, fmt.Errorf("ffprobe failed with error: %v, stderr: %q", err, stderr.String())
}
// 解析输出的时长字符串为浮点数
durationStr := strings.TrimSpace(string(output))
duration, err := strconv.ParseFloat(durationStr, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse duration: %q, error: %v", durationStr, err)
}
return duration, nil
}
func getVideoResolution(ffmpegExecPath string, filePath string) (width int, height int, err error) {
// 构建ffprobe命令
cmd := exec.Command(ffmpegExecPath, "-v", "error", "-show_entries", "stream=width,height", "-of", "csv=p=0:s=x", filePath)
// 执行命令并捕获输出
output, err := cmd.Output()
if err != nil {
return 0, 0, fmt.Errorf("failed to run ffprobe: %w", err)
}
// 解析输出字符串,格式应为 "宽度,高度"
resolutionStr := strings.TrimSpace(string(output))
parts := strings.Split(resolutionStr, ",")
if len(parts) == 2 {
width = util.String2int(parts[0])
height = util.String2int(parts[1])
return width, height, nil
}
parts = strings.Split(resolutionStr, "x")
if len(parts) == 2 {
width = util.String2int(parts[0])
height = util.String2int(parts[1])
return width, height, nil
}
parts = strings.Split(resolutionStr, "\r\n\r\n\r\n\r\n")
if len(parts) == 2 {
tempHw := parts[0]
parts = strings.Split(tempHw, "x")
if len(parts) == 2 {
width = util.String2int(parts[0])
height = util.String2int(parts[1])
return width, height, nil
}
}
parts = strings.Split(resolutionStr, "x")
if len(parts) == 3 {
width = util.String2int(parts[0])
height = util.String2int(parts[1])
return width, height, nil
}
return 0, 0, fmt.Errorf("invalid resolution format: %s", resolutionStr)
}
// 获取视频的时长,单位秒
func readVideoDuration(videoFilePath string) int {
duration, err := getVideoDuration("./ffprobe.exe", videoFilePath)
if err != nil {
fmt.Println("=== Error getting video duration:", err)
return 0
}
//fmt.Printf("=== Video duration: %.2f seconds\n", duration)
return int(math.Floor(duration)) // 向下取整
}
// 获取视频的分辨率
func readVideoWidthHeight(videoFilePath string) (int, int, error) {
width, height, err := getVideoResolution("./ffprobe.exe", videoFilePath)
if err != nil {
fmt.Printf("=== Error getting resolution: %v\n", err)
return 0, 0, err
}
//fmt.Printf("=== Video resolution: %dx%d\n", width, height)
return width, height, nil
}
// 条件视频并分组存放
func doPickVideoFile(rootDir string, videoPath2WidthHeightMap map[string]string) {
if len(videoPath2WidthHeightMap) == 0 {
fmt.Printf("=== 当前目录下没有扫描到视频文件, %s \n", rootDir)
readerFileName := "./ffprobe.exe"
if util.CheckFileIsExist(readerFileName) {
_ = os.Remove(readerFileName)
}
return
}
for currentVideoPath, infoStr := range videoPath2WidthHeightMap {
width2Height := strings.Split(infoStr, "-")
width := util.String2int(width2Height[0])
height := util.String2int(width2Height[1])
suffix := vars.GlobalFilePath2FileExtMap[currentVideoPath]
if width > height {
handleHorizontalVideo(currentVideoPath, width, height, suffix)
continue
}
if width < height {
handleVerticalVideo(currentVideoPath, height, suffix)
continue
}
handleSquareVideo(currentVideoPath, width, height, suffix)
}
moveNormalVideo(rootDir)
moveHorizontalVideo(rootDir)
moveVerticalVideo(rootDir)
moveSquareVideo(rootDir)
}
// 移动垂直视频
func moveVerticalVideo(rootDir string) {
if len(vertical1KVideoList) > 0 {
renameFileV2("[V][1k]", vertical1KVideoList)
}
if len(vertical2KVideoList) > 0 {
renameFileV2("[V][2k]", vertical2KVideoList)
}
if len(vertical3KVideoList) > 0 {
renameFileV2("[V][3k]", vertical3KVideoList)
}
if len(vertical4KVideoList) > 0 {
renameFileV2("[V][4k]", vertical4KVideoList)
}
if len(vertical5KVideoList) > 0 {
renameFileV2("[V][5k]", vertical5KVideoList)
}
if len(vertical6KVideoList) > 0 {
renameFileV2("[V][6k]", vertical6KVideoList)
}
if len(vertical7KVideoList) > 0 {
renameFileV2("[V][7k]", vertical7KVideoList)
}
if len(vertical8KVideoList) > 0 {
renameFileV2("[V][8k]", vertical8KVideoList)
}
if len(vertical9KVideoList) > 0 {
renameFileV2("[V][9k]", vertical9KVideoList)
}
if len(verticalHKVideoList) > 0 {
renameFileV2("[V][原]", verticalHKVideoList)
}
}
// 移动文件到根目录
func renameFile(rootDir string, modelType string, videoList []string, pathSeparator string) {
total := len(videoList)
var count = 0
bar := goPrint.NewBar(100)
bar.SetNotice("=== 重命名文件:")
bar.SetGraph(">")
for _, videoFilePath := range videoList {
wh := videoPath2WidthHeightTagMap[videoFilePath]
fileName := vars.GlobalFilePath2FileNameMap[videoFilePath]
if strings.Contains(fileName, videoTag) { // 处理过了
fileNames := strings.Split(fileName, videoTag)
if len(fileNames) == 2 {
fileName = fileNames[1]
targetFilePath := rootDir + pathSeparator + "[" + videoPath2DurationMap[videoFilePath] + "]" + modelType + wh + videoTag + fileName
err := os.Rename(videoFilePath, targetFilePath)
if err != nil {
fmt.Printf("=== 重命名异常: %s \n", videoFilePath)
}
}
} else {
targetFilePath := rootDir + pathSeparator + "[" + videoPath2DurationMap[videoFilePath] + "]" + modelType + wh + videoTag + " - " + fileName
err := os.Rename(videoFilePath, targetFilePath)
if err != nil {
fmt.Printf("=== 重命名异常: %s \n", videoFilePath)
}
}
count = count + 1
bar.PrintBar(util.CalcPercentage(count, total))
}
bar.PrintEnd("=== Finish")
}
// 移动文件到原目录
func renameFileV2(modelType string, videoList []string) {
total := len(videoList)
var count = 0
bar := goPrint.NewBar(100)
bar.SetNotice("=== 重命名文件:")
bar.SetGraph(">")
for _, videoFilePath := range videoList {
wh := videoPath2WidthHeightTagMap[videoFilePath]
fileName := vars.GlobalFilePath2FileNameMap[videoFilePath]
filePath := util.GetFileDirectory(videoFilePath)
if strings.Contains(fileName, videoTag) { // 处理过了
fileNames := strings.Split(fileName, videoTag)
if len(fileNames) == 2 {
fileName = fileNames[1]
targetFilePath := filePath + "[" + videoPath2DurationMap[videoFilePath] + "]" + modelType + wh + videoTag + fileName
err := os.Rename(videoFilePath, targetFilePath)
if err != nil {
fmt.Printf("=== 重命名异常: %s \n", videoFilePath)
}
}
} else {
targetFilePath := filePath + "[" + videoPath2DurationMap[videoFilePath] + "]" + modelType + wh + videoTag + " - " + fileName
err := os.Rename(videoFilePath, targetFilePath)
if err != nil {
fmt.Printf("=== 重命名异常: %s \n", videoFilePath)
}
}
count = count + 1
bar.PrintBar(util.CalcPercentage(count, total))
}
bar.PrintEnd("=== Finish")
}
// 移动水平视频
func moveHorizontalVideo(rootDir string) {
if len(horizontal1KVideoList) > 0 {
renameFileV2("[H][1k]", horizontal1KVideoList)
}
if len(horizontal2KVideoList) > 0 {
renameFileV2("[H][2k]", horizontal2KVideoList)
}
if len(horizontal3KVideoList) > 0 {
renameFileV2("[H][3k]", horizontal3KVideoList)
}
if len(horizontal4KVideoList) > 0 {
renameFileV2("[H][4k]", horizontal4KVideoList)
}
if len(horizontal5KVideoList) > 0 {
renameFileV2("[H][5k]", horizontal5KVideoList)
}
if len(horizontal6KVideoList) > 0 {
renameFileV2("[H][6k]", horizontal6KVideoList)
}
if len(horizontal7KVideoList) > 0 {
renameFileV2("[H][7k]", horizontal7KVideoList)
}
if len(horizontal8KVideoList) > 0 {
renameFileV2("[H][8k]", horizontal8KVideoList)
}
if len(horizontal9KVideoList) > 0 {
renameFileV2("[H][9k]", horizontal9KVideoList)
}
if len(horizontalHKVideoList) > 0 {
renameFileV2("[H][原]", horizontalHKVideoList)
}
if len(horizontalStandard720PVideoList) > 0 {
renameFileV2("[H][720P]", horizontalStandard720PVideoList)
}
if len(horizontalStandard1080PVideoList) > 0 {
renameFileV2("[H][1080P]", horizontalStandard1080PVideoList)
}
if len(horizontalStandard4KVideoList) > 0 {
renameFileV2("[H][4KP]", horizontalStandard4KVideoList)
}
if len(horizontalStandard8KVideoList) > 0 {
renameFileV2("[H][8KP]", horizontalStandard8KVideoList)
}
}
// 移动等比视频
func moveSquareVideo(rootDir string) {
if len(square1KVideoList) > 0 {
renameFileV2("[M][1k]", square1KVideoList)
}
if len(square2KVideoList) > 0 {
renameFileV2("[M][2k]", square2KVideoList)
}
if len(square3KVideoList) > 0 {
renameFileV2("[M][3k]", square3KVideoList)
}
if len(square4KVideoList) > 0 {
renameFileV2("[M][4k]", square4KVideoList)
}
if len(square5KVideoList) > 0 {
renameFileV2("[M][5k]", square5KVideoList)
}
if len(square6KVideoList) > 0 {
renameFileV2("[M][6k]", square6KVideoList)
}
if len(square7KVideoList) > 0 {
renameFileV2("[M][7k]", square7KVideoList)
}
if len(square8KVideoList) > 0 {
renameFileV2("[M][8k]", square8KVideoList)
}
if len(square9KVideoList) > 0 {
renameFileV2("[M][9k]", square9KVideoList)
}
if len(squareHKVideoList) > 0 {
renameFileV2("[M][原]", squareHKVideoList)
}
if len(squareStandard720PVideoList) > 0 {
renameFileV2("[M][720P]", squareStandard720PVideoList)
}
if len(squareStandard1080PVideoList) > 0 {
renameFileV2("[M][1080P]", squareStandard1080PVideoList)
}
if len(squareStandard4KVideoList) > 0 {
renameFileV2("[M][4KP]", squareStandard4KVideoList)
}
if len(squareStandard8KVideoList) > 0 {
renameFileV2("[M][8KP]", squareStandard8KVideoList)
}
}
// 移动普通视频
func moveNormalVideo(rootDir string) {
//pathSeparator := string(os.PathSeparator)
if len(horizontalNormalVideoList) > 0 {
//renameFile(rootDir, "[L]", horizontalNormalVideoList, pathSeparator)
renameFileV2("[L]", horizontalNormalVideoList)
}
if len(verticalNormalVideoList) > 0 {
renameFileV2("[L]", verticalNormalVideoList)
}
if len(squareNormalVideoList) > 0 {
renameFileV2("[L]", squareNormalVideoList)
}
}
// 处理垂直视频
func handleVerticalVideo(currentVideoPath string, height int, suffix string) {
if strings.EqualFold(suffix, ".gif") {
verticalGifVideoList = append(verticalGifVideoList, currentVideoPath)
return
}
if height < 1000 {
verticalNormalVideoList = append(verticalNormalVideoList, currentVideoPath)
} else if height >= 1000 && height < 2000 {
vertical1KVideoList = append(vertical1KVideoList, currentVideoPath)
} else if height >= 2000 && height < 3000 {
vertical2KVideoList = append(vertical2KVideoList, currentVideoPath)
} else if height >= 3000 && height < 4000 {
vertical3KVideoList = append(vertical3KVideoList, currentVideoPath)
} else if height >= 4000 && height < 5000 {
vertical4KVideoList = append(vertical4KVideoList, currentVideoPath)
} else if height >= 5000 && height < 6000 {
vertical5KVideoList = append(vertical5KVideoList, currentVideoPath)
} else if height >= 6000 && height < 7000 {
vertical6KVideoList = append(vertical6KVideoList, currentVideoPath)
} else if height >= 7000 && height < 8000 {
vertical7KVideoList = append(vertical7KVideoList, currentVideoPath)
} else if height >= 8000 && height < 9000 {
vertical8KVideoList = append(vertical8KVideoList, currentVideoPath)
} else if height >= 9000 && height < 10000 {
vertical9KVideoList = append(vertical9KVideoList, currentVideoPath)
} else if height >= 10000 {
verticalHKVideoList = append(verticalHKVideoList, currentVideoPath)
}
}
// 处理横向视频
func handleHorizontalVideo(currentVideoPath string, width int, height int, suffix string) {
if strings.EqualFold(suffix, ".gif") {
horizontalGifVideoList = append(horizontalGifVideoList, currentVideoPath)
return
}
if width < 1000 {
// 160 × 120
// 320 × 180
// 320 × 240
// 640 × 360
// 640 × 480
horizontalNormalVideoList = append(horizontalNormalVideoList, currentVideoPath)
} else if width >= 1000 && width < 2000 {
// 1280 x 720 -> 720p
if width == 1280 && height == 720 {
horizontalStandard720PVideoList = append(horizontalStandard720PVideoList, currentVideoPath)
return
}
// 1920 x 1080 -> 1080p
if width == 1920 && height == 1080 {
horizontalStandard1080PVideoList = append(horizontalStandard1080PVideoList, currentVideoPath)
return
}
horizontal1KVideoList = append(horizontal1KVideoList, currentVideoPath)
} else if width >= 2000 && width < 3000 {
horizontal2KVideoList = append(horizontal2KVideoList, currentVideoPath)
} else if width >= 3000 && width < 4000 {
// 3840 x 2160 -> 4k
if width == 3840 && height == 2160 {
horizontalStandard4KVideoList = append(horizontalStandard4KVideoList, currentVideoPath)
return
}
horizontal3KVideoList = append(horizontal3KVideoList, currentVideoPath)
} else if width >= 4000 && width < 5000 {
horizontal4KVideoList = append(horizontal4KVideoList, currentVideoPath)
} else if width >= 5000 && width < 6000 {
horizontal5KVideoList = append(horizontal5KVideoList, currentVideoPath)
} else if width >= 6000 && width < 7000 {
horizontal6KVideoList = append(horizontal6KVideoList, currentVideoPath)
} else if width >= 7000 && width < 8000 {
// 7680 x 4320 -> 8k
if width == 7680 && height == 4320 {
horizontalStandard8KVideoList = append(horizontalStandard8KVideoList, currentVideoPath)
return
}
horizontal7KVideoList = append(horizontal7KVideoList, currentVideoPath)
} else if width >= 8000 && width < 9000 {
horizontal8KVideoList = append(horizontal8KVideoList, currentVideoPath)
} else if width >= 9000 && width < 10000 {
horizontal9KVideoList = append(horizontal9KVideoList, currentVideoPath)
} else if width >= 10000 {
horizontalHKVideoList = append(horizontalHKVideoList, currentVideoPath)
}
}
// 处理等比视频
func handleSquareVideo(currentVideoPath string, width int, height int, suffix string) {
if strings.EqualFold(suffix, ".gif") {
squareGifVideoList = append(squareGifVideoList, currentVideoPath)
return
}
if width < 1000 {
squareNormalVideoList = append(squareNormalVideoList, currentVideoPath)
} else if width >= 1000 && width < 2000 {
// 1280 x 720 -> 720p
if width == 1280 && height == 720 {
squareStandard720PVideoList = append(squareStandard720PVideoList, currentVideoPath)
return
}
// 1920 x 1080 -> 1080p
if width == 1920 && height == 1080 {
squareStandard1080PVideoList = append(squareStandard1080PVideoList, currentVideoPath)
return
}
square1KVideoList = append(square1KVideoList, currentVideoPath)
} else if width >= 2000 && width < 3000 {
square2KVideoList = append(square2KVideoList, currentVideoPath)
} else if width >= 3000 && width < 4000 {
// 3840 x 2160 -> 4k
if width == 3840 && height == 2160 {
squareStandard4KVideoList = append(squareStandard4KVideoList, currentVideoPath)
return
}
square3KVideoList = append(square3KVideoList, currentVideoPath)
} else if width >= 4000 && width < 5000 {
square4KVideoList = append(square4KVideoList, currentVideoPath)
} else if width >= 5000 && width < 6000 {
square5KVideoList = append(square5KVideoList, currentVideoPath)
} else if width >= 6000 && width < 7000 {
square6KVideoList = append(square6KVideoList, currentVideoPath)
} else if width >= 7000 && width < 8000 {
// 7680 x 4320 -> 8k
if width == 7680 && height == 4320 {
squareStandard8KVideoList = append(squareStandard8KVideoList, currentVideoPath)
return
}
square7KVideoList = append(square7KVideoList, currentVideoPath)
} else if width >= 8000 && width < 9000 {
square8KVideoList = append(square8KVideoList, currentVideoPath)
} else if width >= 9000 && width < 10000 {
square9KVideoList = append(square9KVideoList, currentVideoPath)
} else if width >= 10000 {
squareHKVideoList = append(squareHKVideoList, currentVideoPath)
}
}
// 判断是否属于支持的视频
func isSupportVideo(videoType string) bool {
for _, supportVideoType := range supportVideoTypes {
if strings.EqualFold(videoType, supportVideoType) {
return true
}
}
return false
}
// 批量移动文件到目录
func doMoveFileToDir(filePatnList []string, videoDirPath string) {
total := len(filePatnList)
var count = 0
bar := goPrint.NewBar(100)
bar.SetNotice("=== 移动文件到目录:")
bar.SetGraph(">")
pathSeparator := string(os.PathSeparator)
for _, videoFilePath := range filePatnList {
moveFileToDir(videoFilePath, videoDirPath+pathSeparator)
count = count + 1
bar.PrintBar(util.CalcPercentage(count, total))
}
bar.PrintEnd("=== Finish")
}
// 移动文件到目录
func moveFileToDir(sourceFilePath string, targetDirectory string) bool {
splits := strings.Split(sourceFilePath, string(os.PathSeparator))
fileName := splits[len(splits)-1]
targetFilePath := targetDirectory + fileName
err := os.Rename(sourceFilePath, targetFilePath)
//fmt.Printf("=== 移动文件, 源: %s, 目标: %s \n", sourceFilePath, targetFilePath)
return err == nil
}