VideoHelp Forum




+ Reply to Thread
Results 1 to 14 of 14
  1. 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!
    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
    Quote Quote  
  2. 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)
    Quote Quote  
  3. 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?
    Quote Quote  
  4. you may find this helpful



    http://www.am-soft.ru/avifrate.html
    Quote Quote  
  5. Member
    Join Date
    Apr 2007
    Location
    Australia
    Search Comp PM
    jack_666
    you may find this helpful
    +1


    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
    Cheers
    Quote Quote  
  6. 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)
    for 30fps that delay_in_microseconds would be 33333,

    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)
    mpv player plays the original frame rate, even if in hex editor shows new owerwritten values, and NLE would not load it
    Quote Quote  
  7. 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.
    Quote Quote  
  8. Member DB83's Avatar
    Join Date
    Jul 2007
    Location
    United Kingdom
    Search Comp PM
    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.
    Quote Quote  
  9. 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.
    Quote Quote  
  10. 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)
    Magix Vegas 19 loaded that video on timeline with video of half length, sped up and audio with just regular original length.
    Last edited by _Al_; 20th Feb 2023 at 18:25.
    Quote Quote  
  11. 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}
    Those prints are out of order though. Order is pretty important to get those offsets right, like on those websites or while defining those headers on the beggining of script.
    Quote Quote  
  12. 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"
    )
    To also recurse all subfolders from the folder with the batch file add /R:

    Code:
    for /R %%F in (*.avi) do (
       echo  your command line here "%%~dpnxF"
    )
    Note that "echo your command line here" is to be replaced by the appropriate command line to run the program to process a single video. "%%~dpnxF" is the name of the file that will be processed on each pass of the for loop.
    Quote Quote  
  13. 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)
    then creating a batch file, again using notepad and saving it as "change_fps.bat":
    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
    put both in the directory where your files are and run that bat file. That changes fps to 200fps per second. That python file takes three arguments:
    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.
    Quote Quote  
  14. 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.
    Quote Quote  



Similar Threads

Visit our sponsor! Try DVDFab and backup Blu-rays!