如何在 haskell 中使用 ffmpeg-light 查找 mp4 元数据?
How to find mp4 metadata with ffmpeg-light in haskell?
我正在使用 ffmpeg-light、JuicyPixels 和 gloss 来显示带有 Haskell 的视频。我想找到我正在自动播放的视频的元数据,但我还没有找到这样做的方法。
我想访问视频的分辨率和帧率等元数据。
你能帮帮我吗?
编辑:
我已经尝试过@CRDrost 的解决方案,但视频现在以 2 倍正常速度播放。我假设函数 imageReaderTime 给出了错误的时间戳。
编辑 2:
播放速度异常是ffmpeg-light库的bug。我在 github 存储库中打开了一个 issue。
我更新的代码:
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) 我 认为 void cleanup
是多余的,只是 cleanup
有效,但我喜欢你不是 100% 确定 IO ()
值确实如此。
我没有看到读取 FPS 的直接方法,但是 imageReaderTime
以秒为单位生成时间戳,这将为您提供一个很好的指标。要传播时间戳,您需要修改:
nextFrame :: IO (Maybe (Image PixelRGB8, Double)) -> IO (Double, Picture)
nextFrame getFrame = fmap fromImageRGB8 . swap . fromJust <$> getFrame
那你会说:
stampedFrames <- replicateM frameCount $ nextFrame getFrame
let (tstamps, frames) = unzip stampedFrames
let approx_fps = fromIntegral (length tstamps) / (maximum tstamps - minimum tstamps)
最后,您可以将 approx_fps
作为参数传递给 frameAt
,这将必须使用 Double
而不是 Float
或某些类型强制函数。
但是,对于您正在做的事情,可能更好的做法是:
frameAt :: [(Double, Picture)] -> Double -> Picture
frameAt list time = snd . head . dropWhile ((< time) . fst) $ list
这会获取列表,删除第一个元素(时间戳)小于请求时间的所有元素,然后 returns 之后出现的第一对中的第二个元素(图片)。 不需要猜测 FPS。
我正在使用 ffmpeg-light、JuicyPixels 和 gloss 来显示带有 Haskell 的视频。我想找到我正在自动播放的视频的元数据,但我还没有找到这样做的方法。
我想访问视频的分辨率和帧率等元数据。
你能帮帮我吗?
编辑:
我已经尝试过@CRDrost 的解决方案,但视频现在以 2 倍正常速度播放。我假设函数 imageReaderTime 给出了错误的时间戳。
编辑 2:
播放速度异常是ffmpeg-light库的bug。我在 github 存储库中打开了一个 issue。
我更新的代码:
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) 我 认为 void cleanup
是多余的,只是 cleanup
有效,但我喜欢你不是 100% 确定 IO ()
值确实如此。
我没有看到读取 FPS 的直接方法,但是 imageReaderTime
以秒为单位生成时间戳,这将为您提供一个很好的指标。要传播时间戳,您需要修改:
nextFrame :: IO (Maybe (Image PixelRGB8, Double)) -> IO (Double, Picture)
nextFrame getFrame = fmap fromImageRGB8 . swap . fromJust <$> getFrame
那你会说:
stampedFrames <- replicateM frameCount $ nextFrame getFrame
let (tstamps, frames) = unzip stampedFrames
let approx_fps = fromIntegral (length tstamps) / (maximum tstamps - minimum tstamps)
最后,您可以将 approx_fps
作为参数传递给 frameAt
,这将必须使用 Double
而不是 Float
或某些类型强制函数。
但是,对于您正在做的事情,可能更好的做法是:
frameAt :: [(Double, Picture)] -> Double -> Picture
frameAt list time = snd . head . dropWhile ((< time) . fst) $ list
这会获取列表,删除第一个元素(时间戳)小于请求时间的所有元素,然后 returns 之后出现的第一对中的第二个元素(图片)。 不需要猜测 FPS。