dca

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2023 License: BSD-3-Clause Imports: 19 Imported by: 0

README

dca Go report Build Status

dca is a audio file format that uses opus packets and json metadata.

This package implements a decoder, encoder and a helper streamer for dca v0 and v1.

Docs on GoDoc

There's also a standalone command you can use here

Usage

Encoding


// Encoding a file and saving it to disk
encodeSession := dca.EncodeFile("path/to/file.mp3", dca.StdEncodeOptions)
// Make sure everything is cleaned up, that for example the encoding process if any issues happened isnt lingering around
defer encodeSession.Cleanup()

output, err := os.Create("output.dca")
if err != nil {
    // Handle the error
}

io.Copy(output, encodeSession)

Decoding, the decoder automatically detects dca version aswell as if metadata was available

// inputReader is an io.Reader, like a file for example
decoder := dca.NewDecoder(inputReader)

for {
    frame, err := decoder.OpusFrame()
    if err != nil {
        if err != io.EOF {
            // Handle the error
        }
        
        break
    }
    
    // Do something with the frame, in this example were sending it to discord
    select{
        case voiceConnection.OpusSend <- frame:
        case <-time.After(time.Second):
            // We haven't been able to send a frame in a second, assume the connection is borked
            return
    }
}

Using the helper streamer, the streamer creates a pausable stream to Discord.


// Source is an OpusReader, both EncodeSession and decoder implements opusreader
done := make(chan error)
streamer := dca.NewStreamer(source, voiceConnection, done)
err := <- done
if err != nil && err != io.EOF {
    // Handle the error
}

Using this youtube-dl Go package, one can stream music to Discord from Youtube

// Change these accordingly
options := dca.StdEncodeOptions
options.RawOutput = true
options.Bitrate = 96
options.Application = "lowdelay"

videoInfo, err := ytdl.GetVideoInfo(videoURL)
if err != nil {
    // Handle the error
}

format := videoInfo.Formats.Extremes(ytdl.FormatAudioBitrateKey, true)[0]
downloadURL, err := videoInfo.GetDownloadURL(format)
if err != nil {
    // Handle the error
}

encodingSession, err := dca.EncodeFile(downloadURL.String(), options)
if err != nil {
    // Handle the error
}
defer encodingSession.Cleanup()
    
done := make(chan error)    
dca.NewStream(encodingSession, voiceConnection, done)
err := <- done
if err != nil && err != io.EOF {
    // Handle the error
}
Official Specifications

Documentation

Index

Constants

View Source
const (
	// The current version of the DCA format
	FormatVersion int8 = 1

	// The current version of the DCA program
	LibraryVersion string = "0.0.5"

	// The URL to the GitHub repository of DCA
	GitHubRepositoryURL string = "https://github.com/jonas747/dca"
)

Define constants

Variables

View Source
var (
	ErrNotDCA        = errors.New("DCA Magic header not found, either not dca or raw dca frames")
	ErrNotFirstFrame = errors.New("Metadata can only be found in the first frame")
)
View Source
var (
	ErrBadFrame = errors.New("Bad Frame")
)
View Source
var (
	ErrNegativeFrameSize = errors.New("Frame size is negative, possibly corrupted.")
)
View Source
var (
	ErrVoiceConnClosed = errors.New("Voice connection closed")
)
View Source
var Logger *log.Logger
View Source
var StdEncodeOptions = &EncodeOptions{
	Volume:           256,
	Channels:         2,
	FrameRate:        48000,
	FrameDuration:    20,
	Bitrate:          64,
	Application:      AudioApplicationAudio,
	CompressionLevel: 10,
	PacketLoss:       1,
	BufferedFrames:   100,
	VBR:              true,
	StartTime:        0,
}

StdEncodeOptions is the standard options for encoding

Functions

func DecodeFrame

func DecodeFrame(r io.Reader) (frame []byte, err error)

DecodeFrame decodes a dca frame from an io.Reader and returns the raw opus audio ready to be sent to discord

Types

type AudioApplication

type AudioApplication string

AudioApplication is an application profile for opus encoding

var (
	AudioApplicationVoip     AudioApplication = "voip"     // Favor improved speech intelligibility.
	AudioApplicationAudio    AudioApplication = "audio"    // Favor faithfulness to the input
	AudioApplicationLowDelay AudioApplication = "lowdelay" // Restrict to only the lowest delay modes.
)

type DCAMetadata

type DCAMetadata struct {
	Version int8             `json:"version"`
	Tool    *DCAToolMetadata `json:"tool"`
}

DCA metadata struct

Contains the DCA version.

type DCAToolMetadata

type DCAToolMetadata struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Url     string `json:"url"`
	Author  string `json:"author"`
}

DCA tool metadata struct

Contains the Git revisions, commit author etc.

type Decoder

type Decoder struct {
	Metadata      *Metadata
	FormatVersion int
	// contains filtered or unexported fields
}

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder returns a new dca decoder

func (*Decoder) FrameDuration

func (d *Decoder) FrameDuration() time.Duration

FrameDuration implements OpusReader, returnining the specified duration per frame

func (*Decoder) OpusFrame

func (d *Decoder) OpusFrame() (frame []byte, err error)

OpusFrame returns the next audio frame If this is the first frame it will also check for metadata in it

func (*Decoder) ReadMetadata

func (d *Decoder) ReadMetadata() error

ReadMetadata reads the first metadata frame OpusFrame will call this automatically if

type EncodeOptions

type EncodeOptions struct {
	Volume           int              // change audio volume (256=normal)
	Channels         int              // audio channels
	FrameRate        int              // audio sampling rate (ex 48000)
	FrameDuration    int              // audio frame duration can be 20, 40, or 60 (ms)
	Bitrate          int              // audio encoding bitrate in kb/s can be 8 - 128
	PacketLoss       int              // expected packet loss percentage
	RawOutput        bool             // Raw opus output (no metadata or magic bytes)
	Application      AudioApplication // Audio application
	CoverFormat      string           // Format the cover art will be encoded with (ex "jpeg)
	CompressionLevel int              // Compression level, higher is better qualiy but slower encoding (0 - 10)
	BufferedFrames   int              // How big the frame buffer should be
	VBR              bool             // Wether vbr is used or not (variable bitrate)
	Threads          int              // Number of threads to use, 0 for auto
	StartTime        int              // Start Time of the input stream in seconds

	// The ffmpeg audio filters to use, see https://ffmpeg.org/ffmpeg-filters.html#Audio-Filters for more info
	// Leave empty to use no filters.
	AudioFilter string

	Comment string // Leave a comment in the metadata
}

EncodeOptions is a set of options for encoding dca

func (EncodeOptions) PCMFrameLen

func (e EncodeOptions) PCMFrameLen() int

func (*EncodeOptions) Validate

func (opts *EncodeOptions) Validate() error

Validate returns an error if the options are not correct

type EncodeSession

type EncodeSession struct {
	sync.Mutex
	// contains filtered or unexported fields
}

func EncodeFile

func EncodeFile(path string, options *EncodeOptions) (session *EncodeSession, err error)

EncodeFile encodes the file/url/other in path

func EncodeMem

func EncodeMem(r io.Reader, options *EncodeOptions) (session *EncodeSession, err error)

EncodedMem encodes data from memory

func (*EncodeSession) Cleanup

func (e *EncodeSession) Cleanup()

Cleanup cleans up the encoding session, throwring away all unread frames and stopping ffmpeg ensuring that no ffmpeg processes starts piling up on your system You should always call this after it's done

func (*EncodeSession) Error

func (e *EncodeSession) Error() error

Error returns any error that occured during the encoding process

func (*EncodeSession) FFMPEGMessages

func (e *EncodeSession) FFMPEGMessages() string

FFMPEGMessages returns messages printed by ffmpeg to stderr, you can use this to see what ffmpeg is saying if your encoding fails

func (*EncodeSession) FrameDuration

func (e *EncodeSession) FrameDuration() time.Duration

FrameDuration implements OpusReader, retruning the duratio of each frame

func (*EncodeSession) Options

func (e *EncodeSession) Options() *EncodeOptions

Options returns the options used

func (*EncodeSession) OpusFrame

func (e *EncodeSession) OpusFrame() (frame []byte, err error)

OpusFrame implements OpusReader, returning the next opus frame

func (*EncodeSession) Read

func (e *EncodeSession) Read(p []byte) (n int, err error)

Read implements io.Reader, n == len(p) if err == nil, otherwise n contains the number bytes read before an error occured

func (*EncodeSession) ReadFrame

func (e *EncodeSession) ReadFrame() (frame []byte, err error)

ReadFrame blocks until a frame is read or there are no more frames Note: If rawoutput is not set, the first frame will be a metadata frame

func (*EncodeSession) Running

func (e *EncodeSession) Running() (running bool)

Running returns true if running

func (*EncodeSession) Stats

func (e *EncodeSession) Stats() *EncodeStats

Stats returns ffmpeg stats, NOTE: this is not playback stats but transcode stats. To get how far into playback you are you have to track the number of frames sent to discord youself

func (*EncodeSession) Stop

func (e *EncodeSession) Stop() error

Stop stops the encoding session

func (*EncodeSession) Truncate

func (e *EncodeSession) Truncate()

Truncate is deprecated, use Cleanup instead this will be removed in a future version

type EncodeStats

type EncodeStats struct {
	Size     int
	Duration time.Duration
	Bitrate  float32
	Speed    float32
}

EncodeStats is transcode stats reported by ffmpeg

type ExtraMetadata

type ExtraMetadata struct{}

Extra metadata struct

type FFprobeFormat

type FFprobeFormat struct {
	FileName       string `json:"filename"`
	NumStreams     int    `json:"nb_streams"`
	NumPrograms    int    `json:"nb_programs"`
	FormatName     string `json:"format_name"`
	FormatLongName string `json:"format_long_name"`
	StartTime      string `json:"start_time"`
	Duration       string `json:"duration"`
	Size           string `json:"size"`
	Bitrate        string `json:"bit_rate"`
	ProbeScore     int    `json:"probe_score"`

	Tags *FFprobeTags `json:"tags"`
}

type FFprobeMetadata

type FFprobeMetadata struct {
	Format *FFprobeFormat `json:"format"`
}

type FFprobeTags

type FFprobeTags struct {
	Date        string `json:"date"`
	Track       string `json:"track"`
	Artist      string `json:"artist"`
	Genre       string `json:"genre"`
	Title       string `json:"title"`
	Album       string `json:"album"`
	Compilation string `json:"compilation"`
}

type Frame

type Frame struct {
	// contains filtered or unexported fields
}

type Metadata

type Metadata struct {
	Dca      *DCAMetadata    `json:"dca"`
	Opus     *OpusMetadata   `json:"opus"`
	SongInfo *SongMetadata   `json:"info"`
	Origin   *OriginMetadata `json:"origin"`
	Extra    *ExtraMetadata  `json:"extra"`
}

Base metadata struct

https://github.com/bwmarrin/dca/issues/5#issuecomment-189713886

type OpusMetadata

type OpusMetadata struct {
	Bitrate     int    `json:"abr"`
	SampleRate  int    `json:"sample_rate"`
	Application string `json:"mode"`
	FrameSize   int    `json:"frame_size"`
	Channels    int    `json:"channels"`
	VBR         bool   `json:"vbr"`
}

Opus metadata struct

Contains information about how the file was encoded with Opus.

type OpusReader

type OpusReader interface {
	OpusFrame() (frame []byte, err error)
	FrameDuration() time.Duration
}

type OriginMetadata

type OriginMetadata struct {
	Source   string `json:"source"`
	Bitrate  int    `json:"abr"`
	Channels int    `json:"channels"`
	Encoding string `json:"encoding"`
	Url      string `json:"url"`
}

Origin information metadata struct

Contains information about where the song came from, audio bitrate, channels and original encoding.

type SongMetadata

type SongMetadata struct {
	Title    string  `json:"title"`
	Artist   string  `json:"artist"`
	Album    string  `json:"album"`
	Genre    string  `json:"genre"`
	Comments string  `json:"comments"`
	Cover    *string `json:"cover"`
}

Song Information metadata struct

Contains information about the song that was encoded.

type StreamingSession

type StreamingSession struct {
	sync.Mutex
	// contains filtered or unexported fields
}

StreamingSession provides an easy way to directly transmit opus audio to discord from an encode session.

func NewStream

func NewStream(source OpusReader, vc *discordgo.VoiceConnection, done chan error) *StreamingSession

Creates a new stream from an Opusreader. source : The source of the opus frames to be sent, either from an encoder or decoder. vc : The voice connecion to stream to. done : If not nil, an error will be sent on it when completed.

func (*StreamingSession) Finished

func (s *StreamingSession) Finished() (bool, error)

Finished returns wether the stream finished or not, and any error that caused it to stop

func (*StreamingSession) Paused

func (s *StreamingSession) Paused() bool

Paused returns wether the sream is paused or not

func (*StreamingSession) PlaybackPosition

func (s *StreamingSession) PlaybackPosition() time.Duration

PlaybackPosition returns the the duration of content we have transmitted so far

func (*StreamingSession) SetPaused

func (s *StreamingSession) SetPaused(paused bool)

SetPaused provides pause/unpause functionality

Directories

Path Synopsis
cmd
dca

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL