Added format / decoder guesser
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
2f33745b66
commit
3cdb593d2c
|
@ -5,13 +5,12 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/aac"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/guess"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/mp3"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/tta"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/vorbis"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/packetizer"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/hasher"
|
||||
"io"
|
||||
|
@ -30,7 +29,7 @@ const TestSingleSample24 = "resources/samples/cYsmix - Haunted House/11. The Gre
|
|||
const TestSingleSample16 = "resources/samples/Babbe Music - RADIANT DANCEFLOOR/01. ENTER.flac"
|
||||
const TestSingleSample16TTA = "resources/samples/Babbe Music - RADIANT DANCEFLOOR/01. ENTER.tta"
|
||||
|
||||
func doTest(format format.Decoder, ext string, t *testing.T) {
|
||||
func doTest(ext string, t *testing.T) {
|
||||
for _, location := range TestSampleLocations {
|
||||
entries, err := os.ReadDir(location)
|
||||
if err != nil {
|
||||
|
@ -48,7 +47,12 @@ func doTest(format format.Decoder, ext string, t *testing.T) {
|
|||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
source, err := format.Open(fp)
|
||||
decoders, err := guess.GetDecoders(fp, fullPath)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
source, err := guess.Open(fp, decoders)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -65,19 +69,19 @@ func doTest(format format.Decoder, ext string, t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFLACDecode(t *testing.T) {
|
||||
doTest(flac.NewFormat(), ".flac", t)
|
||||
doTest(".flac", t)
|
||||
}
|
||||
func TestTTADecode(t *testing.T) {
|
||||
doTest(tta.NewFormat(), ".tta", t)
|
||||
doTest(".tta", t)
|
||||
}
|
||||
func TestOpusDecode(t *testing.T) {
|
||||
doTest(opus.NewFormat(), ".opus", t)
|
||||
doTest(".opus", t)
|
||||
}
|
||||
func TestMP3Decode(t *testing.T) {
|
||||
doTest(mp3.NewFormat(), ".mp3", t)
|
||||
doTest(".mp3", t)
|
||||
}
|
||||
func TestVorbisDecode(t *testing.T) {
|
||||
doTest(vorbis.NewFormat(), ".vorbis", t)
|
||||
doTest(".vorbis", t)
|
||||
}
|
||||
|
||||
func TestHasher24(t *testing.T) {
|
||||
|
|
|
@ -144,5 +144,5 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
return /*bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 || */ extension == "aac"
|
||||
return extension == "aac"
|
||||
}
|
||||
|
|
|
@ -206,5 +206,5 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
return bytes.Compare(peek[:4], []byte{'O', 'g', 'g', 'S'}) == 0 || bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 || extension == "flac" || extension == "ogg"
|
||||
return bytes.Compare(peek[:4], []byte{'O', 'g', 'g', 'S'}) == 0 || bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 || (bytes.Compare(peek[:3], []byte{'I', 'D', '3'}) == 0 && extension != "mp3") || extension == "flac" || extension == "ogg"
|
||||
}
|
||||
|
|
100
audio/format/guess/guess.go
Normal file
100
audio/format/guess/guess.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
package guess
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/aac"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/mp3"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/tta"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/vorbis"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var knownFormats = []format.Format{
|
||||
aac.NewFormat(),
|
||||
flac.NewFormat(),
|
||||
mp3.NewFormat(),
|
||||
opus.NewFormat(),
|
||||
tta.NewFormat(),
|
||||
vorbis.NewFormat(),
|
||||
}
|
||||
|
||||
func GetDecoders(r io.ReadSeekCloser, pathName string) (decoders []format.Decoder, err error) {
|
||||
extension := strings.ToLower(strings.TrimPrefix(path.Ext(pathName), "."))
|
||||
|
||||
var startOffset int64
|
||||
startOffset, err = r.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
peek := make([]byte, 16)
|
||||
if _, err = r.Read(peek); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = r.Seek(startOffset, io.SeekStart); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, formatEntry := range knownFormats {
|
||||
if decoder, ok := formatEntry.(format.Decoder); ok {
|
||||
if decoder.Identify(peek, extension) {
|
||||
decoders = append(decoders, decoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(decoders) == 0 {
|
||||
return nil, errors.New("could not find decoder")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Open(r io.ReadSeekCloser, decoders []format.Decoder) (source audio.Source, err error) {
|
||||
var startOffset int64
|
||||
startOffset, err = r.Seek(0, io.SeekCurrent)
|
||||
|
||||
for _, decoder := range decoders {
|
||||
if _, err = r.Seek(startOffset, io.SeekStart); err != nil {
|
||||
return
|
||||
}
|
||||
source, err = decoder.Open(r)
|
||||
if source.Blocks != nil && err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//seek back
|
||||
if _, err = r.Seek(startOffset, io.SeekStart); err != nil {
|
||||
return
|
||||
}
|
||||
return audio.Source{}, errors.New("could not open stream")
|
||||
}
|
||||
|
||||
func OpenAnalyzer(r io.ReadSeekCloser, decoders []format.Decoder) (source audio.Source, analyzerChannel format.AnalyzerChannel, err error) {
|
||||
var startOffset int64
|
||||
startOffset, err = r.Seek(0, io.SeekCurrent)
|
||||
|
||||
for _, decoder := range decoders {
|
||||
if analyzer, ok := decoder.(format.AnalyzerDecoder); ok {
|
||||
if _, err = r.Seek(startOffset, io.SeekStart); err != nil {
|
||||
return
|
||||
}
|
||||
source, analyzerChannel, err = analyzer.OpenAnalyzer(r)
|
||||
if source.Blocks != nil && err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//seek back
|
||||
if _, err = r.Seek(startOffset, io.SeekStart); err != nil {
|
||||
return
|
||||
}
|
||||
return audio.Source{}, nil, errors.New("could not open stream analyzer")
|
||||
}
|
|
@ -6,6 +6,7 @@ package mp3
|
|||
*/
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
|
@ -159,5 +160,6 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
return /*bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 || */ extension == "mp3"
|
||||
//match ID3 / sync header
|
||||
return peek[0] == 0xff && (peek[1]>>5) == 0b111 || (bytes.Compare(peek[:3], []byte{'I', 'D', '3'}) == 0 && extension != "flac") || extension == "mp3"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue