feat: initial CLI converter (v1.2)
This commit is contained in:
commit
1d0a2cc1ea
5 files changed
+284
No files matched your search
Vendored
+19
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
|
"program": "main.go",
|
||||||
|
"cwd": "${cwd}",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
// "args": ["--debug", "-i", "C:\\Users\\m4xx1m\\Videos\\NVIDIA\\Desktop\\Desktop 2025.07.09 - 13.10.33.02.mp4"]
|
||||||
|
"args": ["-i", "E:\\records\\Derail Valley\\Derail Valley 2025.08.14 - 02.18.46.02.mp4"]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ffprobeOutput struct {
|
||||||
|
Streams []struct {
|
||||||
|
CodecType string `json:"codec_type"`
|
||||||
|
} `json:"streams"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ffprobe(inputFile string) (*ffprobeOutput, error) {
|
||||||
|
var err error
|
||||||
|
var out ffprobeOutput
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-v", "quiet",
|
||||||
|
"-print_format", "json",
|
||||||
|
"-show_streams",
|
||||||
|
inputFile,
|
||||||
|
}
|
||||||
|
proc := exec.Command("ffprobe", args...)
|
||||||
|
stdout, err := proc.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// fmt.Println("read", string(stdout))
|
||||||
|
|
||||||
|
err = json.Unmarshal(stdout, &out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
function pressEnterToExit {
|
||||||
|
Write-Host "Press Enter to exit... " -NoNewline
|
||||||
|
[void][System.Console]::ReadLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
$installPath = Join-Path $HOME "programs\convert"
|
||||||
|
New-Item -Force -ItemType Directory $installPath | Out-Null
|
||||||
|
|
||||||
|
$executablePath = Join-Path $installPath convert.exe
|
||||||
|
go build -o $executablePath
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Host "✅ built: $executablePath"
|
||||||
|
} else {
|
||||||
|
Write-Host "❌ build error"
|
||||||
|
pressEnterToExit
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
|
reg add "HKCU\Software\Classes\SystemFileAssociations\.mp4\shell\convert" /ve /d "convert" /f > $null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "❌ reg error"
|
||||||
|
pressEnterToExit
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = $executablePath + " -i `"%1`""
|
||||||
|
reg add "HKCU\Software\Classes\SystemFileAssociations\.mp4\shell\convert\command" /ve /d $command /f > $null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "❌ reg error"
|
||||||
|
pressEnterToExit
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "✅ reg values added"
|
||||||
|
pressEnterToExit
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DEFAULT_PRESET = "slow"
|
||||||
|
const DEFAULT_CRF = "20"
|
||||||
|
|
||||||
|
func scanArgFromConsole(text string, v *string) error {
|
||||||
|
fmt.Print(text)
|
||||||
|
if _, err := fmt.Scanln(v); err != nil && err.Error() == "EOF" {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pressEnterToExit(code int) {
|
||||||
|
fmt.Print("\npress enter to exit... ")
|
||||||
|
_, _ = fmt.Scanln()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
var debug bool
|
||||||
|
var vfArgs []string
|
||||||
|
var audioFilters []string
|
||||||
|
var videoFilters []string
|
||||||
|
var inputVideo, ss, to, an, crf, preset, mergeAudioStreams, speedUp string
|
||||||
|
|
||||||
|
ffmpegExecutable := "ffmpeg"
|
||||||
|
ffmpegArgs := append(make([]string, 0, 30), "-hide_banner")
|
||||||
|
inputArgs := []string{
|
||||||
|
"-hwaccel", "nvdec",
|
||||||
|
"-threads", "1",
|
||||||
|
}
|
||||||
|
postArgs := []string{
|
||||||
|
"-c:v", "h264",
|
||||||
|
"-profile:v", "high",
|
||||||
|
"-pix_fmt", "yuv420p",
|
||||||
|
"-tune", "film",
|
||||||
|
"-movflags", "+faststart",
|
||||||
|
"-r", "60",
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.StringVar(&inputVideo, "i", "", "input video path")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "debug mode (default params)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// check video exists
|
||||||
|
if _, err := os.Stat(inputVideo); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
pressEnterToExit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := ffprobe(inputVideo)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !debug {
|
||||||
|
err = func() error {
|
||||||
|
if err = scanArgFromConsole("ss > ", &ss); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = scanArgFromConsole("to > ", &to); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = scanArgFromConsole("an > ", &an); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = scanArgFromConsole(fmt.Sprintf("crf [%v] > ", DEFAULT_CRF), &crf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = scanArgFromConsole("speed up > ", &speedUp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = scanArgFromConsole(fmt.Sprintf("preset [%v] > ", DEFAULT_PRESET), &preset); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = scanArgFromConsole("merge audio streams > ", &mergeAudioStreams); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range []*string{&ss, &to, &an, &crf, &preset, &mergeAudioStreams, &speedUp} {
|
||||||
|
*v = strings.ToLower(strings.TrimSpace(*v))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ss) != 0 {
|
||||||
|
inputArgs = append(inputArgs, "-ss", ss)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(to) != 0 {
|
||||||
|
inputArgs = append(inputArgs, "-to", to)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(an) != 0 {
|
||||||
|
postArgs = append(postArgs, "-an")
|
||||||
|
} else {
|
||||||
|
audioFilters = append(audioFilters, "amix=inputs=2:normalize=0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(crf) == 0 {
|
||||||
|
crf = DEFAULT_CRF
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(preset) == 0 {
|
||||||
|
preset = DEFAULT_PRESET
|
||||||
|
}
|
||||||
|
|
||||||
|
postArgs = append(postArgs, "-preset", preset)
|
||||||
|
postArgs = append(postArgs, "-crf", crf)
|
||||||
|
|
||||||
|
// speed up video and audio(?)
|
||||||
|
if len(speedUp) != 0 {
|
||||||
|
videoFilters = append(videoFilters, fmt.Sprintf("setpts=1/%s*PTS", speedUp))
|
||||||
|
if len(an) == 0 {
|
||||||
|
audioFilters = append(audioFilters, fmt.Sprintf("atempo=%s", speedUp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(videoFilters) != 0 {
|
||||||
|
vfArgs = append(vfArgs, "[0:v:0]"+strings.Join(videoFilters, ",")+"[v]")
|
||||||
|
postArgs = append(postArgs, "-map", "[v]")
|
||||||
|
} else {
|
||||||
|
postArgs = append(postArgs, "-map", "0:v:0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(audioFilters) != 0 {
|
||||||
|
vfArgs = append(vfArgs, "[0:a]"+strings.Join(audioFilters, ",")+"[a]")
|
||||||
|
postArgs = append(postArgs, "-map", "[a]")
|
||||||
|
} else {
|
||||||
|
postArgs = append(postArgs, "-map", "0:a")
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge filter complex args
|
||||||
|
if len(vfArgs) != 0 {
|
||||||
|
postArgs = append(postArgs, "-filter_complex", strings.Join(vfArgs, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// out file name
|
||||||
|
outDir, _ := filepath.Split(inputVideo)
|
||||||
|
outFile := filepath.Join(outDir, strconv.FormatInt(time.Now().Unix(), 10)+".mp4")
|
||||||
|
|
||||||
|
// merge args to one slice
|
||||||
|
ffmpegArgs = append(ffmpegArgs, inputArgs...)
|
||||||
|
ffmpegArgs = append(ffmpegArgs, "-i", inputVideo)
|
||||||
|
ffmpegArgs = append(ffmpegArgs, postArgs...)
|
||||||
|
ffmpegArgs = append(ffmpegArgs, outFile)
|
||||||
|
|
||||||
|
fmt.Printf("
|
||||||
|
running %v: %v
|
||||||
|
|
||||||
|
", ffmpegExecutable, strings.Join(ffmpegArgs, " "))
|
||||||
|
|
||||||
|
proc := exec.Command(ffmpegExecutable, ffmpegArgs...)
|
||||||
|
proc.Stderr = os.Stderr
|
||||||
|
err = proc.Start()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
pressEnterToExit(1)
|
||||||
|
}
|
||||||
|
err = proc.Wait()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
pressEnterToExit(1)
|
||||||
|
}
|
||||||
|
if proc.ProcessState.ExitCode() != 0 {
|
||||||
|
fmt.Println(err)
|
||||||
|
pressEnterToExit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user