HeadlessCow
11th January 2004, 07:06
Being that I'm into anime I tend to run into lots of 120 fps encodes because someone thought that was the best way to keep mixed film and ntsc video in the same file at a single usable framerate. Well, that was valid with avi files...but why do that when we've now got matroska?!
So...I decided to take a stab at making the conversion relatively easy...and most importantly, not manual!! Nothing sucks more than counting dropped frames while scrolling through a file in vdub so you can figure out a correct decimation scheme.
Well, thanks to Donald Graft's very useful MultiDecimate (http://neuron2.net/multidecimate/multidecimate.html) tool, gizmotech's helpful thread (http://forum.doom9.org/showthread.php?s=&threadid=64314) about vfr encoding, and little (maybe too much) free time I think I've come up with a good way.
I'll tell you how I came up with this approach, then I'll walk you through all the necessary steps.
First, I was playing with using MultiDecimate to get the whole clip down to a single usable framerate. So I looked in its output file and noticed that, with a 120 fps encode that was created by adding dropped frame, most of the frames had 0 difference from the previous frame. Hmm, I thought, those must be all the dropped frames. So I continued on and generated a dfile.txt with the GUI. Looking in there I could see that it merely contained a list of output-input frame matchings. Sadly, some of my favorite frames were missing from the 29.97 fps segments since I had told it to decimate 4 of every 5 frames to give me 23.976 fps. Hmm, I thought again, what if I wanted all the frames, but with none of the dropped frames. Well...that was not to be because I just couldn't squish ~30 frames into the time of ~24. Or could I?
At about this same time I was bopping around on the doom9 board looking at Matroska stuff because I had free time while my encode was running. I again came across some variable frame rate stuff, and it seemed like a perfect match, because I could get ~30 frames into the time of ~24 if I changed the framerate! But, gizmotech's guide, as helpful as it was left me sad because for a video with as many interwoven sections of framerates (there are 2190 framerate ranges in my final timecodes.txt) as this one had would take me years to get correct. There had to be a better way!
So I started off by looking at mfile.txt for a way to pull out all the good frames, and somehow figure out the correct fps at the same time. I start of by scanning once through the file and counting each good frame and the number of identical (or dropped) frames that follow it. I store all of these in a big list for my second round of computation.
During the next pass I cycle through my list and write out a dfile.txt that matches all the good input frames to output frames, regardless of framerate. At the same time I keep track of all the consecutive output frames that had the same number of dupes following them. Everytime I find a change I write out the previous sections frame range and compute the fps for that segment based on the user input original framerate.
Now I'm left with a dfile.txt that contains all the good frames and a timecodes.txt that tells matroska what ranges are what framerate. Now I open trusty old vdub and encode the video. This file has half the fps of the input file because of the header I wrote to dfile.txt, but that doesn't matter because I just want the video. Demux and encoding the audio is done seperately because otherwise it ends up messed up by the different length of the video due to the fps change.
With video, audio and timecodes.txt I've got everything I need to mux a nice matroska file.
Now, for the step-by-step.
0) Get a 120 fps video that includes mixed film and ntsc. Here's a sample file.
1) Grab the MultiDecimate filter and create an avs for the file you wish to encode. Make sure to trim off any junk frames you don't want before you call MultiDecimate in your script. You should end up with a file that looks something like this (no processing filters are necessary now):
AviSource("mixed-fps.avi").trim(8,0)
ConvertToYUY2()
MultiDecimate(pass=1)
2) Open your avs in vdub and play through it exactly once, then close it. You should now have an mfile.txt file in the same directory as your avs script. If you haven't look in one it will look like this:
0 0.000000
1 0.000000
2 0.000000
3 0.000000
4 0.049069
...
Each line contains a framenumber and the difference to the previous frame.
3) You're probably thinking, I know what's coming, run the MultiDecimate GUI...but no! This is where my code comes in :)
Go grab a copy of the source and executable here. (http://www.matroska.org/downloads/windows.html) It's on the Windows page, but since it's Java you can run it on just about any platform you want.
Make sure you have a Java VM installed first. Run my program and pass it the name of the mfile.txt file (MultiDec doesn't like it renamed, but I don't care :)) and the original fps of the file, probably 119.88.
java -jar vfrTools.jar mfile.txt 119.88
This will generate two files in your directory. The first is the dlist.txt file that MultiDec pass 2 will expect.
3 2 1 1557 359
0 0
1 4
2 8
3 12
4 17
...
The first line is bogus except for the frame counts, but the first 3 values are set to stuff that seemed "safe". (no crashes or other weirdness)
The second is the timecodes.txt which is in the format the mkvmerge expects.
# timecode format v1
Assume 119.881
0,2,29.97025
3,6,23.9762
7,126,29.97025
127,146,23.9762
...
Assume fps is set to the input framerate, which seems safe enough. From then on every frame in the file is part of some range and assigned an fps.
4) Switch to using the second pass of MultiDec and encode your video. Your avs should look like this, but feel free to add any filtering you want to the end.
AviSource("mixed-fps.avi").trim(8,0)
ConvertToYUY2()
MultiDecimate(pass=2)
Yes, this output is gonna be at an odd fps. But it doesn't matter, we'll fix it in the muxing process.
5) Merge your video and any audio you want using mkvmerge. Just make sure you use the timecodes.txt file with your video. Make sure you use a version new enough to support this. I used 0.8.1.
Command split so the post isn't super-wide.
"mkvmerge" -o "variable-fps.mkv" -d 0 -A -S --track-order 0
--timecodes "0:timecodes.txt" "single-fps.avi"
Voila! Now you've nicely converted that EVIL 120 fps encode that probably wouldn't even play in real time on your computer to a HAPPY variable fps matroska file that will :)
Ok, so maybe it's a little more work than I lead you to believe...but the important part is that you had to do almost nothing other than start processes and type a few scary commands. Hopefully I can expand my project a bit and make it even easier. So if you have any feedback or bug reports, please post here!
edit: fixed download links to reflect my updates. Examples too.
edit: removed download links to original files and added a link to matroska.org instead since robUx4 nicely offered to host it there. I don't have my original sample files anymore, so no nice step-by-step output examples :'(
So...I decided to take a stab at making the conversion relatively easy...and most importantly, not manual!! Nothing sucks more than counting dropped frames while scrolling through a file in vdub so you can figure out a correct decimation scheme.
Well, thanks to Donald Graft's very useful MultiDecimate (http://neuron2.net/multidecimate/multidecimate.html) tool, gizmotech's helpful thread (http://forum.doom9.org/showthread.php?s=&threadid=64314) about vfr encoding, and little (maybe too much) free time I think I've come up with a good way.
I'll tell you how I came up with this approach, then I'll walk you through all the necessary steps.
First, I was playing with using MultiDecimate to get the whole clip down to a single usable framerate. So I looked in its output file and noticed that, with a 120 fps encode that was created by adding dropped frame, most of the frames had 0 difference from the previous frame. Hmm, I thought, those must be all the dropped frames. So I continued on and generated a dfile.txt with the GUI. Looking in there I could see that it merely contained a list of output-input frame matchings. Sadly, some of my favorite frames were missing from the 29.97 fps segments since I had told it to decimate 4 of every 5 frames to give me 23.976 fps. Hmm, I thought again, what if I wanted all the frames, but with none of the dropped frames. Well...that was not to be because I just couldn't squish ~30 frames into the time of ~24. Or could I?
At about this same time I was bopping around on the doom9 board looking at Matroska stuff because I had free time while my encode was running. I again came across some variable frame rate stuff, and it seemed like a perfect match, because I could get ~30 frames into the time of ~24 if I changed the framerate! But, gizmotech's guide, as helpful as it was left me sad because for a video with as many interwoven sections of framerates (there are 2190 framerate ranges in my final timecodes.txt) as this one had would take me years to get correct. There had to be a better way!
So I started off by looking at mfile.txt for a way to pull out all the good frames, and somehow figure out the correct fps at the same time. I start of by scanning once through the file and counting each good frame and the number of identical (or dropped) frames that follow it. I store all of these in a big list for my second round of computation.
During the next pass I cycle through my list and write out a dfile.txt that matches all the good input frames to output frames, regardless of framerate. At the same time I keep track of all the consecutive output frames that had the same number of dupes following them. Everytime I find a change I write out the previous sections frame range and compute the fps for that segment based on the user input original framerate.
Now I'm left with a dfile.txt that contains all the good frames and a timecodes.txt that tells matroska what ranges are what framerate. Now I open trusty old vdub and encode the video. This file has half the fps of the input file because of the header I wrote to dfile.txt, but that doesn't matter because I just want the video. Demux and encoding the audio is done seperately because otherwise it ends up messed up by the different length of the video due to the fps change.
With video, audio and timecodes.txt I've got everything I need to mux a nice matroska file.
Now, for the step-by-step.
0) Get a 120 fps video that includes mixed film and ntsc. Here's a sample file.
1) Grab the MultiDecimate filter and create an avs for the file you wish to encode. Make sure to trim off any junk frames you don't want before you call MultiDecimate in your script. You should end up with a file that looks something like this (no processing filters are necessary now):
AviSource("mixed-fps.avi").trim(8,0)
ConvertToYUY2()
MultiDecimate(pass=1)
2) Open your avs in vdub and play through it exactly once, then close it. You should now have an mfile.txt file in the same directory as your avs script. If you haven't look in one it will look like this:
0 0.000000
1 0.000000
2 0.000000
3 0.000000
4 0.049069
...
Each line contains a framenumber and the difference to the previous frame.
3) You're probably thinking, I know what's coming, run the MultiDecimate GUI...but no! This is where my code comes in :)
Go grab a copy of the source and executable here. (http://www.matroska.org/downloads/windows.html) It's on the Windows page, but since it's Java you can run it on just about any platform you want.
Make sure you have a Java VM installed first. Run my program and pass it the name of the mfile.txt file (MultiDec doesn't like it renamed, but I don't care :)) and the original fps of the file, probably 119.88.
java -jar vfrTools.jar mfile.txt 119.88
This will generate two files in your directory. The first is the dlist.txt file that MultiDec pass 2 will expect.
3 2 1 1557 359
0 0
1 4
2 8
3 12
4 17
...
The first line is bogus except for the frame counts, but the first 3 values are set to stuff that seemed "safe". (no crashes or other weirdness)
The second is the timecodes.txt which is in the format the mkvmerge expects.
# timecode format v1
Assume 119.881
0,2,29.97025
3,6,23.9762
7,126,29.97025
127,146,23.9762
...
Assume fps is set to the input framerate, which seems safe enough. From then on every frame in the file is part of some range and assigned an fps.
4) Switch to using the second pass of MultiDec and encode your video. Your avs should look like this, but feel free to add any filtering you want to the end.
AviSource("mixed-fps.avi").trim(8,0)
ConvertToYUY2()
MultiDecimate(pass=2)
Yes, this output is gonna be at an odd fps. But it doesn't matter, we'll fix it in the muxing process.
5) Merge your video and any audio you want using mkvmerge. Just make sure you use the timecodes.txt file with your video. Make sure you use a version new enough to support this. I used 0.8.1.
Command split so the post isn't super-wide.
"mkvmerge" -o "variable-fps.mkv" -d 0 -A -S --track-order 0
--timecodes "0:timecodes.txt" "single-fps.avi"
Voila! Now you've nicely converted that EVIL 120 fps encode that probably wouldn't even play in real time on your computer to a HAPPY variable fps matroska file that will :)
Ok, so maybe it's a little more work than I lead you to believe...but the important part is that you had to do almost nothing other than start processes and type a few scary commands. Hopefully I can expand my project a bit and make it even easier. So if you have any feedback or bug reports, please post here!
edit: fixed download links to reflect my updates. Examples too.
edit: removed download links to original files and added a link to matroska.org instead since robUx4 nicely offered to host it there. I don't have my original sample files anymore, so no nice step-by-step output examples :'(