Dinominant
27th July 2009, 02:22
Lately I've been attempting to convert some Babylon 5 episodes to VFR MKV exclusevly with Mencoder. 2 days ago I found a way to do it:
#!/bin/bash
# Dump video
mencoder -dvd-device ../Babylon5.iso dvd://1 -noskip -mc 0 -vf phase,softskip,harddup -ofps 1 -nosound -ovc raw -of avi -o video.phase.avi -frames 1200
# extract 5 frame sections
for LIMIT in `seq -w 0 239`; do
mencoder video.phase.avi -noskip -mc 0 -ss $(( `printf "$LIMIT" | sed -r "s/0*([0-9])/\\1/"` * 5 )) -ovc raw -of avi -o $LIMIT.avi -frames 5
done
# if exactly 1 frame is a duplicate then drop it and change the framerate
for FILE in [0-9]*.avi; do
if mencoder "$FILE" -vf decimate -ovc raw -o /dev/null 2>&1 >/dev/null | grep -q "1 duplicate frame"; then
# drop duplicate and convert to 24000/1001
mencoder "$FILE" -vf decimate=-4 -noskip -mc 0 -ofps 24000/1001 -ovc raw -o temp.avi >/dev/null 2>/dev/null
mv temp.avi "$FILE"
else
# convert 30000/1001
mencoder "$FILE" -noskip -mc 0 -ofps 30000/1001 -ovc raw -o temp.avi >/dev/null 2>/dev/null
mv temp.avi "$FILE"
fi
done
# join various files
mencoder [0-9]*.avi -forceidx -ofps 120000/1001 -ovc raw -o video.vfr.avi
If you don't feel like reading the code here is how it works:
1. Change the framerate to 1fps so you can randomly seek with perfect precition.
2. Split the video into 5 frame chunks.
3. Determine if each chunk needs to be inverse telecined.
4. Convert each chunk into 120000/1001fps with null frames.
5. Merge the chunks.
6. Mux into MKV with mkvmerge. The null frames will be removed and proper timecodes are created by mkvmerge.
Now, this prototype does have 2 drawbacks (one of which is solved in the next prototype):
Timecode alignment can be off by 1-5 frames. IE the framerate could change 1-5 frames too soon or too late. AV sync is *not* affected in any negative way. IMO this isn't that big of a problem since you would only notice it by counting the NULL frames at a scene change before they are removed by mkvmerge. And this is assuming the framerate changes on a scence change.
This prototype requires many (read hundreds) GB of space to work
I solved the problem of storing hundreds of GB of temporary files with a few thousand FIFO's and some EDL's:
# dump video and convert fps so we can randomly seek it with perfect precision
FRAMES=78901
printf "Dumping video... "
mencoder -dvd-device ../Babylon5.iso dvd://1 -noskip -mc 0 -fps 1 -nosound -ovc copy -o "video.m2v" >/dev/null 2>/dev/null
printf "done.\n"
# generate segment fifo's and EDL's. This is a pain but it'll save hundreds of gigabytes of space
printf "Generating FIFO and EDL segments... "
mkdir "segments"
mkfifo "segments/first.fifo"
printf "6 78901 0\n" > "segments/first.edl"
for SEGMENT in `seq -w 1 $(( $FRAMES / 5 ))`; do # the math is bad. Some frames at the end are not encoded...
INT=$(( `printf "$SEGMENT" | sed -r "s/0*([0-9])/\\1/"` * 5 ))
mkfifo "segments/$SEGMENT.fifo"
printf "0 "$(( $INT ))" 0\n"$(( $INT + 6))" 78901 0\n" > "segments/$SEGMENT.edl"
done
printf "done.\n"
# start the encoder
function end
{
kill -9 $SERVER
kill $ENCODER
exit
}
trap end SIGINT SIGTERM
cat segments/first.fifo segments/[0-9]*.fifo | mencoder - -demuxer rawvideo -rawvideo fps=120000/1001:w=720:h=480:format=yv12 -vf decimate=-0 -ovc x264 -x264encopts qp=10 -of avi -o video.vfr.avi &
ENCODER=$!
# serve the segments to the encoder through the fifo's w00t
for SEGMENT in first `seq -w 1 $(( $FRAMES / 5 ))`; do # the math is bad. Some frames at the end are not encoded...
mencoder "video.m2v" -edl segments/$SEGMENT.edl -hr-edl-seek -ss $(( `printf "$SEGMENT" | sed -r "s/0*([0-9])/\\1/"` * 5 - 100 )) -frames 5 -vf phase -ovc raw -of avi -o "phased.avi" -really-quiet
if mencoder "phased.avi" -vf decimate=-4 -ovc raw -o /dev/null 2>&1 >/dev/null | grep -q "1 duplicate frame"; then
# drop duplicate and convert to 24000/1001
mencoder "phased.avi" -noskip -mc 0 -vf decimate=-4 -ofps 24000/1001 -ovc raw -of avi -o "converted.avi" -really-quiet
else
# convert 30000/1001
mencoder "phased.avi" -noskip -mc 0 -ofps 30000/1001 -ovc raw -of avi -o "converted.avi" -really-quiet
fi
mencoder "converted.avi" -vf harddup -ofps 120000/1001 -ovc raw -of rawvideo -o "segments/$SEGMENT.fifo" -really-quiet &
SERVER=$!
wait $SERVER
done
Here I'm simply using FIFO's and the power of pipes ^_^. Each fifo acts as a single 5 frame chunk that has been converted into 20 frames (5 30000/1001fps frames converted into 20 120000/1001fps progressive frames). Mencoder removes the duplicates, encodes null frames, and you have the AVI ready to mux with mkvmerge.
Here is a sample, before (http://www.nathanshearer.com/modules/core/home/3/temp/Before.avi) and after (http://www.nathanshearer.com/modules/core/home/3/temp/After.mkv), of mixed 30p and 24p Babylon 5. Notice that the mkv is smaller, even with the same QP encode options due to fewer frames and that they are all progressive. Please ignore the audio channel issues, I'm lazy with the downmixing...
Once I find a way to automatically detect VFR material I'll release a new version of Abdvde that can produce VFR encodes ^_^
#!/bin/bash
# Dump video
mencoder -dvd-device ../Babylon5.iso dvd://1 -noskip -mc 0 -vf phase,softskip,harddup -ofps 1 -nosound -ovc raw -of avi -o video.phase.avi -frames 1200
# extract 5 frame sections
for LIMIT in `seq -w 0 239`; do
mencoder video.phase.avi -noskip -mc 0 -ss $(( `printf "$LIMIT" | sed -r "s/0*([0-9])/\\1/"` * 5 )) -ovc raw -of avi -o $LIMIT.avi -frames 5
done
# if exactly 1 frame is a duplicate then drop it and change the framerate
for FILE in [0-9]*.avi; do
if mencoder "$FILE" -vf decimate -ovc raw -o /dev/null 2>&1 >/dev/null | grep -q "1 duplicate frame"; then
# drop duplicate and convert to 24000/1001
mencoder "$FILE" -vf decimate=-4 -noskip -mc 0 -ofps 24000/1001 -ovc raw -o temp.avi >/dev/null 2>/dev/null
mv temp.avi "$FILE"
else
# convert 30000/1001
mencoder "$FILE" -noskip -mc 0 -ofps 30000/1001 -ovc raw -o temp.avi >/dev/null 2>/dev/null
mv temp.avi "$FILE"
fi
done
# join various files
mencoder [0-9]*.avi -forceidx -ofps 120000/1001 -ovc raw -o video.vfr.avi
If you don't feel like reading the code here is how it works:
1. Change the framerate to 1fps so you can randomly seek with perfect precition.
2. Split the video into 5 frame chunks.
3. Determine if each chunk needs to be inverse telecined.
4. Convert each chunk into 120000/1001fps with null frames.
5. Merge the chunks.
6. Mux into MKV with mkvmerge. The null frames will be removed and proper timecodes are created by mkvmerge.
Now, this prototype does have 2 drawbacks (one of which is solved in the next prototype):
Timecode alignment can be off by 1-5 frames. IE the framerate could change 1-5 frames too soon or too late. AV sync is *not* affected in any negative way. IMO this isn't that big of a problem since you would only notice it by counting the NULL frames at a scene change before they are removed by mkvmerge. And this is assuming the framerate changes on a scence change.
This prototype requires many (read hundreds) GB of space to work
I solved the problem of storing hundreds of GB of temporary files with a few thousand FIFO's and some EDL's:
# dump video and convert fps so we can randomly seek it with perfect precision
FRAMES=78901
printf "Dumping video... "
mencoder -dvd-device ../Babylon5.iso dvd://1 -noskip -mc 0 -fps 1 -nosound -ovc copy -o "video.m2v" >/dev/null 2>/dev/null
printf "done.\n"
# generate segment fifo's and EDL's. This is a pain but it'll save hundreds of gigabytes of space
printf "Generating FIFO and EDL segments... "
mkdir "segments"
mkfifo "segments/first.fifo"
printf "6 78901 0\n" > "segments/first.edl"
for SEGMENT in `seq -w 1 $(( $FRAMES / 5 ))`; do # the math is bad. Some frames at the end are not encoded...
INT=$(( `printf "$SEGMENT" | sed -r "s/0*([0-9])/\\1/"` * 5 ))
mkfifo "segments/$SEGMENT.fifo"
printf "0 "$(( $INT ))" 0\n"$(( $INT + 6))" 78901 0\n" > "segments/$SEGMENT.edl"
done
printf "done.\n"
# start the encoder
function end
{
kill -9 $SERVER
kill $ENCODER
exit
}
trap end SIGINT SIGTERM
cat segments/first.fifo segments/[0-9]*.fifo | mencoder - -demuxer rawvideo -rawvideo fps=120000/1001:w=720:h=480:format=yv12 -vf decimate=-0 -ovc x264 -x264encopts qp=10 -of avi -o video.vfr.avi &
ENCODER=$!
# serve the segments to the encoder through the fifo's w00t
for SEGMENT in first `seq -w 1 $(( $FRAMES / 5 ))`; do # the math is bad. Some frames at the end are not encoded...
mencoder "video.m2v" -edl segments/$SEGMENT.edl -hr-edl-seek -ss $(( `printf "$SEGMENT" | sed -r "s/0*([0-9])/\\1/"` * 5 - 100 )) -frames 5 -vf phase -ovc raw -of avi -o "phased.avi" -really-quiet
if mencoder "phased.avi" -vf decimate=-4 -ovc raw -o /dev/null 2>&1 >/dev/null | grep -q "1 duplicate frame"; then
# drop duplicate and convert to 24000/1001
mencoder "phased.avi" -noskip -mc 0 -vf decimate=-4 -ofps 24000/1001 -ovc raw -of avi -o "converted.avi" -really-quiet
else
# convert 30000/1001
mencoder "phased.avi" -noskip -mc 0 -ofps 30000/1001 -ovc raw -of avi -o "converted.avi" -really-quiet
fi
mencoder "converted.avi" -vf harddup -ofps 120000/1001 -ovc raw -of rawvideo -o "segments/$SEGMENT.fifo" -really-quiet &
SERVER=$!
wait $SERVER
done
Here I'm simply using FIFO's and the power of pipes ^_^. Each fifo acts as a single 5 frame chunk that has been converted into 20 frames (5 30000/1001fps frames converted into 20 120000/1001fps progressive frames). Mencoder removes the duplicates, encodes null frames, and you have the AVI ready to mux with mkvmerge.
Here is a sample, before (http://www.nathanshearer.com/modules/core/home/3/temp/Before.avi) and after (http://www.nathanshearer.com/modules/core/home/3/temp/After.mkv), of mixed 30p and 24p Babylon 5. Notice that the mkv is smaller, even with the same QP encode options due to fewer frames and that they are all progressive. Please ignore the audio channel issues, I'm lazy with the downmixing...
Once I find a way to automatically detect VFR material I'll release a new version of Abdvde that can produce VFR encodes ^_^