Last active
February 4, 2021 10:06
-
-
Save JokerCatz/2308ad427a8be1736bace25f63b3a469 to your computer and use it in GitHub Desktop.
golang , vlc , mp4 to mp3 , test at macOS , and not stable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"flag" | |
"fmt" | |
"os" | |
"os/exec" | |
"path/filepath" | |
"regexp" | |
"strings" | |
"time" | |
) | |
/* | |
flow => | |
read file : /SOURCE_PATH/filename.mp4 | |
save file : /TARGET_PATH/filename.mp3 | |
full conv : /usr/local/bin/vlc --sout=#transcode{acodec=mp3,ab=256,vcodec=dummy}:std{access=file,mux=raw,dst=/TARGET_PATH/filename.mp3} --no-repeat --no-loop -I dummy --no-sout-video --sout-audio --no-sout-rtp-sap --no-sout-standard-sap - vlc://quit | |
*/ | |
// _vlcPath VLC exec path | |
var _vlcPath = "vlc" // need use 'which' to try to relay | |
// _vlcOptions VLC base option | |
var _vlcOptions = []string{ | |
"--sout=#transcode{%s}:std{%s,dst=%s}", // 0 output and format | |
"--no-repeat", "--no-loop", "-I dummy", "--no-sout-video", "--sout-audio", "--no-sout-rtp-sap", "--no-sout-standard-sap", // normal | |
"-", "vlc://quit", // tail (- : stdin[pipe]) | |
} | |
// _vlcTranscode VLC audio codec options | |
var _vlcTranscode = "acodec=mp3,ab=256,vcodec=dummy" | |
// _vlcOutputFormat VLC output audio file meta | |
var _vlcOutputFormat = "access=file,mux=raw" | |
// _pathFrom source path | |
var _pathFrom = "." | |
// _pathTo target path , if not exists will be create | |
var _pathTo = "." | |
// _force use force mode? (disable confirm) | |
var _isForce = false | |
// _readonly readonly mode? (list all command and path) | |
var _isReadonly = false | |
// cleanup | |
var _removePathTailChar = []byte{'/', ' ', ' '} | |
var _skipPathPatterm = regexp.MustCompile("\\/\\.") | |
var _processExtPattern = regexp.MustCompile("\\.mp4$") | |
var _pathBadChar = regexp.MustCompile("[ !'\"]") | |
var _pathBadCharClean = regexp.MustCompile("_{1,}") | |
// target ext | |
var _saveExt = ".mp3" // replace from _processExtPattern | |
// cancel convert err msg | |
var _allowErrMsg = []string{ | |
"mp4 demux error", | |
} | |
func checkFileExists(filePath string) bool { | |
state, err := os.Stat(filePath) | |
if !os.IsNotExist(err) && !state.IsDir() && state.Size() > 0 { | |
return true | |
} | |
return false | |
} | |
func cmdOptions(filename string) []string { // deepCopy and replace first string | |
result := []string{} | |
for index, item := range _vlcOptions { | |
if index == 0 { | |
result = append(result, fmt.Sprintf(item, filename)) | |
} else { | |
result = append(result, item) | |
} | |
} | |
return result | |
} | |
func trimPath(source string) string { | |
source = strings.Trim(source, "\n ") | |
for { | |
lenSource := len(source) | |
isFixed := false | |
if lenSource > 0 { | |
for _, char := range _removePathTailChar { | |
if source[lenSource-1] == char { | |
source = source[:lenSource-1] | |
lenSource-- | |
isFixed = true | |
} | |
} | |
} | |
if !isFixed { | |
break | |
} | |
} | |
return source | |
} | |
// execCmd | |
func execCmd(stdinP *os.File, name string, argvs ...string) (string /* stdout */, error) { | |
var stdOut bytes.Buffer | |
var stdErr bytes.Buffer | |
cmd := exec.Command(name, argvs...) | |
cmd.Env = os.Environ() | |
if stdinP != nil { | |
cmd.Stdin = stdinP | |
} | |
cmd.Stdout = &stdOut | |
cmd.Stderr = &stdErr | |
err := cmd.Run() | |
stdOutStr := stdOut.String() | |
stdErrStr := stdErr.String() | |
if err != nil { | |
return stdOutStr, err | |
} | |
if len(stdErrStr) > 0 { | |
return stdOutStr, fmt.Errorf(stdErrStr) | |
} | |
return stdOutStr, nil | |
} | |
func init() { | |
// try to relay default vlc path | |
stdOut, err := execCmd(nil, "which", "vlc") | |
if err == nil { | |
_vlcPath = trimPath(stdOut) // remove tail \n | |
} | |
// init flag | |
flag.BoolVar(&_isForce, "force", _isForce, "use force mode? (disable confirm)") | |
flag.BoolVar(&_isReadonly, "readonly", _isReadonly, "readonly mode? (list all command and path)") | |
flag.StringVar(&_vlcPath, "vlcPath", _vlcPath, "VLC exec path") | |
flag.StringVar(&_vlcTranscode, "vlcTranscode", _vlcTranscode, "VLC audio codec options") | |
flag.StringVar(&_vlcOutputFormat, "vlcOutputFormat", _vlcOutputFormat, "VLC output audio file meta") | |
flag.StringVar(&_pathFrom, "pathFrom", _pathFrom, "source path") | |
flag.StringVar(&_pathTo, "pathTo", _pathTo, "target path , if not exists will be create") | |
flag.Parse() | |
// rewirte path | |
pwd, err := os.Getwd() | |
if err != nil { | |
fmt.Println(err) | |
os.Exit(1) | |
} | |
pwd = trimPath(pwd) | |
_pathFrom = trimPath(_pathFrom) | |
_pathTo = trimPath(_pathTo) | |
if _pathFrom == "." { | |
_pathFrom = pwd | |
} | |
if _pathTo == "." { | |
_pathTo = pwd | |
} | |
// options merge _vlcTranscode & _vlcOutputFormat , tail need %s be output filename | |
_vlcOptions[0] = fmt.Sprintf(_vlcOptions[0], _vlcTranscode, _vlcOutputFormat, "%s") | |
fmt.Printf(`======== | |
convert from : %s | |
convert to : %s | |
full command : %s | |
`, _pathFrom, _pathTo, fmt.Sprintf("%s %s", _vlcPath, strings.Join(cmdOptions(fmt.Sprintf("{SUB}/{OUT}%s", _saveExt)), " "))) | |
if _isReadonly { | |
fmt.Println(" [ readonly mode ]") | |
} else if _isForce { | |
fmt.Println(" [ force mode ]") | |
} | |
fmt.Println("========") | |
} | |
func main() { | |
// path walk | |
err := filepath.Walk(_pathFrom, func(path string, info os.FileInfo, err error) error { | |
if err != nil { | |
fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err) | |
return err | |
} | |
// only process file | |
// file > 512 byte | |
// skip /.xxx hidden path | |
if !info.IsDir() && info.Size() > 512 && !_skipPathPatterm.MatchString(path) && _processExtPattern.MatchString(path) { | |
return nil // next | |
} | |
// need retry if file empty (test count 10) | |
retryCounter := 0 | |
for { | |
err := func() error { | |
outputPath := _pathBadCharClean.ReplaceAllString( | |
_pathBadChar.ReplaceAllString( | |
_processExtPattern.ReplaceAllString( | |
strings.Replace(path, _pathFrom, _pathTo, -1), | |
_saveExt, | |
), "_", | |
), "_", | |
) | |
if checkFileExists(outputPath) { | |
return nil // skip | |
} | |
// make same name folder and remove if = make all parent folder haha ... | |
_ = os.MkdirAll(outputPath, os.ModePerm) // just make all | |
err = os.RemoveAll(outputPath) // remove file or folder | |
if err != nil { | |
return err | |
} | |
tempOptions := cmdOptions(outputPath) | |
fmt.Printf("read file : %s\n", path) | |
fmt.Printf("save file : %s\n", outputPath) | |
fmt.Printf("full conv : %s %s\n", _vlcPath, strings.Join(tempOptions, " ")) | |
fmt.Printf("retry : %d\n", retryCounter) | |
fmt.Println("========") | |
sourceFile, err := os.Open(path) | |
if err != nil { | |
return err | |
} | |
defer sourceFile.Close() | |
_, err := execCmd(sourceFile, _vlcPath, tempOptions...) | |
if err != nil { | |
errMsg := err.Error() | |
if strings.Index(errMsg, "idummy demux: command `quit'") != -1 { | |
if strings.Index(errMsg, "error") != -1 { | |
fmt.Println(errMsg) | |
return fmt.Errorf("need retry") | |
} | |
// else : do nothing , look like success converted | |
} else { | |
for _, allowErr := range _allowErrMsg { | |
if strings.Index(errMsg, allowErr) != -1 { | |
return nil | |
} | |
} | |
return err | |
} | |
} | |
/* | |
// ... no stdout ... all stderr ... WTF !? | |
if len(stdOutStr) > 0 { | |
fmt.Println(stdOutStr) | |
} | |
*/ | |
if checkFileExists(outputPath) { | |
return nil | |
} | |
return fmt.Errorf("need retry") | |
}() | |
if err == nil { | |
break | |
} | |
if err.Error() == "need retry" { | |
if retryCounter >= 10 { | |
fmt.Println("QwQ too many retry ... skip") | |
return nil | |
} | |
time.Sleep(1000 * time.Millisecond) | |
retryCounter++ | |
continue // retry | |
} | |
panic(fmt.Sprintf("unknow err : %s", err.Error())) | |
} | |
return nil | |
}) | |
if err != nil { | |
fmt.Printf("error walking the path %s\n", err) | |
return | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment