2022-02-22 14:02:58 +00:00
|
|
|
package transcoder
|
|
|
|
|
|
|
|
import (
|
2022-08-04 15:59:06 +01:00
|
|
|
"bufio"
|
|
|
|
"encoding/json"
|
2022-02-22 14:02:58 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-08-04 15:59:06 +01:00
|
|
|
"math"
|
|
|
|
"os"
|
2022-02-22 14:02:58 +00:00
|
|
|
"os/exec"
|
2022-08-04 15:59:06 +01:00
|
|
|
"strconv"
|
2022-02-22 14:02:58 +00:00
|
|
|
"strings"
|
2022-08-04 15:59:06 +01:00
|
|
|
"time"
|
2022-02-22 14:02:58 +00:00
|
|
|
|
|
|
|
"gitlab.com/ChaotiCryptidz/musicutil/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
type TranscodeConfig struct {
|
|
|
|
UseQuality bool `json:"use_quality"`
|
|
|
|
UseBitrate bool `json:"use_bitrate"`
|
|
|
|
Encoder string `json:"encoder"`
|
|
|
|
FileExtension string `json:"file_extension"`
|
|
|
|
Container string `json:"container"`
|
|
|
|
Bitrate string `json:"bitrate"`
|
|
|
|
Quality string `json:"quality"`
|
|
|
|
SampleRate string `json:"sample_rate"`
|
|
|
|
Channels string `json:"channels"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func isNotEmptyString(x string) bool {
|
|
|
|
if len(strings.TrimSpace(x)) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-08-04 15:59:06 +01:00
|
|
|
func progressDurationToMilliSeconds(duration string) int64 {
|
|
|
|
fields := strings.Split(strings.TrimSpace(duration), ":")
|
|
|
|
durationNs := int64(0)
|
|
|
|
|
|
|
|
h, _ := strconv.ParseInt(fields[0], 10, 64)
|
|
|
|
durationNs += int64(time.Hour) * h
|
|
|
|
|
|
|
|
m, _ := strconv.ParseInt(fields[1], 10, 64)
|
|
|
|
durationNs += int64(time.Minute) * m
|
|
|
|
|
|
|
|
s, _ := strconv.ParseFloat(fields[2], 64)
|
|
|
|
durationNs += int64(float64(time.Second) * s)
|
|
|
|
|
|
|
|
return time.Duration(durationNs).Milliseconds()
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFileLengthMilliSeconds(filename string) (int64, error) {
|
|
|
|
type FFProbeFormat struct {
|
|
|
|
Duration string `json:"duration"`
|
|
|
|
}
|
|
|
|
type FFProbeOutput struct {
|
|
|
|
Format FFProbeFormat `json:"format"`
|
|
|
|
}
|
|
|
|
|
|
|
|
ffprobe_output_str, err := exec.Command(
|
|
|
|
"ffprobe",
|
|
|
|
"-v", "quiet",
|
|
|
|
"-print_format", "json",
|
|
|
|
"-show_format",
|
|
|
|
filename,
|
|
|
|
).Output()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var ffprobe_output FFProbeOutput
|
|
|
|
err = json.Unmarshal([]byte(ffprobe_output_str), &ffprobe_output)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
} else {
|
|
|
|
duration_str := ffprobe_output.Format.Duration
|
|
|
|
duration_float, err := strconv.ParseFloat(duration_str, 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
duration_millis := int64(math.Round(duration_float * 1000))
|
|
|
|
return duration_millis, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Transcode(file *types.File, config *TranscodeConfig, progress_chan chan string, dest string) (string, error) {
|
2022-02-22 14:02:58 +00:00
|
|
|
command_args := make([]string, 0)
|
|
|
|
command_args = append(command_args, "-y")
|
|
|
|
command_args = append(command_args, "-hide_banner")
|
|
|
|
|
|
|
|
command_args = append(command_args, "-i")
|
|
|
|
command_args = append(command_args, file.JoinPathTo())
|
|
|
|
|
|
|
|
if isNotEmptyString(config.Encoder) {
|
|
|
|
command_args = append(command_args, "-c:a")
|
|
|
|
command_args = append(command_args, config.Encoder)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isNotEmptyString(config.Container) {
|
|
|
|
command_args = append(command_args, "-f")
|
|
|
|
command_args = append(command_args, config.Container)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isNotEmptyString(config.SampleRate) {
|
|
|
|
command_args = append(command_args, "-ar")
|
|
|
|
command_args = append(command_args, config.SampleRate)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isNotEmptyString(config.Channels) {
|
|
|
|
command_args = append(command_args, "-ac")
|
|
|
|
command_args = append(command_args, config.Channels)
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.UseQuality {
|
|
|
|
command_args = append(command_args, "-q:a")
|
|
|
|
command_args = append(command_args, config.Quality)
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.UseBitrate {
|
|
|
|
command_args = append(command_args, "-b:a")
|
|
|
|
command_args = append(command_args, config.Bitrate)
|
|
|
|
}
|
|
|
|
|
|
|
|
command_args = append(command_args, dest)
|
|
|
|
|
2022-08-04 15:59:06 +01:00
|
|
|
// Progress Shenanigans
|
|
|
|
|
|
|
|
if progress_chan != nil {
|
|
|
|
total_length_milliseconds, err := getFileLengthMilliSeconds(file.JoinPathTo())
|
|
|
|
if err != nil {
|
|
|
|
goto end
|
|
|
|
}
|
|
|
|
|
|
|
|
progress_temp_dir, err := os.MkdirTemp("", "*-musicutil_transcode_temp")
|
|
|
|
if err != nil {
|
|
|
|
goto end
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(progress_temp_dir)
|
|
|
|
|
|
|
|
progress_filename := progress_temp_dir + "/progress.log"
|
|
|
|
progress_file, err := os.Create(progress_filename)
|
|
|
|
if err != nil {
|
|
|
|
goto end
|
|
|
|
}
|
|
|
|
defer os.Remove(progress_filename)
|
|
|
|
|
|
|
|
command_args = append(command_args, "-progress", progress_filename, "-nostats")
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
reader := bufio.NewReader(progress_file)
|
|
|
|
for {
|
|
|
|
line, err := reader.ReadBytes('\n')
|
|
|
|
if err == nil {
|
|
|
|
if strings.HasPrefix(string(line), "out_time=") {
|
|
|
|
out_time := strings.TrimSuffix(strings.TrimPrefix(string(line), "out_time="), "\n")
|
|
|
|
out_time_ms := progressDurationToMilliSeconds(out_time)
|
|
|
|
|
|
|
|
progress_chan <- fmt.Sprintf("%0.2f%%", (float64(out_time_ms) / float64(total_length_milliseconds) * 100))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
2022-02-22 14:02:58 +00:00
|
|
|
cmd := exec.Command("ffmpeg", command_args...)
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(output)
|
|
|
|
return string(output), errors.New("ffmpeg error")
|
|
|
|
}
|
2022-08-04 15:59:06 +01:00
|
|
|
|
|
|
|
if progress_chan != nil {
|
|
|
|
close(progress_chan)
|
|
|
|
}
|
2022-02-22 14:02:58 +00:00
|
|
|
return string(output), nil
|
|
|
|
}
|