Hello, I have what I think should be a simple problem, but I'm stuck! Most of the related questions I found while googling were posts on this forum, so I hope someone here will take pity on me and help me out!
The problem:
I have several hundred (or actually probably several thousand) .avi videos that were recorded as part of a scientific experiment. They were recorded using a high speed camera, and the frame rate should be 5 kHz (5000 frames per second), but they have been saved incorrectly, as though the frame rate was actually 1fps. So each video should be a second or two long, but instead they play very slowly and are a couple hours long. (This mistake was made by someone else and now I am responsible for analyzing these very slow videos.) I do not need to change anything other than the frame rate. There is no audio track. I do not want to add or subtract any frames, I only want to correct the frame rate so that a regular video player will play it at the correct speed.
What I have tried:
I have managed to use VirtualDub2 to fix a single video, so I think the batch process/job control feature *should* work, but I think I just don't understand the GUI. Could somebody please explain it like I'm 5?
Here is what I am doing:
1 - Open VirtualDub2
2 - File > Open video file... --> choose my first file (let's say it's called trial_001.avi), video currently plays extremely slowly
3 - Video > Frame rate... > Change frame to (fps): --> enter 5000 and click "OK", video now plays at correct speed in the GUI
4 - File > Save video... --> save it with a different file name (videosEdited\trial_001.avi), new file now plays at correct speed in VirtualDub2 or other players
5 - File > Save processing settings... --> call it something like changeFR.vdscript
6 - File > Queue batch operation > Batch Wizard --> Select "Route outputs to a different folder" (if I was doing it right, I would be happy to overwrite these because I have a backup elsewhere, but for testing I've been trying to use a different folder), then "Add to Queue > Run video analysis pass", then I drag & drop my next file (trial_002.avi) and press OK and now I can see that file name in the Job Control panel.
7 - Press "Start" in Job Control panel, video switches to In Progress and then Done.
8 - There is no new file in my different folder, and the old file still plays slowly, so it seems like it didn't actually do anything. I have tried the "File > Process directory..." option in the Job Control panel in case that is where I am supposed to use my script from step 5, but it doesn't see the file in the folder where I saved it.
What am I doing wrong? I am sure I am making a stupid mistake somewhere.
Also, if there is a better tool than VirtualDub2 I would love to know. I don't know very much about video, but I am okay at Python and have done other command line stuff before (I'm more comfortable in linux than Windows but I have to use Windows for this project for reasons outside my control).
Thank you to anyone who took the time to read this!
+ Reply to Thread
Results 1 to 14 of 14
-
Last edited by cluelessScientist; 19th Feb 2023 at 13:20. Reason: Replaced some information that I had accidentally deleted when trying to edit my post for clarity
-
From what it looks like you'd have to transcode all the videos with new framerate. I suggest to try different approach:
1) Remux .avi files into .mov files
2) Conform .mov files to desired fps without re-encoding, with the help of mp4fpsmod tool
Both operations are avail as presets in my customized pack of tools (go to the Converter\Quick fix\ folder) -
The video frame rate in an AVI file is specified by a pair of numbers, a numerate and a denominator, each four bytes long, in Intel little endian format. You can easily locate and change those with a hex editor (or write a program to do it). Can you upload a sample of a file you want to change?
-
jack_666
you may find this helpful
This batch file should do what you want.
Put the AVI clips and the 2 programs in the same folder.
ffmpeg.exe makes a copy of the input in the 'New\' folder, deleting any audio.
AVIfrate.exe (command line version) modifies the New\????.avi
The original is untouched. (Yeah right! Make another backup.)
Code:if not exist New\ md New for %%a in (*.avi) do call :process "%%a" goto :end :process ffmpeg.exe -i "%~nx1" -c copy -an -threads 0 -y "New\%~nx1" avifrate.exe "New\%~nx1" -setfps 5000 1 goto :eof :end
-
Besides that avifrate /not tested it though/ , I tried to do it with the code,
that frame rate is stored on 32nd byte in the file (in my case), could be retrieved in Python:Code:filepath = r'F:\video\video.avi' with open(filepath,'rb') as f: f.seek(32,0) delay_in_microseconds = int.from_bytes(f.read(4), byteorder='little') fps = 10**6/delay_in_microseconds print(fps)
but if I tried to overwrite it by double fps (60fps) , in reverse workflow:
Code:delay_in_microseconds = 16667 #1000000/60 bytes_val = delay_in_microseconds.to_bytes(4, 'little') with open(filepath,'r+b') as f: f.seek(32,0) f.write(bytes_val)
-
The "microseconds per frame" value you are changing is in the the AVIMAINHEADER:
https://learn.microsoft.com/en-us/previous-versions/windows/desktop/api/Aviriff/ns-avi...-avimainheader
Most players don't use that value but rather dwRate/dwScale in the AVIStreamHeader:
https://learn.microsoft.com/en-us/previous-versions/windows/desktop/api/avifmt/ns-avif...vistreamheader
You must set those two values as well.
Beware that the AVIStreamHeader may not always be at the same location. Best practice would be to search for the structure by name.Last edited by jagabo; 20th Feb 2023 at 06:59.
-
I am probably way of course here but it is still worth a mention.
Surely the whole purpose of filming at a very high frame rate is to slow down the footage to, hopefully, see things that would not be seen at a more 'normal' frame rate.
So if you change the frame rate to the 'norm', or in this case what you shot it at, you are not going to see what you intended to see.
So, yes, the true playback speed should be very slow. -
Yes, obviously no display can show 5000 frames per second. And even if you had such a display you couldn't really perceive such a high motion rate as anything more than a blur. A playback frame rate of maybe 50 or 60 fps might be ok. That would give smooth motion and and one second worth of 5000 fps frames would play in a minute and a half or so.
-
Thanks jagabo for links,
so for those preppers who make even toilet paper themselves and using Swiss knives (like a Python):
That avi header and stream header could be read and modified, like below. For head header I searched for "avih" string and got offset to where a should start to read from. Then for stream header a searched for string "vids" and then got offset as well.
Original video was 30fps avi so I changed it to 60fps like this:
In header I changed "dwMicroSecPerFrame" to 16667 (that's 1 000 000 /60fps) instead of 33333
Then in strem header I modified "dwScale" and "dwRate" to 1000000 and 16667 (instead of original 1000000 and 33333)
Code:import pprint avi_path = r'F:/video/video.AVI' size_in_bytes = {'FOURCC':4, 'WORD':2, 'DWORD':4} AVI_HEADER = { 'fcc': 'FOURCC', 'cb': 'DWORD', 'dwMicroSecPerFrame': 'DWORD', 'dwMaxBytesPerSec': 'DWORD', 'dwPaddingGranularity': 'DWORD', 'dwFlags': 'DWORD', 'dwTotalFrames': 'DWORD', 'dwInitialFrames': 'DWORD', 'dwStreams': 'DWORD', 'dwSuggestedBufferSize':'DWORD', 'dwWidth': 'DWORD', 'dwHeight': 'DWORD', } AVI_STREAM_HEADER = { 'fccType': 'FOURCC', 'fccHandler': 'FOURCC', 'dwFlags': 'DWORD', 'wPriority': 'WORD', 'wLanguage': 'WORD', 'dwInitialFrames': 'DWORD', 'dwScale': 'DWORD', 'dwRate': 'DWORD', 'dwStart': 'DWORD', 'dwLength': 'DWORD', 'dwSuggestedBufferSize':'DWORD', 'dwQuality': 'DWORD', 'dwSampleSize': 'DWORD', } def load_header(avi_path, header, offset): out_header = {} with open(avi_path,'rb') as f: f.seek(0) f.seek(offset,0) for name, size_label in header.items(): raw_bytes = f.read(size_in_bytes[size_label]) if size_label in ['FOURCC']: out_header[name] = raw_bytes.decode() else: out_header[name] = int.from_bytes(raw_bytes, byteorder='little') return out_header def get_offset(avi_path, string): with open(avi_path,'rb') as f: offset = 0 f.seek(0) while 1: buff = f.read(16) if string in buff: return offset + buff.index(string) else: offset += 16 def overwrite_bytes(avi_path, offset, new_integer): with open(avi_path,'r+b') as f: f.seek(offset,0) bytes_val = new_integer.to_bytes(4, 'little') f.write(bytes_val) total_offset = get_offset(avi_path, b'avih') avi_header = load_header(avi_path, AVI_HEADER, total_offset) print('avi header BEFORE:') pprint.pprint(avi_header) dwMicroSecPerFrame_offset = total_offset + 8 overwrite_bytes(avi_path, dwMicroSecPerFrame_offset, 16667) avi_header = load_header(avi_path, AVI_HEADER, total_offset) print('avi header AFTER:') pprint.pprint(avi_header) total_offset = get_offset(avi_path, b'vids') avi_stream_header = load_header(avi_path, AVI_STREAM_HEADER, total_offset) print('avi stream header BEFORE:') pprint.pprint(avi_stream_header) dwScale_offset = total_offset + 20 dwRate_offset = total_offset + 24 overwrite_bytes(avi_path, dwRate_offset, 1000000) overwrite_bytes(avi_path, dwScale_offset, 16667) avi_stream_header = load_header(avi_path, AVI_STREAM_HEADER, total_offset) print('avi stream header AFTER:') pprint.pprint(avi_stream_header)
Last edited by _Al_; 20th Feb 2023 at 18:25.
-
prints would be:
Code:avi header BEFORE: {'cb': 56, 'dwFlags': 65552, 'dwHeight': 720, 'dwInitialFrames': 0, 'dwMaxBytesPerSec': 3270732, 'dwMicroSecPerFrame': 33333, 'dwPaddingGranularity': 0, 'dwStreams': 2, 'dwSuggestedBufferSize': 537600, 'dwTotalFrames': 440, 'dwWidth': 1280, 'fcc': 'avih'} avi header AFTER: {'cb': 56, 'dwFlags': 65552, 'dwHeight': 720, 'dwInitialFrames': 0, 'dwMaxBytesPerSec': 3270732, 'dwMicroSecPerFrame': 16667, 'dwPaddingGranularity': 0, 'dwStreams': 2, 'dwSuggestedBufferSize': 537600, 'dwTotalFrames': 440, 'dwWidth': 1280, 'fcc': 'avih'} avi stream header BEFORE: {'dwFlags': 0, 'dwInitialFrames': 0, 'dwLength': 450, 'dwQuality': 10000, 'dwRate': 1000000, 'dwSampleSize': 0, 'dwScale': 33333, 'dwStart': 0, 'dwSuggestedBufferSize': 537600, 'fccHandler': 'mjpg', 'fccType': 'vids', 'wLanguage': 0, 'wPriority': 0} avi stream header AFTER: {'dwFlags': 0, 'dwInitialFrames': 0, 'dwLength': 450, 'dwQuality': 10000, 'dwRate': 1000000, 'dwSampleSize': 0, 'dwScale': 16667, 'dwStart': 0, 'dwSuggestedBufferSize': 537600, 'fccHandler': 'mjpg', 'fccType': 'vids', 'wLanguage': 0, 'wPriority': 0}
-
I agree having the printouts in order would be better.
The OP's files probably don't have any audio. But in theory you can change the audio sampling rate that way too. I think one would have problems speeding up audio 50000 fold by increasing the sampling rate to 48000 * 5000 = 240,000,000 samples per second.
I suppose one should change dwMaxBytesPerSec too. But I've never seen a player/editor have a problem with that field unchanged.
And with a command line based tool one can automate the processing with a simple batch file. This will process all AVI files in the same folder as the bat file:
Code:for %%F in (*.avi) do ( echo your command line here "%%~dpnxF" )
Code:for /R %%F in (*.avi) do ( echo your command line here "%%~dpnxF" )
-
thanks,
to make it clear for op, yes, luckily, he has no audio, it should be ok, if using python method.
Python needs to be installed. Then this file could be created, using notepad, copying this text and saving it as "avi_fps.py"
Code:import os import sys def load_value(avi_path, offset): with open(avi_path,'rb') as f: f.seek(offset,0) return int.from_bytes(f.read(4), byteorder='little') def get_offset(avi_path, string): with open(avi_path,'rb') as f: offset = 0 f.seek(0) while (buff := f.read(16)): if string in buff: return offset + buff.index(string) else: offset += 16 else: print(f'avi header with fcc type: "{string.decode()}" not found in {avi_path}') return None def overwrite_bytes(avi_path, offset, new_integer): with open(avi_path,'r+b') as f: f.seek(offset,0) bytes_val = new_integer.to_bytes(4, 'little') f.write(bytes_val) def main(avi_path, dwRate, dwScale): print(avi_path) dwRate = int(dwRate) dwScale = int(dwScale) total_offset = get_offset(avi_path, b'avih') if total_offset is not None: dwMicroSecPerFrame = load_value(avi_path, total_offset + 8) new_dwMicroSecPerFrame = int(round(10**6/dwRate*dwScale)) overwrite_bytes(avi_path, total_offset + 8, new_dwMicroSecPerFrame) print(f' dwMicroSecPerFrame: {dwMicroSecPerFrame} to {new_dwMicroSecPerFrame}') total_offset = get_offset(avi_path, b'vids') if total_offset is not None: old_dwScale = load_value(avi_path, total_offset + 20) old_dwRate = load_value(avi_path, total_offset + 24) overwrite_bytes(avi_path, total_offset + 20, dwScale) overwrite_bytes(avi_path, total_offset + 24, dwRate) print(f' dwRate/dwScale: {old_dwRate}/{old_dwScale} to {dwRate}/{dwScale}') if __name__ == '__main__': ''' usage, use full paths for avi filenale and two arguments, fps numerator and fps denominator, this should change avi to 200fps: python avi_fps.py filename.avi 200 1 ''' if len(sys.argv) > 1: args = [] for arg in sys.argv[1:]: args.append(arg) if len(args) < 3: raise ValueError('Three arguments needed. First argument is avi filename, second fps numerator, third is fps denominator') for i, arg in enumerate(args[:3]): if i==0: if not os.path.isfile(arg) or not arg.lower().endswith('.avi'): raise ValueError(f'First argument must be existing avi filepath. Got: {arg}') else: try: int(arg) except (TypeError, ValueError): raise ValueError(f'Second and third argument must be integers. Got: {arg}') main(*args)
Code:@echo off if not exist New\ md New for %%a in (*.avi) do call :process "%%a" echo press any key to exit ... & pause>nul & exit :process copy "%~1" "%~dp0\New" python avi_fps.py "%~dp0\New\%~nx1" 200 1 goto :eof
avi path, fps numerator and fps denominator. You can do 100fps if selecting 100 and 1 as second and third argument, for example. Your choice.
Test it thoroughly first, copy couple of files into an empty directory and test it first. You don't want to destroy your originals. That batch copies those files first, like pcspeak did, but anyway.Last edited by _Al_; 22nd Feb 2023 at 18:37.
-
Thank you very much everyone, this was so helpful, and the python solution from _Al_ seems to work.
Yes, it's correct that I have no audio so don't have to worry about that. And yes, I ended up deciding to set the frame rate a little slower than real life - some of the movement we're looking at is fast, so 50 fps was a good compromise.
Similar Threads
-
ffmpeg change frame rate on muxing not working,...
By Selur in forum Newbie / General discussionsReplies: 2Last Post: 10th Dec 2020, 07:42 -
Batch convert mkv files to different frame rate
By Nico Darko in forum Video ConversionReplies: 22Last Post: 18th Nov 2020, 10:04 -
PAL DVD - change frame rate 25 --> 23.976
By Flintheart in forum Video ConversionReplies: 23Last Post: 24th Apr 2020, 14:09 -
Possible wrong frame rate after IVTC, leave it or change?
By attackworld in forum EditingReplies: 7Last Post: 4th Sep 2018, 08:21 -
transcode Variable Frame Rate (VFR) AVC video to Constant Frame rate (CFR)
By hydra3333 in forum Video ConversionReplies: 2Last Post: 4th Mar 2018, 05:01