[PYTHON] Fix FLV files with strange playback time

Thing you want to do

In some FLV files, the playback time was more than 2 hours even though the video was about 2 minutes, and the video did not play well. Fix this so that it can be played cleanly. FLV MetaData Injector doesn't seem to be a problem, so I'd like to find the cause and fix it.

Search for the cause

For the time being, use FLV Extract to divide into video files (.264), audio files (.aac), and time code (.txt). As a result, there was no problem with the video and audio, and there seemed to be a problem with the time code description. When I open it, it looks like the following.

timecode.txt


# timecode format v2
0
0
70
134
334
609
952
1358
1816
2352
2957

Timecode, which increases in milliseconds, swells and grows. Moreover, the numerical value sometimes becomes smaller (the time code contains the numerical value of the elapsed time, so it decreases = it should not rewind ...).

1305237
1329824
1354481
24718
49511
74372
99290

Guess the solution

After doing various calculations,

It seems that (at least it looked like that). The calculation based on these results is as follows.

0       -> 0
0       -> 0
70      -> 70
134     -> 134
334     -> 200
609     -> 275
952     -> 343
1358    -> 406
...
1329824 -> 24587
1354481 -> 24657
24718   -> 24718
49511   -> 24793
74372   -> 24861
99290   -> 24918

It's increasing by about 66 (milliseconds) (about 15 frames of timecode) and looks correct.

Write code

I modified this number as follows (any language is acceptable as it is only file input / output and simple calculation)

timecode.py


import sys
import os
import datetime

filename =''

if(len(sys.argv) > 1):
	filename = os.path.abspath(sys.argv[1])
else:
    filename = 'timecode.txt'

f = open(filename)

lines = []
header = []

for i,line in enumerate(f.readlines()):
    #Separate the header and timecode at the beginning of the line
    if(line.startswith('#') == False):
        lines.append(int(line))
    else:
        header.append(line)

f.close()

timecodes = []
val = 0

for i,line in enumerate(lines):
 
    #0 remains as it is
    if(line == 0):
         timecodes.append(line)
 
    #A value smaller than the front is estimated to be the correct timecode, so insert it as it is
    elif(line < lines[i-1]):
        if(timecodes[i-1] < line):
            timecodes.append(line)

        #If indefinite (smaller than the previous timecode), insert the previous timecode as is
        else:
            timecodes.append(timecodes[i-1])

    else:
        #If you take the difference from the previous line, it seems to be the correct timecode
        val = line - lines[i-1]

        #If the same number continues, set the previous number
        if(val == 0):
            timecodes.append(timecodes[i-1])
            
        #If the difference is smaller than the front (the original value is assumed to be the correct timecode), insert it as it is.
        elif(timecodes[i-1] > val):
            timecodes.append(line)

        #If not, insert diff
        else:
            timecodes.append(val)


#Make a string list with a line break at the end
output = [str(v)+'\n' for v in timecodes]


now = datetime.datetime.now()

#Insert timecode header
header.extend(output)

#Save the file and exit
if(len(sys.argv) > 1):
    o_file,o_ext = os.path.splitext(os.path.abspath(sys.argv[1]))
    filename = o_file + now.strftime('_%Y%m%d%H%M%S') + o_ext
else:
    filename = 'timecode_change' + now.strftime('_%Y%m%d%H%M%S') + '.txt'

with open(filename, mode='w') as f:
    f.writelines(header)

Create a video file that reflects the corrected time code

MKVToolNix was used to combine the modified time code with the video / audio that was divided first. When I played the completed mkv file, it played cleanly.

Make it an executable file

Use pyinstaller to make it an executable file so that it can be used by people who do not have python installed. If you drag and drop the file to the completed timecode.exe, the modified timecode file will be generated in the same folder.

$ pyinstaller --onefile timecode.py

review

** 1. Put the target FLV file in FLV Extract and divide it into video / audio / timecode files. 2. Determine the correction logic 3. Apply the above correction process to the time code file 4. Put the modified time code and the divided video / audio file in MKVToolNix and combine them to create an mkv file. 5. Play **

Since it is convenient to be able to process 1 to 4 together, I added it to the above code and it became as follows.

timecode.py


###################################################################################################
#
#The playback time of FLV is strange, so
#Script to modify timecode file extracted from FLV Extractor
#If you use the modified timecode and combine it with video and audio files with MKV toolnix
#If the time code follows this rule, it will be an mkv file that can be played properly.
#Install FLV Extract and MKV ToolNix, and put the executable file in the PATH.
#
###################################################################################################

import sys
import os
import datetime
import subprocess
import tempfile
import shutil

#===================================================================================================
#Timecode extraction with flv extract
#===================================================================================================
def flvExtract(flvFilename,outputDir):

    cmd = [
        'flvextractcl',
        '-t',
        '-v',
        '-a',
        '-d',
        outputDir,
        flvFilename,
   ]
    print(flvFilename)
    subprocess.call(cmd)

#===================================================================================================
#Read and modify the timecode file
#===================================================================================================
def repairTimecode(timecodeFilename):

    f = open(timecodeFilename)

    lines = []
    header = []

    for i,line in enumerate(f.readlines()):
        #Separate the header and timecode at the beginning of the line
        if(line.startswith('#') == False):
            lines.append(int(line))
        else:
            header.append(line)

    f.close()

    timecodes = []
    val = 0

    for i,line in enumerate(lines):
    
        #0 remains as it is
        if(line == 0):
            timecodes.append(line)
    
        #A value smaller than the front is estimated to be the correct timecode, so insert it as it is
        elif(line < lines[i-1]):
            if(timecodes[i-1] < line):
                timecodes.append(line)

            #If indefinite (smaller than the previous timecode), insert the previous timecode as is
            else:
                timecodes.append(timecodes[i-1])

        else:
            #If you take the difference from the previous line, it seems to be the correct timecode
            val = line - lines[i-1]

            #If the same number continues, set the previous number
            if(val == 0):
                timecodes.append(timecodes[i-1])
            
            #If the difference is smaller than the front (the original value is assumed to be the correct timecode), insert it as it is.
            elif(timecodes[i-1] > val):
                timecodes.append(line)

            #If not, insert diff
            else:
                timecodes.append(val)
        
        if(i > 0 and timecodes[i] < timecodes[i-1]):
            print(timecodes[i-1])
            print(timecodes[i])
            print('\n')

    #Make a string list with a line break at the end
    output = [str(v)+'\n' for v in timecodes]

    #Insert timecode header
    header.extend(output)

    #Save file by overwriting
    with open(timecodeFilename, mode='w') as f:
        f.writelines(header)

    return

#===================================================================================================
#Combine video, audio, and modified timecode with MKVToolNIX
#===================================================================================================
def mkvMerge(video,audio,timecode,outputDir):

    now = datetime.datetime.now()
    basename = os.path.splitext(os.path.abspath(video))[0]
    mkv = basename + now.strftime('_%Y%m%d%H%M%S') + '.mkv'

    #Use mkvmerge
    #Create mkv by muxing various files
    cmd = [
        'mkvmerge',
        '-o',
        mkv,
        '--timecodes',
        '0:' + timecode,
        video,
        audio
    ]

    subprocess.call(cmd)

    #Output to the specified directory
    shutil.move(mkv,outputDir)

    return

#===================================================================================================
#Main processing
#===================================================================================================
def main():

    flvFilename = ''

    #Get file name
    if(len(sys.argv) > 1):
        flvFilename = os.path.abspath(sys.argv[1])
    else:
        flvFilename = os.path.join(os.getcwd(),'test.flv')

    #Prepare three file names: video, audio, and time code
    o_file = os.path.splitext(os.path.basename(flvFilename))[0]
    outputDir = os.path.dirname(os.path.abspath(flvFilename))

    #Create temporary directory for work
    with tempfile.TemporaryDirectory() as tmpDir:

        print(tmpDir+'\n')

        #Extract with FLV Extract
        flvExtract(flvFilename,tmpDir)

        timecodeFilename = os.path.join(tmpDir,o_file + '.txt')
        videoFilename = os.path.join(tmpDir,o_file + '.264')
        audioFilename =os.path.join(tmpDir,o_file + '.aac')

        #Timecode repair and MKV creation if video / audio / timecode files exist
        if(os.path.exists(timecodeFilename) and os.path.exists(videoFilename) and os.path.exists(audioFilename)):

            repairTimecode(timecodeFilename)
            mkvMerge(videoFilename,audioFilename,timecodeFilename,outputDir)
 

            #For the FLV file used for processing, add a time stamp to the file name and move it to the FLV directory directly under it.
            os.makedirs(os.path.join(outputDir,'flv'),exist_ok=True)

            flv = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '_' + os.path.splitext(os.path.basename(flvFilename))[0] + '.flv'
            shutil.move(flvFilename,os.path.join(os.path.join(outputDir,'flv'),flv))
 

main()

in conclusion

This worked well, and I couldn't solve it in the first place, and there were times when the timecode was all 0 and there was nothing I could do about it. (It is possible to calculate the length from the length of the audio file, divide it by the number of video frames, and output a provisional time code, but of course the sound gap will be severe.)

However, if you have a problem in the same situation, it is recommended to look at the time code for the time being.

Recommended Posts

Fix FLV files with strange playback time
How to measure mp3 file playback time with python
Upload files with Django
Fix database with pytest-docker