I'm using ffmpeg-light, JuicyPixels and gloss to display a video with Haskell. I want to find the metadata of videos I'm playing automatically, but I have not yet found a way to do so.
I would like to access metadata like the resolution and the framerate of the video.
Can you help me?
EDIT:
I have tried your solution @CRDrost, but the video is now playing at 2x normal speed. I assume the function imageReaderTime is giving the wrong timestamps.
EDIT 2:
The abnormal playing speed is a bug in the ffmpeg-light library. I've opened an issue at the github repository.
My updated code:
import Graphics.Gloss
import Codec.FFmpeg
import Codec.FFmpeg.Juicy
import Codec.Picture
import Control.Applicative
import Data.Maybe
import Graphics.Gloss.Juicy
import Control.Monad
-- import System.IO.Unsafe (unsafePerformIO)-- for debugging purposes
resolution :: (Int,Int)
resolution = (640, 360)
frameCount :: Int
frameCount = 100
main :: IO ()
main = do
initFFmpeg
(getFrame, cleanup) <- imageReaderTime "big_buck_bunny.mp4"
frames <- replicateM frameCount $ nextFrame getFrame
cleanup
animate (InWindow "Nice Window" resolution (10,10)) white (frameAt frames)
nextFrame :: IO (Maybe (Image PixelRGB8, Double)) -> IO (Picture, Float)
nextFrame getFrame = mapSnd realToFrac . mapFst fromImageRGB8 . fromJust <$> getFrame
frameAt :: [(Picture, Float)] -> Float -> Picture
frameAt list time = fst . head . dropWhile ((< time) . snd) $ list
mapFst :: (a -> c) -> (a, b) -> (c, b)
mapFst f (a, b) = (f a, b) -- applies f to first element of a 2-tuple
mapSnd :: (b -> c) -> (a, b) -> (a, c)
mapSnd f (a, b) = (a, f b) -- applies f to the second element of a 2-tuple
(a) I think
void cleanup
is redundant and justcleanup
works, but I like you am not 100% sure what thatIO ()
value does precisely.I don't see a direct way to read FPS, but
imageReaderTime
produces timestamps in seconds, which would give you a good indicator. To propagate the timestamp you'll need to modify:Then you would say:
Finally you can pass
approx_fps
as a parameter toframeAt
, which will have to useDouble
rather thanFloat
or else some type-coercing function.However, for what you're doing, what might be better is to have something like:
This takes the list, drops all elements whose first element (timestamp) is less than the requested time, and then returns the second element (picture) of the first pair that occurs after that. No FPS guessing is needed.