Open JonnyBurger opened 3 months ago
hey hey hey⦠good weekend project and learning experience if you wanna assign!
Hey @JonnyBurger would love to take a look at this. Thanks!
Hey, I give it to @Just-Moh-it because he wrote first. Happy to give you the next one @fitzmode!
π This issue has a bounty on it!
Read our contributing guidelines:
/bounty 500
There should be a new component added to the core
package (remotion
) called <AnimatedImage>
.
It should work similarly to the @remotion/gif
package - fetch the file and parse the frames and display the current frame based on useCurrentFrame()
.
The @remotion/gif
package has a separate implementation for development and rendering - this is not necessary.
When fetching a URL, the component should take a look at the Content-Type
header. We will support three headers:
image/gif
(plenty of sample GIFs out there)image/webp
(sample image: https://mathiasbynens.be/demo/animated-webp-supported.webp) image/avif
(sample image: https://colinbendell.github.io/webperf/animated-gif-decode/2.avif)All work the same, just the type
in ImageDecoder
needs to be adjusted.
Other content types should be rejected.
There should be a good error message if ImageDecoder
is not available (e.g. Firefox)
There should be a documentation page that aligns with other docs in the remotion
package.
/attempt #4175
below to get assigned/claim #4175
in the PR body to claim the bountyThank you for contributing to remotion-dev/remotion!
Hey, would love to have a look at this, I'm in the row.
Hi, would like to work on this issue. I'm in the queue.
Am also available to start working on this if chanced.
/attempt #4175
@amochuko: Another person is already attempting this issue. Please don't start working on this issue unless you were explicitly asked to do so.
@JonnyBurger Can i get this one assigned ,i am really excited to get this component done
update: finishing this up, closing in!
@Just-Moh-it can you open a Draft PR with the progress so far? maybe we can help finishing it
Lemme give it a Trryyy!
@j4nlksh: Another person is already attempting this issue. Please don't start working on this issue unless you were explicitly asked to do so.
@JonnyBurger lmk if this issue is up for grabs.
@Just-Moh-it Are you still on this?
@JonnyBurger since the issue still open I would like to contribute. Can you please assign this for me
Whats status here ? Is this open for anyone to work ? @JonnyBurger
I needed this, so I did create it on my own. Works with gif, webp and avif, if browser not supported or wrong content-type then falls back to use <Img />
instead.
Feel free to use it in remotion, or if still needed I could make this meet all the requirements and write the docs to solve this issue.
import { CSSProperties, useEffect, useMemo, useRef, useState } from 'react'
import { Img, continueRender, delayRender, useCurrentFrame, useVideoConfig } from 'remotion'
type ImageDecoderInit = {
type: string
data: ReadableStream | ArrayBuffer | ArrayBufferView | Blob | string | null
}
type ImageDecodeResult = {
image: VideoFrame
complete: boolean
}
type ImageDecoderVideoTrack = {
selected: boolean
frameCount: number
repetitionCount: number
frameSize: {
width: number
height: number
}
}
type ImageDecoderTracks = {
selectedIndex: number
selectedTrack: ImageDecoderVideoTrack
}
declare class ImageDecoder {
constructor(init: ImageDecoderInit)
readonly tracks: ImageDecoderTracks
readonly completed: Promise<void>
decode(options?: { frameIndex?: number }): Promise<ImageDecodeResult>
reset(): void
close(): void
}
const ANIMATED_IMAGE_CONTENT_TYPES = ['image/gif', 'image/webp', 'image/avif']
type AnimatedImageMetadata = {
width: number
height: number
fps: number | null
frameCount: number
}
const useAnimatedImage = (src: string) => {
const [handle] = useState(delayRender)
const [decoder, setDecoder] = useState<ImageDecoder>()
const [metadata, setMetadata] = useState<AnimatedImageMetadata>()
useEffect(() => {
const effect = async () => {
if (typeof ImageDecoder === 'undefined') return console.error('ImageDecoder not available in this browser!')
const response = await fetch(src)
const contentType = response.headers.get('Content-Type')
if (!contentType || !ANIMATED_IMAGE_CONTENT_TYPES.includes(contentType))
return console.error(`Content type '${contentType}' is not supported!`)
const decoder = new ImageDecoder({ data: response.body, type: contentType })
await decoder.completed
const frameCount = decoder.tracks.selectedTrack.frameCount
// Decoding the first frame to get metadata
const { image } = await decoder.decode({ frameIndex: 0 })
const height = image.displayHeight
const width = image.displayWidth
// image.duration can be null if it's non-animated image
const fps = image.duration ? 1000000 / image.duration : null
setDecoder(decoder)
setMetadata({ frameCount, height, width, fps })
}
effect().then(() => continueRender(handle))
}, [src])
return { decoder, metadata }
}
type AnimatedImageProps = {
src: string
style?: CSSProperties
loopBehavior?: 'loop' | 'pause-after-finish'
playbackRate?: number
}
export const AnimatedImage = ({ src, style, loopBehavior = 'loop', playbackRate = 1 }: AnimatedImageProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const { fps } = useVideoConfig()
const frame = useCurrentFrame()
const { decoder, metadata } = useAnimatedImage(src)
const frameIndex = useMemo(() => {
if (!metadata || !metadata.fps) return 0
const currentFrame =
loopBehavior === 'loop'
? (frame * playbackRate) % metadata.frameCount
: Math.min(frame * playbackRate, metadata.frameCount)
return Math.floor((currentFrame * metadata.fps) / fps)
}, [frame, playbackRate, metadata, loopBehavior])
useEffect(() => {
const renderFrame = async () => {
const canvas = canvasRef.current
if (!decoder || !canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
const { image } = await decoder.decode({ frameIndex })
ctx.drawImage(image, 0, 0)
}
const handle = delayRender()
renderFrame().then(() => continueRender(handle))
}, [decoder, frameIndex])
if (!metadata) return <Img src={src} style={style} />
return <canvas ref={canvasRef} width={metadata.width} height={metadata.height} style={style} />
}
TIL about
ImageDecoder
: https://developer.mozilla.org/en-US/docs/Web/API/ImageDecoderSample:
Crazy that it works with animated GIFs, AVIFs and animated WebPs!
We can re-implement the
Gif
component with it and also support other image formats!