Integrate MP3 duration extraction and silence handling (#6)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Docker / build (push) Has been cancelled
golangci-lint / lint (push) Has been cancelled

* feat: integrate MP3 duration extraction and silence handling

- Added a new dependency on github.com/tcolgate/mp3 for MP3 frame decoding.
- Implemented actual MP3 duration retrieval in the scanRecordings function, falling back to an estimation if retrieval fails.
- Introduced a silent frame asset for generating silence in audio streams.
- Created a silence reader to provide a continuous stream of silent frames.
- Added necessary documentation and licensing for the new MP3 package.
- Updated .gitignore to exclude MP3 files except for the internal data directory.

* feat: update README and improve application configuration and logging

* refactor: remove unused GenerateFileHash function and improve resource cleanup in MP3 and recorder modules
This commit is contained in:
2025-04-13 15:56:22 +03:00
committed by GitHub
parent ca6d23daf7
commit b5f23a50b7
28 changed files with 1362 additions and 265 deletions

2
vendor/github.com/tcolgate/mp3/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
*.mp3
!internal/data/*.mp3

20
vendor/github.com/tcolgate/mp3/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 Tristan Colgate-McFarlane and badgerodon
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

6
vendor/github.com/tcolgate/mp3/README.md generated vendored Normal file
View File

@@ -0,0 +1,6 @@
MP3
Stream orientated mp3 frame decoder
[![GoDoc](https://godoc.org/github.com/tcolgate/mp3?status.svg)](https://godoc.org/github.com/tcolgate/mp3)

31
vendor/github.com/tcolgate/mp3/doc.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
// The MIT License (MIT)
//
// Copyright (c) 2015 Tristan Colgate-McFarlane and badgerodon
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Package mp3 provides decoding of mp3 files into their underlying frames. It
// is primarily intended for streaming tasks with minimal internal buffering
// and no requirement to seek.
//
// The implementation started as a reworking of github.com/badgerodon/mp3, and
// has also drawn from Konrad Windszus' excellent article on mp3 frame parsing
// http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
//
// TODO CRC isn't currently checked.
package mp3

View File

@@ -0,0 +1,16 @@
// Code generated by "stringer -type=FrameChannelMode"; DO NOT EDIT
package mp3
import "fmt"
const _FrameChannelMode_name = "StereoJointStereoDualChannelSingleChannelChannelModeMax"
var _FrameChannelMode_index = [...]uint8{0, 6, 17, 28, 41, 55}
func (i FrameChannelMode) String() string {
if i >= FrameChannelMode(len(_FrameChannelMode_index)-1) {
return fmt.Sprintf("FrameChannelMode(%d)", i)
}
return _FrameChannelMode_name[_FrameChannelMode_index[i]:_FrameChannelMode_index[i+1]]
}

16
vendor/github.com/tcolgate/mp3/frameemphasis_string.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// Code generated by "stringer -type=FrameEmphasis"; DO NOT EDIT
package mp3
import "fmt"
const _FrameEmphasis_name = "EmphNoneEmph5015EmphReservedEmphCCITJ17EmphMax"
var _FrameEmphasis_index = [...]uint8{0, 8, 16, 28, 39, 46}
func (i FrameEmphasis) String() string {
if i >= FrameEmphasis(len(_FrameEmphasis_index)-1) {
return fmt.Sprintf("FrameEmphasis(%d)", i)
}
return _FrameEmphasis_name[_FrameEmphasis_index[i]:_FrameEmphasis_index[i+1]]
}

16
vendor/github.com/tcolgate/mp3/framelayer_string.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// Code generated by "stringer -type=FrameLayer"; DO NOT EDIT
package mp3
import "fmt"
const _FrameLayer_name = "LayerReservedLayer3Layer2Layer1LayerMax"
var _FrameLayer_index = [...]uint8{0, 13, 19, 25, 31, 39}
func (i FrameLayer) String() string {
if i >= FrameLayer(len(_FrameLayer_index)-1) {
return fmt.Sprintf("FrameLayer(%d)", i)
}
return _FrameLayer_name[_FrameLayer_index[i]:_FrameLayer_index[i+1]]
}

448
vendor/github.com/tcolgate/mp3/frames.go generated vendored Normal file
View File

@@ -0,0 +1,448 @@
package mp3
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"time"
)
type (
// Decoder translates a io.Reader into a series of frames
Decoder struct {
src io.Reader
err error
}
// Frame represents one individual mp3 frame
Frame struct {
buf []byte
}
// FrameHeader represents the entire header of a frame
FrameHeader []byte
// FrameVersion is the MPEG version given in the frame header
FrameVersion byte
// FrameLayer is the MPEG layer given in the frame header
FrameLayer byte
// FrameEmphasis is the Emphasis value from the frame header
FrameEmphasis byte
// FrameChannelMode is the Channel mode from the frame header
FrameChannelMode byte
// FrameBitRate is the bit rate from the frame header
FrameBitRate int
// FrameSampleRate is the sample rate from teh frame header
FrameSampleRate int
// FrameSideInfo holds the SideInfo bytes from the frame
FrameSideInfo []byte
)
//go:generate stringer -type=FrameVersion
const (
MPEG25 FrameVersion = iota
MPEGReserved
MPEG2
MPEG1
VERSIONMAX
)
//go:generate stringer -type=FrameLayer
const (
LayerReserved FrameLayer = iota
Layer3
Layer2
Layer1
LayerMax
)
//go:generate stringer -type=FrameEmphasis
const (
EmphNone FrameEmphasis = iota
Emph5015
EmphReserved
EmphCCITJ17
EmphMax
)
//go:generate stringer -type=FrameChannelMode
const (
Stereo FrameChannelMode = iota
JointStereo
DualChannel
SingleChannel
ChannelModeMax
)
const (
// ErrInvalidBitrate indicates that the header information did not contain a recognized bitrate
ErrInvalidBitrate FrameBitRate = -1
)
var (
bitrates = [VERSIONMAX][LayerMax][15]int{
{ // MPEG 2.5
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer3
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer2
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, // Layer1
},
{ // Reserved
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer3
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer2
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer1
},
{ // MPEG 2
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer3
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer2
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, // Layer1
},
{ // MPEG 1
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}, // Layer3
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}, // Layer2
{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}, // Layer1
},
}
sampleRates = [int(VERSIONMAX)][3]int{
{11025, 12000, 8000}, //MPEG25
{0, 0, 0}, //MPEGReserved
{22050, 24000, 16000}, //MPEG2
{44100, 48000, 32000}, //MPEG1
}
// ErrInvalidSampleRate indicates that no samplerate could be found for the frame header provided
ErrInvalidSampleRate = FrameSampleRate(-1)
samplesPerFrame = [VERSIONMAX][LayerMax]int{
{ // MPEG25
0,
576,
1152,
384,
},
{ // Reserved
0,
0,
0,
0,
},
{ // MPEG2
0,
576,
1152,
384,
},
{ // MPEG1
0,
1152,
1152,
384,
},
}
slotSize = [LayerMax]int{
0, // LayerReserved
1, // Layer3
1, // Layer2
4, // Layer1
}
// ErrNoSyncBits implies we could not find a valid frame header sync bit before EOF
ErrNoSyncBits = errors.New("EOF before sync bits found")
// ErrPrematureEOF indicates that the filed ended before a complete frame could be read
ErrPrematureEOF = errors.New("EOF mid stream")
)
func init() {
bitrates[MPEG25] = bitrates[MPEG2]
samplesPerFrame[MPEG25] = samplesPerFrame[MPEG2]
}
// NewDecoder returns a decoder that will process the provided reader.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r, nil}
}
// fill slice d until it is of len l, using bytes from reader r
func fillbuf(d []byte, r io.Reader, l int) (res []byte, err error) {
if len(d) >= l {
// we already have enough bytes
return d, nil
}
// How many bytes do we need to fetch
missing := l - len(d)
// Does d have sufficient capacity? if not extent it
if cap(d) < l {
if d == nil {
d = make([]byte, l)
} else {
il := len(d)
d = d[:cap(d)] // stretch d to it's full capacity
d = append(d, make([]byte, l-cap(d))...)
d = d[:il] //we've extended the capm reset len
}
}
d = d[:l]
_, err = io.ReadFull(r, d[len(d)-missing:])
return d, err
}
// Decode reads the next complete discovered frame into the provided
// Frame struct. A count of skipped bytes will be written to skipped.
func (d *Decoder) Decode(v *Frame, skipped *int) (err error) {
// Truncate the array
v.buf = v.buf[:0]
hLen := 4
// locate a sync frame
*skipped = 0
for {
v.buf, err = fillbuf(v.buf, d.src, hLen)
if err != nil {
return err
}
if v.buf[0] == 0xFF && (v.buf[1]&0xE0 == 0xE0) &&
v.Header().Emphasis() != EmphReserved &&
v.Header().Layer() != LayerReserved &&
v.Header().Version() != MPEGReserved &&
v.Header().SampleRate() != -1 &&
v.Header().BitRate() != -1 {
break
}
switch {
case v.buf[1] == 0xFF:
v.buf = v.buf[1:]
*skipped++
default:
v.buf = v.buf[2:]
*skipped += 2
}
}
crcLen := 0
if v.Header().Protection() {
crcLen = 2
v.buf, err = fillbuf(v.buf, d.src, hLen+crcLen)
if err != nil {
return err
}
}
sideLen, err := v.SideInfoLength()
if err != nil {
return err
}
v.buf, err = fillbuf(v.buf, d.src, hLen+crcLen+sideLen)
if err != nil {
return err
}
dataLen := v.Size()
v.buf, err = fillbuf(v.buf, d.src, dataLen)
if err != nil {
return err
}
return nil
}
// SideInfoLength retursn the expected side info length for this
// mp3 frame
func (f *Frame) SideInfoLength() (int, error) {
switch f.Header().Version() {
case MPEG1:
switch f.Header().ChannelMode() {
case SingleChannel:
return 17, nil
case Stereo, JointStereo, DualChannel:
return 32, nil
default:
return 0, errors.New("bad channel mode")
}
case MPEG2, MPEG25:
switch f.Header().ChannelMode() {
case SingleChannel:
return 9, nil
case Stereo, JointStereo, DualChannel:
return 17, nil
default:
return 0, errors.New("bad channel mode")
}
default:
return 0, fmt.Errorf("bad version (%v)", f.Header().Version())
}
}
// Header returns the header for this frame
func (f *Frame) Header() FrameHeader {
return FrameHeader(f.buf[0:4])
}
// CRC returns the CRC word stored in this frame
func (f *Frame) CRC() (uint16, error) {
var crc uint16
if !f.Header().Protection() {
return 0, nil
}
crcdata := bytes.NewReader(f.buf[4:6])
err := binary.Read(crcdata, binary.BigEndian, &crc)
return crc, err
}
// SideInfo returns the side info for this frame
func (f *Frame) SideInfo() FrameSideInfo {
if f.Header().Protection() {
return FrameSideInfo(f.buf[6:])
}
return FrameSideInfo(f.buf[4:])
}
// Frame returns a string describing this frame, header and side info
func (f *Frame) String() string {
str := ""
str += fmt.Sprintf("Header: \n%s", f.Header())
str += fmt.Sprintf("SideInfo: \n%s", f.SideInfo())
crc, err := f.CRC()
str += fmt.Sprintf("CRC: %x (err: %v)\n", crc, err)
str += fmt.Sprintf("Samples: %v\n", f.Samples())
str += fmt.Sprintf("Size: %v\n", f.Size())
str += fmt.Sprintf("Duration: %v\n", f.Duration())
return str
}
// Version returns the MPEG version from the header
func (h FrameHeader) Version() FrameVersion {
return FrameVersion((h[1] >> 3) & 0x03)
}
// Layer returns the MPEG layer from the header
func (h FrameHeader) Layer() FrameLayer {
return FrameLayer((h[1] >> 1) & 0x03)
}
// Protection indicates if there is a CRC present after the header (before the side data)
func (h FrameHeader) Protection() bool {
return (h[1] & 0x01) != 0x01
}
// BitRate returns the calculated bit rate from the header
func (h FrameHeader) BitRate() FrameBitRate {
bitrateIdx := (h[2] >> 4) & 0x0F
if bitrateIdx == 0x0F {
return ErrInvalidBitrate
}
br := bitrates[h.Version()][h.Layer()][bitrateIdx] * 1000
if br == 0 {
return ErrInvalidBitrate
}
return FrameBitRate(br)
}
// SampleRate returns the samplerate from the header
func (h FrameHeader) SampleRate() FrameSampleRate {
sri := (h[2] >> 2) & 0x03
if sri == 0x03 {
return ErrInvalidSampleRate
}
return FrameSampleRate(sampleRates[h.Version()][sri])
}
// Pad returns the pad bit, indicating if there are extra samples
// in this frame to make up the correct bitrate
func (h FrameHeader) Pad() bool {
return ((h[2] >> 1) & 0x01) == 0x01
}
// Private retrusn the Private bit from the header
func (h FrameHeader) Private() bool {
return (h[2] & 0x01) == 0x01
}
// ChannelMode returns the channel mode from the header
func (h FrameHeader) ChannelMode() FrameChannelMode {
return FrameChannelMode((h[3] >> 6) & 0x03)
}
// CopyRight returns the CopyRight bit from the header
func (h FrameHeader) CopyRight() bool {
return (h[3]>>3)&0x01 == 0x01
}
// Original returns the "original content" bit from the header
func (h FrameHeader) Original() bool {
return (h[3]>>2)&0x01 == 0x01
}
// Emphasis returns the Emphasis from the header
func (h FrameHeader) Emphasis() FrameEmphasis {
return FrameEmphasis((h[3] & 0x03))
}
// String dumps the frame header as a string for display purposes
func (h FrameHeader) String() string {
str := ""
str += fmt.Sprintf(" Layer: %v\n", h.Layer())
str += fmt.Sprintf(" Version: %v\n", h.Version())
str += fmt.Sprintf(" Protection: %v\n", h.Protection())
str += fmt.Sprintf(" BitRate: %v\n", h.BitRate())
str += fmt.Sprintf(" SampleRate: %v\n", h.SampleRate())
str += fmt.Sprintf(" Pad: %v\n", h.Pad())
str += fmt.Sprintf(" Private: %v\n", h.Private())
str += fmt.Sprintf(" ChannelMode: %v\n", h.ChannelMode())
str += fmt.Sprintf(" CopyRight: %v\n", h.CopyRight())
str += fmt.Sprintf(" Original: %v\n", h.Original())
str += fmt.Sprintf(" Emphasis: %v\n", h.Emphasis())
return str
}
// NDataBegin is the number of bytes before the frame header at which the sample data begins
// 0 indicates that the data begins after the side channel information. This data is the
// data from the "bit reservoir" and can be up to 511 bytes
func (i FrameSideInfo) NDataBegin() uint16 {
return (uint16(i[0]) << 1 & (uint16(i[1]) >> 7))
}
// Samples determines the number of samples based on the MPEG version and Layer from the header
func (f *Frame) Samples() int {
return samplesPerFrame[f.Header().Version()][f.Header().Layer()]
}
// Size clculates the expected size of this frame in bytes based on the header
// information
func (f *Frame) Size() int {
bps := float64(f.Samples()) / 8
fsize := (bps * float64(f.Header().BitRate())) / float64(f.Header().SampleRate())
if f.Header().Pad() {
fsize += float64(slotSize[f.Header().Layer()])
}
return int(fsize)
}
// Duration calculates the time duration of this frame based on the samplerate and number of samples
func (f *Frame) Duration() time.Duration {
ms := (1000 / float64(f.Header().SampleRate())) * float64(f.Samples())
return time.Duration(int(float64(time.Millisecond) * ms))
}
// String renders the side info as a string for display purposes
func (i FrameSideInfo) String() string {
str := ""
str += fmt.Sprintf(" NDataBegin: %v\n", i.NDataBegin())
return str
}
// Reader returns an io.Reader that reads the individual bytes from the frame
func (f *Frame) Reader() io.Reader {
return bytes.NewReader(f.buf)
}

16
vendor/github.com/tcolgate/mp3/frameversion_string.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// Code generated by "stringer -type=FrameVersion"; DO NOT EDIT
package mp3
import "fmt"
const _FrameVersion_name = "MPEG25MPEGReservedMPEG2MPEG1VERSIONMAX"
var _FrameVersion_index = [...]uint8{0, 6, 18, 23, 28, 38}
func (i FrameVersion) String() string {
if i >= FrameVersion(len(_FrameVersion_index)-1) {
return fmt.Sprintf("FrameVersion(%d)", i)
}
return _FrameVersion_name[_FrameVersion_index[i]:_FrameVersion_index[i+1]]
}

83
vendor/github.com/tcolgate/mp3/header.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
package mp3
/*
func (this *FrameHeader) Parse(bs []byte) error {
this.Size = 0
this.Samples = 0
this.Duration = 0
if len(bs) < 4 {
return fmt.Errorf("not enough bytes")
}
if bs[0] != 0xFF || (bs[1]&0xE0) != 0xE0 {
return fmt.Errorf("missing sync word, got: %x, %x", bs[0], bs[1])
}
this.Version = Version((bs[1] >> 3) & 0x03)
if this.Version == MPEGReserved {
return fmt.Errorf("reserved mpeg version")
}
this.Layer = Layer(((bs[1] >> 1) & 0x03))
if this.Layer == LayerReserved {
return fmt.Errorf("reserved layer")
}
this.Protection = (bs[1] & 0x01) != 0x01
bitrateIdx := (bs[2] >> 4) & 0x0F
if bitrateIdx == 0x0F {
return fmt.Errorf("invalid bitrate: %v", bitrateIdx)
}
this.Bitrate = bitrates[this.Version][this.Layer][bitrateIdx] * 1000
if this.Bitrate == 0 {
return fmt.Errorf("invalid bitrate: %v", bitrateIdx)
}
sampleRateIdx := (bs[2] >> 2) & 0x03
if sampleRateIdx == 0x03 {
return fmt.Errorf("invalid sample rate: %v", sampleRateIdx)
}
this.SampleRate = sampleRates[this.Version][sampleRateIdx]
this.Pad = ((bs[2] >> 1) & 0x01) == 0x01
this.Private = (bs[2] & 0x01) == 0x01
this.ChannelMode = ChannelMode(bs[3]>>6) & 0x03
// todo: mode extension
this.CopyRight = (bs[3]>>3)&0x01 == 0x01
this.Original = (bs[3]>>2)&0x01 == 0x01
this.Emphasis = Emphasis(bs[3] & 0x03)
if this.Emphasis == EmphReserved {
return fmt.Errorf("reserved emphasis")
}
this.Size = this.size()
this.Samples = this.samples()
this.Duration = this.duration()
return nil
}
func (this *FrameHeader) samples() int {
return samplesPerFrame[this.Version][this.Layer]
}
func (this *FrameHeader) size() int64 {
bps := float64(this.samples()) / 8
fsize := (bps * float64(this.Bitrate)) / float64(this.SampleRate)
if this.Pad {
fsize += float64(slotSize[this.Layer])
}
return int64(fsize)
}
func (this *FrameHeader) duration() time.Duration {
ms := (1000 / float64(this.SampleRate)) * float64(this.samples())
return time.Duration(time.Duration(float64(time.Millisecond) * ms))
}
*/

250
vendor/github.com/tcolgate/mp3/internal/data/bindata.go generated vendored Normal file
View File

@@ -0,0 +1,250 @@
package data
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"reflect"
"strings"
"unsafe"
"os"
"time"
"io/ioutil"
"path"
"path/filepath"
)
func bindata_read(data, name string) ([]byte, error) {
var empty [0]byte
sx := (*reflect.StringHeader)(unsafe.Pointer(&data))
b := empty[:]
bx := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bx.Data = sx.Data
bx.Len = len(data)
bx.Cap = bx.Len
gz, err := gzip.NewReader(bytes.NewBuffer(b))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindata_file_info struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindata_file_info) Name() string {
return fi.name
}
func (fi bindata_file_info) Size() int64 {
return fi.size
}
func (fi bindata_file_info) Mode() os.FileMode {
return fi.mode
}
func (fi bindata_file_info) ModTime() time.Time {
return fi.modTime
}
func (fi bindata_file_info) IsDir() bool {
return false
}
func (fi bindata_file_info) Sys() interface{} {
return nil
}
var _silent_1frame_go = "\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x64\x90\x41\x4b\x3b\x31\x10\xc5\xcf\x3b\x9f\xe2\xfd\xf7\xb4\x85\x7f\x1b\xaa\x17\x11\x7a\x50\xc1\x8b\x47\x8f\x22\x92\xee\xce\xa6\xa1\x9b\x99\x90\xa4\x4a\x91\x7e\x77\x77\xe3\x45\xf1\x10\x02\x33\x6f\x7e\xef\xcd\x44\xdb\x1f\xad\x63\x0c\xb6\x58\x22\x1f\xa2\xa6\x82\x76\x52\xd7\x12\xbd\xdb\x84\x8e\x1a\x63\xf0\xec\x27\x96\x72\x7f\x2e\x9c\xe1\x33\xca\x81\x91\xec\x47\x1d\xc2\x98\x34\xc0\x62\x7b\x75\x83\xa7\xbd\xc9\xd0\x11\x93\x0d\x0c\x96\x5e\x07\x1e\x20\x5a\x0e\x5e\x9c\x70\xce\xd4\xfc\x04\xbd\xbc\xee\xe7\x9f\x56\x44\xc6\x38\xbd\x75\x2c\x9c\x6c\x61\x38\x5d\xef\xbd\x54\xf6\x3a\x1e\xdd\xb7\xcb\x5a\x34\x70\xe8\x35\x9e\xb1\x31\x34\x9e\xa4\x87\x17\x5f\xba\x15\x3e\xa9\x59\x82\x72\xaa\x4f\xd3\x2f\x93\xff\xb5\xbe\xc3\x5d\xce\x5c\xba\x76\x41\x99\x5c\xdb\x6f\xdb\x31\xcd\x31\x37\x21\x5e\xb7\x2b\x6a\xfc\x58\x95\xff\x76\x10\x3f\x2d\xcc\x66\xbe\xc1\xe6\x71\xd6\x4f\x63\xd7\x3e\xe8\x69\xaa\x9b\x40\x23\x0b\xfe\x10\x60\x17\xfe\xc2\xb9\xd0\x85\xbe\x02\x00\x00\xff\xff\x0e\x9e\x37\x70\x54\x01\x00\x00"
func silent_1frame_go_bytes() ([]byte, error) {
return bindata_read(
_silent_1frame_go,
"silent_1frame.go",
)
}
func silent_1frame_go() (*asset, error) {
bytes, err := silent_1frame_go_bytes()
if err != nil {
return nil, err
}
info := bindata_file_info{name: "silent_1frame.go", size: 340, mode: os.FileMode(420), modTime: time.Unix(1424984811, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _silent_1frame_mp3 = "\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xfa\xff\x7b\x43\x0a\x03\xff\x07\x06\x86\x4c\x06\x06\x06\x0e\x06\x06\x5e\x05\x06\x06\x46\x20\x5a\x02\xe4\x02\x99\x26\x0d\x0c\x0c\x2c\x3e\x8e\xbe\xae\xc6\x7a\x96\x96\x7a\xa6\x0c\xa3\x60\x14\x50\x08\x00\x01\x00\x00\xff\xff\xa1\x6f\x84\x53\x72\x02\x00\x00"
func silent_1frame_mp3_bytes() ([]byte, error) {
return bindata_read(
_silent_1frame_mp3,
"silent_1frame.mp3",
)
}
func silent_1frame_mp3() (*asset, error) {
bytes, err := silent_1frame_mp3_bytes()
if err != nil {
return nil, err
}
info := bindata_file_info{name: "silent_1frame.mp3", size: 626, mode: os.FileMode(420), modTime: time.Unix(1424763406, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"silent_1frame.go": silent_1frame_go,
"silent_1frame.mp3": silent_1frame_mp3,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for name := range node.Children {
rv = append(rv, name)
}
return rv, nil
}
type _bintree_t struct {
Func func() (*asset, error)
Children map[string]*_bintree_t
}
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"silent_1frame.go": &_bintree_t{silent_1frame_go, map[string]*_bintree_t{
}},
"silent_1frame.mp3": &_bintree_t{silent_1frame_mp3, map[string]*_bintree_t{
}},
}}
// Restore an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, path.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// Restore assets under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
if err != nil { // File
return RestoreAsset(dir, name)
} else { // Dir
for _, child := range children {
err = RestoreAssets(dir, path.Join(name, child))
if err != nil {
return err
}
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@@ -0,0 +1,17 @@
package data
import "log"
var (
// SilentBytes is the raw data from a 128 Kb/s of lame encoded nothingness
SilentBytes []byte
)
//go:generate go-bindata -pkg data -nomemcopy ./
func init() {
var err error
SilentBytes, err = Asset("silent_1frame.mp3")
if err != nil {
log.Fatalf("Could not open silent_1frame.mp3 asset")
}
}

Binary file not shown.

51
vendor/github.com/tcolgate/mp3/silence.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
package mp3
import (
"bytes"
"io"
"github.com/tcolgate/mp3/internal/data"
)
var (
// SilentFrame is the sound of Ripley screaming on the Nostromo, from the outside
SilentFrame *Frame
// SilentBytes is the raw raw data behind SilentFrame
SilentBytes []byte
)
func init() {
skipped := 0
SilentBytes = data.SilentBytes
dec := NewDecoder(bytes.NewBuffer(SilentBytes))
frame := Frame{}
SilentFrame = &frame
dec.Decode(&frame, &skipped)
}
type silenceReader struct {
int // Location into the silence frame
}
func (s *silenceReader) Close() error {
return nil
}
func (s *silenceReader) Read(out []byte) (int, error) {
for i := 0; i < len(out); i++ {
out[i] = SilentBytes[s.int]
s.int++
if s.int >= len(SilentBytes) {
s.int = 0
}
}
return len(out), nil
}
// MakeSilence provides a constant stream of silenct frames.
func MakeSilence() io.ReadCloser {
return &silenceReader{0}
}

6
vendor/modules.txt vendored
View File

@@ -9,8 +9,6 @@ github.com/go-viper/mapstructure/v2/internal/errors
# github.com/gorilla/feeds v1.2.0
## explicit; go 1.20
github.com/gorilla/feeds
# github.com/mattn/go-sqlite3 v1.14.27
## explicit; go 1.19
# github.com/pelletier/go-toml/v2 v2.2.3
## explicit; go 1.21.0
github.com/pelletier/go-toml/v2
@@ -49,6 +47,10 @@ github.com/spf13/viper/internal/features
# github.com/subosito/gotenv v1.6.0
## explicit; go 1.18
github.com/subosito/gotenv
# github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300
## explicit
github.com/tcolgate/mp3
github.com/tcolgate/mp3/internal/data
# go.uber.org/multierr v1.11.0
## explicit; go 1.19
go.uber.org/multierr