// // rhimportd // // The Radio Helsinki Rivendell Import Daemon // // // Copyright (C) 2015-2016 Christian Pointner // // This file is part of rhimportd. // // rhimportd 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 // any later version. // // rhimportd 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 rhimportd. If not, see . // package rhimport import ( "errors" "fmt" "io" "os" "os/exec" "path/filepath" "strings" ) type FetchConverter interface { io.WriteCloser GetResult() (result string, err error, loudnessCorr float64) } type ConverterResult struct { output string err error loudnessCorr float64 } func NewFetchConverter(convType, filename string, metadata map[string]string) (FetchConverter, string, error) { switch convType { case "null": return NewNullFetchConverter(filename, metadata) case "ffmpeg": return NewFFMpegFetchConverter(filename, metadata) case "ffmpeg-bs1770": return NewFFMpegBS1770FetchConverter(filename, metadata) } return nil, "", errors.New("unknown fetch converter type: " + convType) } // // NUll Converter aka File Writer // type NullFetchConverter struct { file *os.File } func NewNullFetchConverter(filename string, metadata map[string]string) (n *NullFetchConverter, newFilename string, err error) { n = &NullFetchConverter{} rhl.Printf("null-converter: opening file '%s'", filename) newFilename = filename n.file, err = os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) return } func (c *NullFetchConverter) Write(p []byte) (n int, err error) { return c.file.Write(p) } func (c *NullFetchConverter) Close() (err error) { return c.file.Close() } func (c *NullFetchConverter) GetResult() (result string, err error, loudnessCorr float64) { return "", nil, 0.0 } // // FFMpeg Converter: converts all files into flac // type FFMpegFetchConverter struct { cmd *exec.Cmd pipe io.WriteCloser result chan ConverterResult } func NewFFMpegFetchConverter(filename string, metadata map[string]string) (ff *FFMpegFetchConverter, filenameFlac string, err error) { ff = &FFMpegFetchConverter{} ext := filepath.Ext(filename) filenameFlac = strings.TrimSuffix(filename, ext) + ".flac" rhl.Printf("ffmpeg-converter: starting ffmpeg for file '%s' (had extension: '%s')", filenameFlac, ext) ff.cmd = exec.Command("ffmpeg", "-loglevel", "warning", "-i", "-", "-map_metadata", "0") if metadata != nil { for key, value := range metadata { ff.cmd.Args = append(ff.cmd.Args, "-metadata", fmt.Sprintf("%s=%s", key, value)) } } ff.cmd.Args = append(ff.cmd.Args, "-f", "flac", filenameFlac) if ff.pipe, err = ff.cmd.StdinPipe(); err != nil { return nil, "", err } ff.result = make(chan ConverterResult, 1) go func() { output, err := ff.cmd.CombinedOutput() ff.result <- ConverterResult{strings.TrimSpace(string(output)), err, 0.0} }() return } func (ff *FFMpegFetchConverter) Write(p []byte) (n int, err error) { return ff.pipe.Write(p) } func (ff *FFMpegFetchConverter) Close() (err error) { return ff.pipe.Close() } func (ff *FFMpegFetchConverter) GetResult() (result string, err error, loudnessCorr float64) { if ff.result != nil { r := <-ff.result return r.output, r.err, r.loudnessCorr } return "", nil, 0.0 } // // FFMpeg Converter: converts all files into flac and calculates loudness correction value // using ITU BS1770 (EBU R128) // type FFMpegBS1770FetchConverter struct { ffmpeg *exec.Cmd bs1770 *exec.Cmd pipe io.WriteCloser result chan ConverterResult } func NewFFMpegBS1770FetchConverter(filename string, metadata map[string]string) (ff *FFMpegBS1770FetchConverter, filenameFlac string, err error) { ff = &FFMpegBS1770FetchConverter{} ext := filepath.Ext(filename) filenameFlac = strings.TrimSuffix(filename, ext) + ".flac" rhl.Printf("ffmpeg-converter: starting ffmpeg for file '%s' (had extension: '%s')", filenameFlac, ext) ff.ffmpeg = exec.Command("ffmpeg", "-loglevel", "warning", "-i", "-", "-map_metadata", "0") if metadata != nil { for key, value := range metadata { ff.ffmpeg.Args = append(ff.ffmpeg.Args, "-metadata", fmt.Sprintf("%s=%s", key, value)) } } ff.ffmpeg.Args = append(ff.ffmpeg.Args, "-f", "flac", filenameFlac) if ff.pipe, err = ff.ffmpeg.StdinPipe(); err != nil { return nil, "", err } ff.result = make(chan ConverterResult, 1) go func() { output, err := ff.ffmpeg.CombinedOutput() ff.result <- ConverterResult{strings.TrimSpace(string(output)), err, 0.0} }() return } func (ff *FFMpegBS1770FetchConverter) Write(p []byte) (n int, err error) { return ff.pipe.Write(p) } func (ff *FFMpegBS1770FetchConverter) Close() (err error) { return ff.pipe.Close() } func (ff *FFMpegBS1770FetchConverter) GetResult() (result string, err error, loudnessCorr float64) { if ff.result != nil { r := <-ff.result return r.output, r.err, r.loudnessCorr } return "", nil, 0.0 }