tkVideoPlayer
tkVideoPlayer copied to clipboard
Can we seek the video based on video frame?
tkVideoPlayer only seeks in seconds, but I want to seek the video based on frame number of video, can we do this with tkVideoPlayer and also the progress bar based on the number of frames?
Hello @Akascape, You can surely make a progress bar based on frames, we have <<FrameGenerated>>
virtual event that is generated whenever a new frame is read from the file. The ability to seek to specific frame was removed from version 2.0.0 . I would strongly recommend not to downgrade from 2.0.0 as there were memory problems with prior versions of 2.0.0. If you can wait till tomorrow I can come up with something to seek specific frames, as I am busy with some work now. Thank you.
@Akascape In the meantime can you check this algorithm?
sec = (1/frame_rate) * number_of _frames_to_skip
You can get the frame_rate
from the video_info()
method you can then pass the sec to seek()
method.
Let me know if it works
ok thanks, I would like to give some more information on a project I am currently working, Actually I want to implement a video player in a program. You can see how it looks:
What I want is the right window to show the video frame and the slider to seek the video frame
@Akascape Your project looks Cool! Also, Did you check out this example? https://github.com/PaulleDemon/tkVideoPlayer/blob/master/examples/sample_player.py , though it does not seek specific frames, it would give you an idea of how I implemented something similar.
Yes, I took the reference from there only.
@Akascape In the meantime can you check this algorithm?
sec = (1/frame_rate) * number_of _frames_to_skip
You can get the
frame_rate
from thevideo_info()
method you can then pass the sec toseek()
method.Let me know if it works
Alright Try this then and let me know.
BTW what about the sound? This doesn't matter much in my project but still wanted to know if you are working on it.
@Akascape I am currently not working on it as I have another major project that I am working on. The major problem with adding audio would be syncing audio with video. The audio would have to run on a different thread. If you want audio you can try the code in the following link: https://stackoverflow.com/a/69041999/15993687 I have kept this issue so I can come back to it when I have enough time.
@Akascape In the meantime can you check this algorithm?
sec = (1/frame_rate) * number_of _frames_to_skip
You can get the
frame_rate
from thevideo_info()
method you can then pass the sec toseek()
method. Let me know if it worksAlright Try this then and let me know.
I tried this in the test video player,
I just created a new seek frame button and added the lines as you mentioned.
Though it is seeking to the part based on the frame number, but I think it doesn't seek to the exact frame as only integer value can be passed to the seek()
method.
But I hope you can do something...
@Akascape Unfortunately the underlying library pyav that this library is using doesn't allow us to pass floating points to the seek method: https://pyav.org/docs/develop/api/container.html#av.container.InputContainer.seek. The closest answer that I got is https://stackoverflow.com/a/17549987/15993687 , but it's written for c, translating it might be a hassle for me. Let me know if you have any solutions in mind. I'll keep this issue open and if I find any resources that can help me I'll update the library.
Hello @Akascape, You can surely make a progress bar based on frames, we have
<<FrameGenerated>>
virtual event that is generated whenever a new frame is read from the file.The ability to seek to specific frame was removed from version 2.0.0
. I would strongly recommend not to downgrade from 2.0.0 as there were memory problems with prior versions of 2.0.0. If you can wait till tomorrow I can come up with something to seek specific frames, as I am busy with some work now. Thank you.
How it was done in the older versions?
BTW, how can we remove the video after using stop() method? When I close the video then the video frame is not removed and stays there in top of the label.
@Akascape In the previous versions, the entire video was loaded into the memory, this caused memory issues even a small video file of 16 MB took around 3 GB of memory space, so it was removed.
As for the removal of the image after stop, try the below, Now you probably need to unbind the ended event depending on what triggered it.
import tkinter as tk
from tkVideoPlayer import TkinterVideo
from PIL import ImageTk, Image
def ended(e):
# tkvideo.config(image="")
tkvideo._current_img = Image.new("RGBA", tkvideo.video_info()["framesize"], (255, 0, 0, 0))
tkvideo.config(image=ImageTk.PhotoImage(tkvideo._current_img))
def skip():
tkvideo.stop()
root = tk.Tk()
tkvideo = TkinterVideo(scaled=True, master=root)
tkvideo.load(r"test.mp4")
tkvideo.pack(expand=True, fill="both")
tkvideo.play() # play the video
tkvideo.bind("<<Ended>>", ended)
btn = tk.Button(master=root, text="Stop", command=skip)
btn.pack()
root.mainloop()
Ok, thanks again for the help...
@Akascape Unfortunately the underlying library pyav that this library is using doesn't allow us to pass floating points to the seek method: https://pyav.org/docs/develop/api/container.html#av.container.InputContainer.seek. The closest answer that I got is https://stackoverflow.com/a/17549987/15993687 , but it's written for c, translating it might be a hassle for me. Let me know if you have any solutions in mind. I'll keep this issue open and if I find any resources that can help me I'll update the library.
@PaulleDemon Can we add a different seek function that can take float values. I don't know much about the pyav stuff but as you provided the documentation of pyav, I saw some parameters in seek method like any_frame, stream etc which says that it can seek to any frame of the video. Maybe we can transfer float values after making any_frame=True?
@Akascape Unfortunately that won't work as seek parameter only accepts integer values. It will raise errors if a floating value is passed. Also, note I have updated the previous code that removes image from the label after stopping. The update prevents label from going back to previous image when resized.
As there is no other seek method that takes us to particular frame, for now I will just change the whole frame stuff to seconds.
I had the same problem. My goal is to take screenshots/images from the current video position up to the next Y seconds, with X images taken per second.
It feels more than dirty, but this is my solution. Internally, tkVideoPlayer get's the next frame from current position with:
vid_player = TkinterVideo(scaled=True, master=root, keep_aspect=True)
frame = next(vid_player._container.decode(video=0))
So I got the current frame and looping over until I get the desired one:
frame = next(vid_player._container.decode(video=0))
for _ in range(skip_amount): # skipping to desired frame
frame = next(vid_player._container.decode(video=0))
img = frame.to_image()
After that, I have the wanted frame.
Hope that help's a bit. Im sure there is a better way to do it (without getting every 'next' frame, but I failed to do it with itertools.islice())
I've noticed that you multiply the _seek_sec
by 1 000 000.
I assume that this is to convert to microseconds, you have stated that it is to correct the frame.
self._container.seek(self._seek_sec*1000000 , whence='time', backward=True, any_frame=False) # the seek time is given in av.time_base, the multiplication is to correct the frame
self._frame_number = self._video_info["framerate"] * self._seek_sec
If this is the case, would it not be possible to convert the seek
method to take an microsecond: int
parameter, instead of sec: int
?
@DylanKirbs I am a little busy ryt now, Can you check this out in the mean time and let me know if you still have questions: https://pyav.org/docs/develop/api/container.html#av.container.InputContainer.seek and https://pyav.org/docs/develop/api/time.html
Thank you.