Skip to content

Commit

Permalink
Add more options and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
HbHbNr committed Feb 19, 2024
1 parent 02e1c58 commit cf73710
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 52 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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)
113 changes: 80 additions & 33 deletions binary2video
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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}
60 changes: 48 additions & 12 deletions test/test.bats
Original file line number Diff line number Diff line change
@@ -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" {
Expand All @@ -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 **********"
Expand All @@ -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 **********"
Expand All @@ -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 **********"
Expand All @@ -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 **********"
Expand All @@ -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 **********"
Expand All @@ -76,11 +76,47 @@ 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 **********"
./video2binary 'test/filename with (brackets).mp4' 'test/filename with (brackets).txt_restored'
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
}
41 changes: 35 additions & 6 deletions video2binary
Original file line number Diff line number Diff line change
Expand Up @@ -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}

0 comments on commit cf73710

Please sign in to comment.