Skip to content

Commit

Permalink
docs: Add function comments, no functional changes.
Browse files Browse the repository at this point in the history
  • Loading branch information
skidder committed Nov 28, 2024
1 parent 79f53a1 commit f02828a
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 86 deletions.
22 changes: 22 additions & 0 deletions avcodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const atomHeaderSize = 8
// Enable by building/running with "-ldflags=-X=github.com/discord/lilliput.hevcEnabled=true"
var hevcEnabled string

// avCodecDecoder handles decoding of various video/image formats using FFmpeg's avcodec.
type avCodecDecoder struct {
decoder C.avcodec_decoder
mat C.opencv_mat
Expand All @@ -26,6 +27,8 @@ type avCodecDecoder struct {
hasSubtitles bool
}

// newAVCodecDecoder creates a new decoder instance from the provided buffer.
// Returns an error if the buffer is too small or contains invalid data.
func newAVCodecDecoder(buf []byte) (*avCodecDecoder, error) {
mat := createMatFromBytes(buf)
if mat == nil {
Expand All @@ -48,18 +51,24 @@ func newAVCodecDecoder(buf []byte) (*avCodecDecoder, error) {
}, nil
}

// createMatFromBytes creates an OpenCV matrix from a byte buffer.
// The matrix is created as a single-channel 8-bit unsigned type.
func createMatFromBytes(buf []byte) C.opencv_mat {
return C.opencv_mat_create_from_data(C.int(len(buf)), 1, C.CV_8U, unsafe.Pointer(&buf[0]), C.size_t(len(buf)))
}

// hasSubtitles checks if the decoder has detected any subtitle streams.
func hasSubtitles(d C.avcodec_decoder) bool {
return bool(C.avcodec_decoder_has_subtitles(d))
}

// isStreamable determines if the media content can be streamed.
func isStreamable(mat C.opencv_mat) bool {
return bool(C.avcodec_decoder_is_streamable(mat))
}

// Description returns the format description of the media.
// Special handling is included to differentiate between MOV and MP4 formats.
func (d *avCodecDecoder) Description() string {
fmt := C.GoString(C.avcodec_decoder_get_description(d.decoder))

Expand All @@ -71,22 +80,27 @@ func (d *avCodecDecoder) Description() string {
return fmt
}

// HasSubtitles returns whether the media contains subtitle streams.
func (d *avCodecDecoder) HasSubtitles() bool {
return d.hasSubtitles
}

// IsStreamable returns whether the media content can be streamed.
func (d *avCodecDecoder) IsStreamable() bool {
return d.isStreamable
}

// BackgroundColor returns the default background color (white).
func (d *avCodecDecoder) BackgroundColor() uint32 {
return 0xFFFFFFFF
}

// LoopCount returns the number of times the media should loop (0 for no looping).
func (d *avCodecDecoder) LoopCount() int {
return 0
}

// ICC returns the ICC color profile data if present, or an empty slice if not.
func (d *avCodecDecoder) ICC() []byte {
iccDst := make([]byte, 8192)
iccLength := C.avcodec_decoder_get_icc(d.decoder, unsafe.Pointer(&iccDst[0]), C.size_t(cap(iccDst)))
Expand All @@ -96,10 +110,13 @@ func (d *avCodecDecoder) ICC() []byte {
return iccDst[:iccLength]
}

// Duration returns the total duration of the media content.
func (d *avCodecDecoder) Duration() time.Duration {
return time.Duration(float64(C.avcodec_decoder_get_duration(d.decoder)) * float64(time.Second))
}

// Header returns the image metadata including dimensions, pixel format, and orientation.
// Frame count is always 1 since it requires the entire buffer to be decoded.
func (d *avCodecDecoder) Header() (*ImageHeader, error) {
return &ImageHeader{
width: int(C.avcodec_decoder_get_width(d.decoder)),
Expand All @@ -111,6 +128,8 @@ func (d *avCodecDecoder) Header() (*ImageHeader, error) {
}, nil
}

// DecodeTo decodes the next frame into the provided Framebuffer.
// Returns io.EOF when no more frames are available.
func (d *avCodecDecoder) DecodeTo(f *Framebuffer) error {
if d.hasDecoded {
return io.EOF
Expand All @@ -136,16 +155,19 @@ func (d *avCodecDecoder) DecodeTo(f *Framebuffer) error {
return nil
}

// SkipFrame attempts to skip the next frame, but is not supported by this decoder.
func (d *avCodecDecoder) SkipFrame() error {
return ErrSkipNotSupported
}

// Close releases all resources associated with the decoder.
func (d *avCodecDecoder) Close() {
C.avcodec_decoder_release(d.decoder)
C.opencv_mat_release(d.mat)
d.buf = nil
}

// init initializes the avcodec library when the package is loaded.
func init() {
C.avcodec_init()
}
35 changes: 30 additions & 5 deletions giflib.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"unsafe"
)

// gifDecoder implements image decoding for GIF format
type gifDecoder struct {
decoder C.giflib_decoder
mat C.opencv_mat
Expand All @@ -25,6 +26,7 @@ type gifDecoder struct {
bgAlpha uint8
}

// gifEncoder implements image encoding for GIF format
type gifEncoder struct {
encoder C.giflib_encoder
decoder C.giflib_decoder
Expand All @@ -41,13 +43,15 @@ var (
ErrGifEncoderNeedsDecoder = errors.New("GIF encoder needs decoder used to create image")
)

// SetGIFMaxFrameDimension sets the largest GIF width/height that can be
// decoded
// SetGIFMaxFrameDimension sets the largest GIF width/height that can be decoded.
// This helps prevent loading extremely large GIF images that could exhaust memory.
func SetGIFMaxFrameDimension(dim uint64) {
// TODO we should investigate if this can be removed/become a mat check in decoder
atomic.StoreUint64(&gifMaxFrameDimension, dim)
}

// newGifDecoder creates a new GIF decoder from the provided byte buffer.
// Returns an error if the buffer is too small or contains invalid GIF data.
func newGifDecoder(buf []byte) (*gifDecoder, error) {
mat := C.opencv_mat_create_from_data(C.int(len(buf)), 1, C.CV_8U, unsafe.Pointer(&buf[0]), C.size_t(len(buf)))

Expand All @@ -68,6 +72,7 @@ func newGifDecoder(buf []byte) (*gifDecoder, error) {
}, nil
}

// Header returns the image header information including dimensions and pixel format.
func (d *gifDecoder) Header() (*ImageHeader, error) {
return &ImageHeader{
width: int(C.giflib_decoder_get_width(d.decoder)),
Expand All @@ -79,6 +84,7 @@ func (d *gifDecoder) Header() (*ImageHeader, error) {
}, nil
}

// FrameHeader returns the current frame's header information.
func (d *gifDecoder) FrameHeader() (*ImageHeader, error) {
return &ImageHeader{
width: int(C.giflib_decoder_get_frame_width(d.decoder)),
Expand All @@ -90,40 +96,46 @@ func (d *gifDecoder) FrameHeader() (*ImageHeader, error) {
}, nil
}

// Close releases resources associated with the decoder.
func (d *gifDecoder) Close() {
C.giflib_decoder_release(d.decoder)
C.opencv_mat_release(d.mat)
d.buf = nil
}

// Description returns the image format description ("GIF").
func (d *gifDecoder) Description() string {
return "GIF"
}

// IsStreamable returns whether the format supports streaming decoding.
func (d *gifDecoder) IsStreamable() bool {
return true
}

// HasSubtitles returns whether the format supports subtitles.
func (d *gifDecoder) HasSubtitles() bool {
return false
}

// ICC returns the ICC color profile data, if any.
func (d *gifDecoder) ICC() []byte {
return []byte{}
}

// Duration returns the total duration of the GIF animation.
func (d *gifDecoder) Duration() time.Duration {
return time.Duration(0)
}

// BackgroundColor returns the GIF background color as a 32-bit RGBA value.
func (d *gifDecoder) BackgroundColor() uint32 {
d.readAnimationInfo()
return uint32(d.bgRed)<<16 | uint32(d.bgGreen)<<8 | uint32(d.bgBlue) | uint32(d.bgAlpha)<<24
}

// readAnimationInfo reads the GIF info from the decoder and caches it
// this involves reading extension blocks and is relatively expensive
// so we only do it once when we need it
// readAnimationInfo reads and caches GIF animation metadata like loop count and frame count.
// This is done lazily since reading extension blocks is relatively expensive.
func (d *gifDecoder) readAnimationInfo() {
if !d.animationInfoRead {
info := C.giflib_decoder_get_animation_info(d.decoder)
Expand All @@ -137,16 +149,21 @@ func (d *gifDecoder) readAnimationInfo() {
}
}

// LoopCount returns the number of times the GIF animation should loop.
// A value of 0 means loop forever.
func (d *gifDecoder) LoopCount() int {
d.readAnimationInfo()
return d.loopCount
}

// FrameCount returns the total number of frames in the GIF.
func (d *gifDecoder) FrameCount() int {
d.readAnimationInfo()
return d.frameCount
}

// DecodeTo decodes the next GIF frame into the provided Framebuffer.
// Returns io.EOF when all frames have been decoded.
func (d *gifDecoder) DecodeTo(f *Framebuffer) error {
h, err := d.Header()
if err != nil {
Expand Down Expand Up @@ -188,6 +205,8 @@ func (d *gifDecoder) DecodeTo(f *Framebuffer) error {
return nil
}

// SkipFrame skips decoding of the next frame.
// Returns io.EOF when all frames have been skipped.
func (d *gifDecoder) SkipFrame() error {
nextFrameResult := int(C.giflib_decoder_skip_frame(d.decoder))

Expand All @@ -201,6 +220,8 @@ func (d *gifDecoder) SkipFrame() error {
return nil
}

// newGifEncoder creates a new GIF encoder that will write to the provided buffer.
// Requires the original decoder that was used to decode the source GIF.
func newGifEncoder(decodedBy Decoder, buf []byte) (*gifEncoder, error) {
// we must have a decoder since we can't build our own palettes
// so if we don't get a gif decoder, bail out
Expand All @@ -227,6 +248,8 @@ func newGifEncoder(decodedBy Decoder, buf []byte) (*gifEncoder, error) {
}, nil
}

// Encode encodes a frame into the GIF. If f is nil, flushes any remaining data
// and returns the complete encoded GIF. Returns io.EOF after flushing.
func (e *gifEncoder) Encode(f *Framebuffer, opt map[int]int) ([]byte, error) {
if e.hasFlushed {
return nil, io.EOF
Expand Down Expand Up @@ -259,10 +282,12 @@ func (e *gifEncoder) Encode(f *Framebuffer, opt map[int]int) ([]byte, error) {
return nil, nil
}

// Close releases resources associated with the encoder.
func (e *gifEncoder) Close() {
C.giflib_encoder_release(e.encoder)
}

// init initializes the GIF decoder with default settings
func init() {
SetGIFMaxFrameDimension(defaultMaxFrameDimension)
}
Loading

0 comments on commit f02828a

Please sign in to comment.