// Copyright (C) 2019-2026 Chrystian Huot // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see package main import ( "bytes" "errors" "fmt" "os/exec" "path" "regexp" "strconv" "strings" ) type FFMpeg struct { available bool version43 bool warned bool } func NewFFMpeg() *FFMpeg { ffmpeg := &FFMpeg{} stdout := bytes.NewBuffer([]byte(nil)) cmd := exec.Command("ffmpeg", "-version") cmd.Stdout = stdout if err := cmd.Run(); err == nil { ffmpeg.available = true if l, err := stdout.ReadString('\n'); err == nil { s := regexp.MustCompile(`.*ffmpeg version .{0,1}([0-9])\.([0-9])\.[0-9].*`).ReplaceAllString(strings.TrimSuffix(l, "\n"), "$1.$2") v := strings.Split(s, ".") if len(v) > 1 { if major, err := strconv.Atoi(v[0]); err == nil { if minor, err := strconv.Atoi(v[1]); err == nil { if major > 4 || (major == 4 && minor >= 3) { ffmpeg.version43 = true } } } } } } return ffmpeg } func (ffmpeg *FFMpeg) Convert(call *Call, systems *Systems, tags *Tags, mode uint) error { var ( args = []string{"-i", "-"} err error ) if mode == AUDIO_CONVERSION_DISABLED { return nil } if !ffmpeg.available { if !ffmpeg.warned { ffmpeg.warned = true return errors.New("ffmpeg is not available, no audio conversion will be performed") } return nil } if tag, ok := tags.GetTagById(call.Talkgroup.TagId); ok { args = append(args, "-metadata", fmt.Sprintf("album=%v", call.Talkgroup.Label), "-metadata", fmt.Sprintf("artist=%v", call.System.Label), "-metadata", fmt.Sprintf("date=%v", call.Timestamp), "-metadata", fmt.Sprintf("genre=%v", tag), "-metadata", fmt.Sprintf("title=%v", call.Talkgroup.Name), ) } if ffmpeg.version43 { if mode == AUDIO_CONVERSION_ENABLED_NORM { args = append(args, "-af", "apad=whole_dur=3s,loudnorm") } else if mode == AUDIO_CONVERSION_ENABLED_LOUD_NORM { args = append(args, "-af", "apad=whole_dur=3s,loudnorm=I=-16:TP=-1.5:LRA=11") } } args = append(args, "-c:a", "aac", "-b:a", "32k", "-movflags", "frag_keyframe+empty_moov", "-f", "ipod", "-") cmd := exec.Command("ffmpeg", args...) cmd.Stdin = bytes.NewReader(call.Audio) stdout := bytes.NewBuffer([]byte(nil)) cmd.Stdout = stdout stderr := bytes.NewBuffer([]byte(nil)) cmd.Stderr = stderr if err = cmd.Run(); err == nil { call.Audio = stdout.Bytes() call.AudioFilename = fmt.Sprintf("%v.m4a", strings.TrimSuffix(call.AudioFilename, path.Ext((call.AudioFilename)))) call.AudioMime = "audio/mp4" } else { fmt.Println(stderr.String()) } return nil }