Как найти метаданные mp4 с помощью ffmpeg-light в haskell?

Я использую ffmpeg-light, JuicyPixels и глянец для отображения видео с помощью Haskell. Я хочу найти метаданные видео, которое я играю автоматически, но я еще не нашел способ сделать это.

Я хотел бы получить доступ к метаданным, таким как разрешение и частота кадров видео.

Вы можете мне помочь?

РЕДАКТИРОВАТЬ:

Я попробовал ваше решение @CRDrost, но теперь видео воспроизводится с 2-кратной нормальной скоростью. Я предполагаю, что функция imageReaderTime дает неправильные метки времени.

РЕДАКТИРОВАТЬ 2:

Ненормальная скорость воспроизведения - это ошибка в библиотеке ffmpeg-light. Я открыл проблему в репозитории github.

Мой обновленный код:

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

1 ответ

Решение

(а) я думаю 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

Он берет список, удаляет все элементы, чей первый элемент (временная метка) меньше запрашиваемого времени, а затем возвращает второй элемент (изображение) первой пары, которая появляется после этого. Не нужно угадывать FPS.

Другие вопросы по тегам