Skip to content

Instantly share code, notes, and snippets.

@JokerCatz
Last active February 4, 2021 10:06
Show Gist options
  • Save JokerCatz/2308ad427a8be1736bace25f63b3a469 to your computer and use it in GitHub Desktop.
Save JokerCatz/2308ad427a8be1736bace25f63b3a469 to your computer and use it in GitHub Desktop.
golang , vlc , mp4 to mp3 , test at macOS , and not stable
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