diff --git a/README.md b/README.md index abb76a2..cef6f28 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ binary2video [-f fps] [-w width] [-h height] [-v] infile outfile -h height Set height, default 240. --c codec Set video codec: h.264, vp9, ffv1, or ffv1.3. Default is vp9. +-c codec Set video codec: h264, vp9, ffv1, or ffv1l3. Default is vp9. -v Be verbose. @@ -43,6 +43,18 @@ infile The name and path of the video file from which the original binary f outfile The name and path of the binary file to which the original data should be restored to. ``` + +## Compatibility matrix + +| Container vs. Codec | **h264** | **vp9** | **ffv1** | **ffv1l3** | +|--------------------:|:--------:|:-------:|:--------:|:----------:| +| **.mp4** | ✓ | ✓ | | | +| **.webm** | | ✓ | | | +| **.avi** | ✓ | ✓ | ✓ | ✓ | +| **.mkv** | ✓ | ✓ | ✓ | ✓ | + +Other containers have not been tested and may also work. + ## Internal process ### Conversion from binary file to video file @@ -70,5 +82,6 @@ Run the unit tests with ## References * FFmpeg Formats Documentation: [Demuxers - rawvideo](https://ffmpeg.org/ffmpeg-formats.html#rawvideo) +* FFmpeg FFV1: [FFV1 encoding cheatsheet](https://trac.ffmpeg.org/wiki/Encode/FFV1) * AntumDeluge: [List of Lossless FFmpeg Video Encoders](https://antumdeluge.wordpress.com/lossless-ffmpeg-video-encoders/) * Fufu Fang: [Converting-Arbitrary-Data-To-Video](https://github.com/fangfufu/Converting-Arbitrary-Data-To-Video) diff --git a/binary2video b/binary2video index d44c8f5..25505bc 100755 --- a/binary2video +++ b/binary2video @@ -1,34 +1,67 @@ #!/usr/bin/env bash usage_and_exit () { - echo "Usage: binary2video [-f fps] [-w width] [-h height] [-c codec] [-v] infile outfile" + echo "Usage: binary2video [-f fps] [-w width] [-h height] [-c (vp9 | h264 | ffv1 | ffv1l3)] [-v] infile outfile" + echo " Defaults: fps=1 width=320 height=240 codec=vp9" exit $1 } +check_executable_or_exit() { + if [ ! -x $1 ]; then + echo "error: no $(basename $1) at $gzip" + exit 2 + fi +} -if [ $# = 1 -a x$1 = 'x--help' ]; then +if [ \( $# -eq 1 \) -a \( "$1" = '--help' \) ]; then usage_and_exit 0 fi +# check needed binaries gzip=/usr/bin/gzip +check_executable_or_exit $gzip ffmpeg=/usr/bin/ffmpeg +check_executable_or_exit $ffmpeg + +# check parameters width=320 height=240 framerate=1 -while getopts 'f:w:h:' option; do - case $option in +codec=vp9 +verbose=0 +while getopts 'f:w:h:c:v' option; do + case "$option" in f) framerate="$OPTARG";; w) width="$OPTARG";; h) height="$OPTARG";; + c) codec="$OPTARG";; + v) verbose=1;; ?) usage_and_exit 1;; esac done shift $(($OPTIND - 1)) -# echo $framerate $width $height $1 $2 -# exit 0 + +# verify codec +case "$codec" in + h264) ;; + vp9) ;; + ffv1) ;; + ffv1l3) ;; + *) + echo "error: unknown codec '$codec'" + usage_and_exit 3 + ;; +esac if [ $# != 2 ]; then usage_and_exit 1 fi +if [ ! -r "$1" ]; then + echo "error: file '$1' does not exist or is not readable" + exit 4 +fi +if [ $verbose = 1 ]; then + echo "parameters: fps=$framerate width=$width height=$height codec=$codec" +fi tmpfile_gzipped=$(mktemp --tmpdir binary2video.gz.XXXXXXXXXX) tmpfile_padded=$(mktemp --tmpdir binary2video.padded.XXXXXXXXXX) @@ -37,43 +70,57 @@ infile="$1" outfile="$2" pixel_format=rgb24 blocksize=$(($width * $height * 3)) -echo "blocksize is $blocksize bytes" - -md5sum "${infile}" +if [ $verbose = 1 ]; then + echo "blocksize is $blocksize bytes" + echo -n "input file MD5: " + md5sum "${infile}" | cut -d\ -f1 +fi $gzip --fast --to-stdout "${infile}" > ${tmpfile_gzipped} size_gzipped=$(stat --format=%s ${tmpfile_gzipped}) -echo "gzipped file has $size_gzipped bytes" +if [ $verbose = 1 ]; then + echo "gzipped file has $size_gzipped bytes" +fi padding_needed=$(($blocksize - ($size_gzipped % $blocksize))) -echo "padding needed: $padding_needed bytes" +if [ $verbose = 1 ]; then + echo "padding needed: $padding_needed bytes" +fi (cat ${tmpfile_gzipped}; dd if=/dev/zero bs=$padding_needed count=1 status=none) >> ${tmpfile_padded} size_padded=$(stat --format=%s ${tmpfile_padded}) -echo "padded file has $size_padded bytes" +if [ $verbose = 1 ]; then + echo "padded file has $size_padded bytes" +fi -# vp9 works in mp4/webm/avi/mkv -$ffmpeg -y -hide_banner -loglevel error \ - -f rawvideo -pixel_format $pixel_format -video_size "${width}x${height}" -framerate ${framerate} -i "${tmpfile_padded}" \ - -c:v libvpx-vp9 -lossless 1 \ - "${outfile}" -# ffv1 version 1 works only in mkv/avi -# $ffmpeg -y -hide_banner -loglevel error \ -# -f rawvideo -pixel_format $pixel_format -video_size "${width}x${height}" -framerate ${framerate} -i "${tmpfile_padded}" \ -# -c:v ffv1 -level 1 -coder 1 -context 1 -g 1 \ -# "${outfile}" -# ffv1 version 3 works only in mkv/avi -# $ffmpeg -y -hide_banner -loglevel error \ -# -f rawvideo -pixel_format $pixel_format -video_size "${width}x${height}" -framerate ${framerate} -i "${tmpfile_padded}" \ -# -c:v ffv1 -level 3 -threads 8 -coder 1 -context 1 -g 1 -slices 24 -slicecrc 1 \ -# "${outfile}" -# h.264 works in mp4/avi/mkv -# $ffmpeg -y -hide_banner -loglevel error \ -# -f rawvideo -pixel_format $pixel_format -video_size "${width}x${height}" -framerate ${framerate} -i "${tmpfile_padded}" \ -# -c:v libx264rgb -preset ultrafast -qp 0 \ -# "${outfile}" +if [ $codec = "vp9" ]; then + $ffmpeg -y -hide_banner -loglevel error \ + -f rawvideo -pixel_format $pixel_format -video_size "${width}x${height}" -framerate ${framerate} -i "${tmpfile_padded}" \ + -c:v libvpx-vp9 -lossless 1 \ + "${outfile}" +elif [ $codec = "h264" ]; then + $ffmpeg -y -hide_banner -loglevel error \ + -f rawvideo -pixel_format $pixel_format -video_size "${width}x${height}" -framerate ${framerate} -i "${tmpfile_padded}" \ + -c:v libx264rgb -preset ultrafast -qp 0 \ + "${outfile}" +elif [ $codec = "ffv1" ]; then + $ffmpeg -y -hide_banner -loglevel error \ + -f rawvideo -pixel_format $pixel_format -video_size "${width}x${height}" -framerate ${framerate} -i "${tmpfile_padded}" \ + -c:v ffv1 -level 1 -coder 1 -context 1 -g 1 \ + "${outfile}" +elif [ $codec = "ffv1l3" ]; then + $ffmpeg -y -hide_banner -loglevel error \ + -f rawvideo -pixel_format $pixel_format -video_size "${width}x${height}" -framerate ${framerate} -i "${tmpfile_padded}" \ + -c:v ffv1 -level 3 -threads 8 -coder 1 -context 1 -g 1 -slices 24 -slicecrc 1 \ + "${outfile}" +else + echo "error: unknown codec '$codec'" + usage_and_exit 5 +fi -echo "output file has $(stat --format=%s "${outfile}") bytes" +if [ $verbose = 1 ]; then + echo "output file has $(stat --format=%s "${outfile}") bytes" +fi rm ${tmpfile_gzipped} rm ${tmpfile_padded} diff --git a/test/test.bats b/test/test.bats index 1c3251e..a2a3e37 100644 --- a/test/test.bats +++ b/test/test.bats @@ -1,10 +1,10 @@ teardown() { - rm -f test/LICENSE.{mp4,webm,avi} test/LICENSE_restored - rm -f test/9.{mp4,webm,avi} test/9.txt_restored - rm -f test/42.{mp4,webm,avi} test/42.txt_restored - rm -f test/16777216.{mp4,webm,avi} test/16777216.txt_restored - rm -f test/'filename with spaces'.{mp4,webm,avi} test/'filename with spaces'.txt_restored - rm -f test/'filename with (brackets)'.{mp4,webm,avi} test/'filename with (brackets)'.txt_restored + rm -f test/LICENSE.{mp4,webm,avi,mkv} test/LICENSE_restored + rm -f test/9.{mp4,webm,avi,mkv} test/9.txt_restored + rm -f test/42.{mp4,webm,avi,mkv} test/42.txt_restored + rm -f test/16777216.{mp4,webm,avi,mkv} test/16777216.txt_restored + rm -f test/'filename with spaces'.{mp4,webm,avi,mkv} test/'filename with spaces'.txt_restored + rm -f test/'filename with (brackets)'.{mp4,webm,avi,mkv} test/'filename with (brackets)'.txt_restored } @test "can run binary2video" { @@ -31,7 +31,7 @@ teardown() { [[ "${lines[0]}" == "Usage:"* ]] } -@test "convert to MP4 and restore file LICENSE" { +@test "convert to MP4/default and restore file LICENSE" { echo "********** convert **********" ./binary2video LICENSE test/LICENSE.mp4 echo "********** restore **********" @@ -40,7 +40,7 @@ teardown() { cmp --silent LICENSE test/LICENSE_restored } -@test "convert To WebM and restore file test/9.txt" { +@test "convert To WebM/default and restore file test/9.txt" { echo "********** convert **********" ./binary2video test/9.txt test/9.webm echo "********** restore **********" @@ -49,7 +49,7 @@ teardown() { cmp --silent test/9.txt test/9.txt_restored } -@test "convert to AVI and restore file test/42.txt" { +@test "convert to AVI/default and restore file test/42.txt" { echo "********** convert **********" ./binary2video test/42.txt test/42.avi echo "********** restore **********" @@ -58,7 +58,7 @@ teardown() { cmp --silent test/42.txt test/42.txt_restored } -@test "convert to MP4 and restore file test/16777216.txt" { +@test "convert to MP4/default and restore file test/16777216.txt" { echo "********** convert **********" ./binary2video test/16777216.txt test/16777216.mp4 echo "********** restore **********" @@ -67,7 +67,7 @@ teardown() { cmp --silent test/16777216.txt test/16777216.txt_restored } -@test "convert to MP4 and restore file 'test/filename with spaces.txt'" { +@test "convert to MP4/default and restore file 'test/filename with spaces.txt'" { echo "********** convert **********" ./binary2video 'test/filename with spaces.txt' 'test/filename with spaces.mp4' echo "********** restore **********" @@ -76,7 +76,7 @@ teardown() { cmp --silent 'test/filename with spaces.txt' 'test/filename with spaces.txt_restored' } -@test "convert to MP4 and restore file 'test/filename with (brackets).txt'" { +@test "convert to MP4/default and restore file 'test/filename with (brackets).txt'" { echo "********** convert **********" ./binary2video 'test/filename with (brackets).txt' 'test/filename with (brackets).mp4' echo "********** restore **********" @@ -84,3 +84,39 @@ teardown() { echo "********** compare **********" cmp --silent 'test/filename with (brackets).txt' 'test/filename with (brackets).txt_restored' } + +@test "convert to MKV/vp9 and restore file LICENSE" { + echo "********** convert **********" + ./binary2video -c vp9 LICENSE test/LICENSE.mkv + echo "********** restore **********" + ./video2binary test/LICENSE.mkv test/LICENSE_restored + echo "********** compare **********" + cmp --silent LICENSE test/LICENSE_restored +} + +@test "convert to MKV/h264 and restore file LICENSE" { + echo "********** convert **********" + ./binary2video -c h264 LICENSE test/LICENSE.mkv + echo "********** restore **********" + ./video2binary test/LICENSE.mkv test/LICENSE_restored + echo "********** compare **********" + cmp --silent LICENSE test/LICENSE_restored +} + +@test "convert to MKV/ffv1 and restore file LICENSE" { + echo "********** convert **********" + ./binary2video -c ffv1 LICENSE test/LICENSE.mkv + echo "********** restore **********" + ./video2binary test/LICENSE.mkv test/LICENSE_restored + echo "********** compare **********" + cmp --silent LICENSE test/LICENSE_restored +} + +@test "convert to MKV/ffv1l3 and restore file LICENSE" { + echo "********** convert **********" + ./binary2video -c ffv1l3 LICENSE test/LICENSE.mkv + echo "********** restore **********" + ./video2binary test/LICENSE.mkv test/LICENSE_restored + echo "********** compare **********" + cmp --silent LICENSE test/LICENSE_restored +} diff --git a/video2binary b/video2binary index 5899173..014e858 100755 --- a/video2binary +++ b/video2binary @@ -4,30 +4,59 @@ usage_and_exit () { echo "Usage: video2binary [-v] infile outfile" exit $1 } +check_executable_or_exit() { + if [ ! -x $1 ]; then + echo "error: no $(basename $1) at $gzip" + exit 2 + fi +} -if [ $# = 1 -a x$1 = 'x--help' ]; then +if [ \( $# -eq 1 \) -a \( "$1" = '--help' \) ]; then usage_and_exit 0 fi +# check needed binaries +gzip=/usr/bin/gzip +check_executable_or_exit $gzip +ffmpeg=/usr/bin/ffmpeg +check_executable_or_exit $ffmpeg + +# check parameters +verbose=0 +while getopts 'v' option; do + case "$option" in + v) verbose=1;; + ?) usage_and_exit 1;; + esac +done +shift $(($OPTIND - 1)) + if [ $# != 2 ]; then usage_and_exit 1 fi +if [ ! -r "$1" ]; then + echo "error: file '$1' does not exist or is not readable" + exit 4 +fi tmpfile_gzipped=$(mktemp --tmpdir video2binary.gz.XXXXXXXXXX) infile="$1" outfile="$2" -gzip=/usr/bin/gzip -ffmpeg=/usr/bin/ffmpeg pixel_format=rgb24 $ffmpeg -y -hide_banner -loglevel error \ -i "${infile}" -f rawvideo -pix_fmt rgb24 "${tmpfile_gzipped}" size_gzipped=$(stat --format=%s ${tmpfile_gzipped}) -echo "gzipped file has $size_gzipped bytes" +if [ $verbose = 1 ]; then + echo "gzipped file has $size_gzipped bytes" +fi $gzip -d -c "${tmpfile_gzipped}" > "${outfile}" -echo "output file has $(stat --format=%s "${outfile}") bytes" -md5sum "${outfile}" +if [ $verbose = 1 ]; then + echo "output file has $(stat --format=%s "${outfile}") bytes" + echo -n "output file MD5: " + md5sum "${outfile}" | cut -d\ -f1 +fi rm ${tmpfile_gzipped}