Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Add function comments, no functional changes. #203

Merged
merged 1 commit into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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