package opusframes // Package to encode/decode .of files. // The .of file format will allow us to cache/preprocess // files from the queue and save it in a format that we // can quickly read and send to discord. // The format spect is very simple: // frame size: 2 bytes // frame data: Up to 1275 bytes // data will be stored in little endian format import ( "bytes" "encoding/binary" "errors" "io" "log" "os" "os/exec" ) type ErrFFMPEG error type ErrEncoding error // TODO: Allow streaming frames as they are encoded instead // of having to wait for the whole file. // We can probably do this by taking an io.Reader as input // instead of just a file path. Then we return an io.Writer // and allow the user to either stream each encoded frame // or save it to a file. We could also make the Decode // function take an io.Reader so these functions can // just be piped into each other. // TODO: Can we pack the frames into chunks of 960? I wonder // if that would make playback smoother or just fuck the // audio. We can try it at some point. func Encode(input, output string) error { ffmpeg := exec.Command( "ffmpeg", "-i", input, "-hide_banner", "-loglevel", "quiet", "-f", "data", "-map", "0:a", "-ar", "48k", "-ac", "2", "-acodec", "libopus", "-b:a", "128k", "pipe:1") ffmpegOut, err := ffmpeg.StdoutPipe() if err != nil { return ErrFFMPEG(err) } err = ffmpeg.Start() if err != nil { return ErrFFMPEG(err) } var fileBuffer bytes.Buffer for { p := make([]byte, 960) n, err := ffmpegOut.Read(p) binary.Write(&fileBuffer, binary.LittleEndian, uint16(n)) binary.Write(&fileBuffer, binary.LittleEndian, p[:n]) log.Printf("Frame of: %d", n) if err != nil { if err == io.EOF { break } return ErrEncoding(err) } } f, err := os.OpenFile(output, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644) if err != nil { log.Println(err) } defer f.Close() f.Write(fileBuffer.Bytes()) return nil } var ErrMalformedFile error = errors.New("malformed file") // TODO: Allow streaming frames as they are decoded instead // of having to decode the whole file. func Decode(input string) ([][]byte, error){ f, err := os.Open(input) if err != nil { return nil, err } defer f.Close() // We read the first 2 bytes for the length // then n bytes after that until we're done frames := [][]byte{} for { var frameSize uint16 err = binary.Read(f, binary.LittleEndian, &frameSize) if err != nil { if err == io.EOF { return frames, nil } return nil, err } frame := make([]byte, frameSize) n, err := io.ReadFull(f, frame) if n != int(frameSize) { return nil, ErrMalformedFile } if err != nil { return nil, err } frames = append(frames, frame) } }