JuliaIO / VideoIO.jl

Reading and writing of video files in Julia via ffmpeg
https://juliaio.github.io/VideoIO.jl/stable
Other
125 stars 53 forks source link

Dealing with start time larger than zero #334

Open yakir12 opened 2 years ago

yakir12 commented 2 years ago

Some videos have a start time that is larger than zero. So after reading the first frame, gettime returns a number that is much larger than expected (>>1/fps). This is very common when the recording camera split the file into ~2GB segments. In such cases, a video file will have a starting time equal to the total amount of time all the previous segments had.

It becomes a problem because most video players show the current playing time relative to the beginning of the video, i.e. zero. So if a user wants to inspect an event in the video, the time they make a note of is "wrong" and needs to be adjusted to the starting time of the video.

Is there a way to retrieve that information?

Currently I do this:

vid = VideoIO.openvideo(file)
read(vid);
t0 = gettime(vid) # now equals to the real time stamp

I can use cli ffprobe to get to that data:

ffprobe -v error -show_entries format=start_time -of default=noprint_wrappers=1:nokey=1 -i file.MTS
yakir12 commented 2 years ago

After some careful thought, I think that there are two distinct cases here:

  1. a user has a video that s/he knows little about, tries to seek(vid, t) where t is smaller than the starting time of the video, and gets the same first frame from the video regardless of t (as long as t is smaller than the starting time).
  2. a user has a video that has been segmented into n video-files, and wants to seek into the video, either in "glabal" video-time, or in "individual" file time. This user is looking for a convenient way to do this.

Case number 1 can/should be addressed with a simple warning/info box in the documentation/FAQ.

Case number 2 warrants a more careful approach/API. Perhaps in a separate package even.

yakir12 commented 2 years ago

I'll polish this some more tomorrow, but the following naive implementation could solve case number 2:

import VideoIO: seek, read

mutable struct SegmentedVideo
  files::Vector{String}
  videos::Vector{VideoIO.StreamContext}
  cumdur::Vector{Float64}
  current::Int
  SegmentedVideo(files::Vector{String}) = new(files, VideoIO.openvideo.(files), cumsum(VideoIO.get_duration(file) for file in files[1:end-1]), 1)
end

function VideoIO.seek(v::SegmentedVideo, t::Number)
  v.current = findfirst(<(t), v.cumdur)
  seek(v.videos[v.current], t)
end

function VideoIO.seek(v::SegmentedVideo, file::String, t::Number)
  v.current = findfirst(==(file), v.files)
  seek(v.videos[v.current], v.cumdur[v.current - 1] + t)
end

function VideoIO.read(v::SegmentedVideo) 
  if eof(v.videos[v.current]) && v.current < length(v.videos)
    v.current += 1
  end
  read(v.videos[v.current])
end