diff --git a/README.md b/README.md index 5441ad2..66512fb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # muxsa -muxsa (MUltipleXer for Slides and Audio): a collection of Linux tools for efficiently creating video files from presentation slides and background narration. \ No newline at end of file +muxsa (MUltipleXer for Slides and Audio): a collection of Linux tools for efficiently creating video files from presentation slides and background narration. + +## Documentation +* [Demonstration video (a.k.a. the making of Network Security 2020)](https://nks-devel3.rus.uni-stuttgart.de/muxsa/making_of_netsec_2020.mp4) +* [muxsa block diagram](doc/muxsa-blockdiagram.png) diff --git a/bin/muxsa-al2fc b/bin/muxsa-al2fc new file mode 100755 index 0000000..bc6805a --- /dev/null +++ b/bin/muxsa-al2fc @@ -0,0 +1,108 @@ +#!/usr/bin/perl -w +use warnings; +use strict; + +# muxsa-al2fc +# +# part of muxsa, https://git-nks-public.tik.uni-stuttgart.de/edu/muxsa +# +# this script reads a "label track" that was exported by the +# Audacity (https://www.audacityteam.org/) audio recoder/editor. +# It assumes that the labels contain slide numbers. +# It will output (to standard output) a sequence of statements that +# can be read by the ffmpeg (https://ffmpeg.org/) concat multiplexer. +# +# MIT License +# +# Copyright (c) 2023 Sebastian Kiesel +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +############################################################################ + + +sub printf_file { + my $slide_prefix = $ENV{'MUXSA_KVM2PNG_SLIDE_PREFIX'}//""; + if( $_[0] =~ /^[0-9]+$/ ){ + printf("file '%s%04i.png'\n", $slide_prefix, $_[0]); + } + else { + printf("file '%s%s.png'\n", $slide_prefix, $_[0]); + } +} + +sub printf_duration { + printf("duration %.6f\n", $_[0]); +} + +############################################################################ + +my @cur; +my @prev; + +printf("# Beginning of ffmpeg concat sequence produced by muxsa-al2fc\n"); + +while(<>) { + chomp(); + + # label start time, label end time, label text + @cur = /^(-?[0-9]+\.[0-9]+)\t(-?[0-9]+\.[0-9]+)\t(.*)$/ or next; + + if(!@prev){ # no previous label -> this is the first label we read + if( $cur[0] != 0.0 ){ + printf("# Warning: 1st label ts changed: %.6f to 0.0\n", $cur[0]); + $cur[0]=0.0; + } + @prev = @cur; + next; + } + + my $duration = $cur[0] - $prev[0]; + printf("# Label \"%s\" from %.6f to %.6f duration %.6f\n", + $prev[2], $prev[0], $cur[0], $duration); + + while( $duration > 1.0 ){ + printf_file($prev[2]); + printf_duration(1.0); + $duration-=1.0; + } + printf_file($prev[2]); + printf_duration($duration); + + if( uc($cur[2]) eq "END" ){ + printf("# END label at %.6f\n",$cur[0]); + # known bug in the demuxer: + # the last slide name has to be repeated w/o duration + printf_file($prev[2]); + printf("# End of ffmpeg concat sequence produced by muxsa-al2fc\n"); + exit(0); + } + @prev = @cur; +} + +# we have reached the end of the input file without an explicit END label +# ASSUME that the last slide should be shown for 1 sec +printf("# EOF but no END label. ASSUMING a duration for the last slide.\n"); +printf_file($cur[2]); +printf_duration(1.0); +printf_file($cur[2]); + +printf("# End of ffmpeg concat sequence produced by muxsa-al2fc\n"); + +############################################################################ diff --git a/bin/muxsa-kvm2png b/bin/muxsa-kvm2png new file mode 100755 index 0000000..9916046 --- /dev/null +++ b/bin/muxsa-kvm2png @@ -0,0 +1,224 @@ +#!/bin/bash + +# muxsa-kvm2png +# +# part of muxsa, https://git-nks-public.tik.uni-stuttgart.de/edu/muxsa +# +# this script makes a series of screenshots from a KVM virtual machine +# and saves them as PNG files. It will press the PgDown key after each +# screenshot. Intended usage: dump a presentation (e.g., Powerpoint +# running in a Windows VM) into a series of PNG files, for further +# processing with the muxsa tool chain. + +# MIT License +# +# Copyright (c) 2023 Sebastian Kiesel +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +############################################################################ + +echo "This is muxsa-kvm2png." + +# various parameters can be set as an environment variable (export XX="y z") +# or can be set in our RC file. Otherwise we will assign default values below. + +if [ -r "${HOME}/.muxsarc" ] ; then + echo "Found ${HOME}/.muxsarc ... will source it." + . "${HOME}/.muxsarc" +fi + +echo "Will work with these settings:" + +echo "MUXSA_KVM2PNG_VM_NAME=${MUXSA_KVM2PNG_VM_NAME:="win10-office"}" +echo "MUXSA_KVM2PNG_SIZE_X=${MUXSA_KVM2PNG_SIZE_X:="1920"}" +echo "MUXSA_KVM2PNG_SIZE_Y=${MUXSA_KVM2PNG_SIZE_Y:="1080"}" +echo "MUXSA_KVM2PNG_EXTRA_X=${MUXSA_KVM2PNG_EXTRA_X:="0"}" +echo "MUXSA_KVM2PNG_EXTRA_Y=${MUXSA_KVM2PNG_EXTRA_Y:="0"}" +echo "MUXSA_KVM2PNG_SLIDE_PREFIX=${MUXSA_KVM2PNG_SLIDE_PREFIX:=""}" +echo "MUXSA_KVM2PNG_SLIDE_NUMBER_START=${MUXSA_KVM2PNG_SLIDE_NUMBER_START:="0"}" +echo "MUXSA_KVM2PNG_SLIDE_NUMBER_END=${MUXSA_KVM2PNG_SLIDE_NUMBER_END:="9999"}" +echo "MUXSA_KVM2PNG_FORCE_OVERWRITE=${MUXSA_KVM2PNG_FORCE_OVERWRITE:=""}" +echo "MUXSA_KVM2PNG_BUGFIX_SHOOT_TWICE=${MUXSA_KVM2PNG_BUGFIX_SHOOT_TWICE:=""}" +echo "MUXSA_KVM2PNG_SLEEP=${MUXSA_KVM2PNG_SLEEP:="3"}" +echo "MUXSA_KVM2PNG_MD5_LAST_SLIDE=${MUXSA_KVM2PNG_MD5_LAST_SLIDE:="092eb68a91b4d2a73833f00a01dc5cb0"}" +echo + +############################################################################ + +TMP_FILE="$(mktemp -t muxsa-kvm2png-XXXXXXXXXX.ppm)" +if [ ! -w "${TMP_FILE}" ] ; then + echo "Error: cannot create tmp_file. abort." + exit 1 +fi +trap 'rm -f -- "${TMP_FILE}"' EXIT + +############################################################################ + +muxsa-kvm2png-screenshot-to-tmp () { + echo "muxsa-kvm2png: screenshot ${MUXSA_KVM2PNG_VM_NAME} ${TMP_FILE}" + virsh -c qemu:///system "screenshot ${MUXSA_KVM2PNG_VM_NAME} ${TMP_FILE}" + if [ $? -ne 0 ] ; then + echo "muxsa-kvm2png: screenshot failed. abort." + exit 1 + fi + + # the first sceenshot after a resolution change is sometimes garbled + # workaround: save it twice + if [ "${MUXSA_KVM2PNG_BUGFIX_SHOOT_TWICE}" ] ; then + virsh -c qemu:///system \ + "screenshot ${MUXSA_KVM2PNG_VM_NAME} ${TMP_FILE}" + if [ $? -ne 0 ] ; then + echo "muxsa-kvm2png: screenshot failed. abort." + exit 1 + fi + fi + + # get the size in pixels of the screenshot we have taken + # (needs identify from imagemagick), and ... + TMP_SZ_X=$(identify -format "%w" "${TMP_FILE}") + TMP_SZ_Y=$(identify -format "%h" "${TMP_FILE}") + + # ... compare to desired size + if [ ${TMP_SZ_X} -eq ${MUXSA_KVM2PNG_SIZE_X} -a \ + ${TMP_SZ_Y} -eq ${MUXSA_KVM2PNG_SIZE_Y} ] ; then + echo "Screenshot is ${TMP_SZ_X} x ${TMP_SZ_Y} - as desired. good." + TMP_SZ_OK=1 + else + echo "Warning: Screenshot is ${TMP_SZ_X} x ${TMP_SZ_Y} but desired"\ + "size is ${MUXSA_KVM2PNG_SIZE_X} x ${MUXSA_KVM2PNG_SIZE_Y} ." + TMP_SZ_OK="" + fi +} + +############################################################################ + +echo "Performing a test screenshot to check image size." +muxsa-kvm2png-screenshot-to-tmp +if [ ! "${TMP_SZ_OK}" ] ; then + echo "We will try to adjust the size of the spicy window." + case "$(read -p "Press q [return] to abort or [return] to continue: ")" in + n|N|q|Q) + echo "muxsa-kvm2png aborted." + exit 1; + ;; + esac + xdotool search --class spicy windowsize %@ \ + $(( ${MUXSA_KVM2PNG_SIZE_X} + ${MUXSA_KVM2PNG_EXTRA_X} )) \ + $(( ${MUXSA_KVM2PNG_SIZE_Y} + ${MUXSA_KVM2PNG_EXTRA_Y} )) + sleep 5 + echo "Performing another test screenshot to check image size." + muxsa-kvm2png-screenshot-to-tmp + if [ ! "${TMP_SZ_OK}" ] ; then + echo "Screenshot still does not have the desired size in pixels." + echo "Maybe, resizing the spicy window has failed." + echo "Or maybe, you need to adjust MUXSA_KVM2PNG_EXTRA_X / _Y" + # extra size accounts for menus of the spicy window. + # if the desired size is larger than the actual size, that + # difference has to be added to the current extra size + echo "Current MUXSA_KVM2PNG_EXTRA_X value: ${MUXSA_KVM2PNG_EXTRA_X}" + echo "Try: $(( ${MUXSA_KVM2PNG_SIZE_X} - ${TMP_SZ_X} + \ + ${MUXSA_KVM2PNG_EXTRA_X} ))" + echo "Current MUXSA_KVM2PNG_EXTRA_Y value: ${MUXSA_KVM2PNG_EXTRA_Y}" + echo "Try: $(( ${MUXSA_KVM2PNG_SIZE_Y} - ${TMP_SZ_Y} + \ + ${MUXSA_KVM2PNG_EXTRA_Y} ))" + echo "You can set these as environment variables or in ~/.muxsarc" + echo "muxsa-kvm2png aborted." + exit 1; + fi +fi +echo "Successfully took test screenshot with the right size." + +############################################################################ + +cat < "$OUTFILE" + if [ $? -ne 0 ] ; then + echo "Converting and saving screenshot failed. abort." + exit 1 + fi + + if [ "${MUXSA_KVM2PNG_MD5_LAST_SLIDE}" ] ; then + if md5sum "${OUTFILE}" | \ + grep -q "^${MUXSA_KVM2PNG_MD5_LAST_SLIDE}" ; then + + echo "Screenshot's md5sum = MUXSA_KVM2PNG_MD5_LAST_SLIDE. end." + break + fi + fi + + if [ "${SLIDE_NUMBER}" -gt "${MUXSA_KVM2PNG_SLIDE_NUMBER_END}" ] ; then + echo "Maximum slide number reached. abort." + exit 1 + fi + + echo "Sending PgDown and sleeping briefly" + virsh -c qemu:///system "send-key ${MUXSA_KVM2PNG_VM_NAME} KEY_PAGEDOWN" + sleep "${MUXSA_KVM2PNG_SLEEP}" + +done +echo "muxsa-kvm2png finished successfully!" + +############################################################################ diff --git a/bin/muxsa-pngaac2mp4 b/bin/muxsa-pngaac2mp4 new file mode 100755 index 0000000..e4cb2cc --- /dev/null +++ b/bin/muxsa-pngaac2mp4 @@ -0,0 +1,78 @@ +#!/bin/bash + +# muxsa-pngaac2mp4 +# +# part of muxsa, https://git-nks-public.tik.uni-stuttgart.de/edu/muxsa +# +# this script tells ffmpeg (https://ffmpeg.org/) to read +# a series of slides in png file format (created with muxsa-kvm2png), +# a soundtrack.aac (recorded with audacity), and +# a ffmpeg concat multiplexer sequence file (created with muxsa-al2fc) +# and multiplex everything together +# into a mp4 video of the slideshow with background narration + +# MIT License +# +# Copyright (c) 2023 Sebastian Kiesel +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +############################################################################ +# see https://trac.ffmpeg.org/wiki/Slideshow + +echo "This is muxsa-pngaac2mp4." + +OUTFILE="out.mp4" +if [ "$1" ] ; then + OUTFILE="$1" + # make sure that file ends on .mp4 + OUTFILE="${OUTFILE%%.mp4}.mp4" +fi + +if [ ! -f slides.concat ] ; then + echo "error: file slides.concat missing. abort." + echo "consider using muxsa-al2fc for creating one." + exit 1 +fi + +if [ -f soundtrack.m4a ] ; then + AUDIOFILE="-i soundtrack.m4a" + AUDIOOPTS="-c:a copy" +else + if [ -f soundtrack.wav ] ; then + AUDIOFILE="-i soundtrack.wav" + AUDIOOPTS="-c:a aac -b:a 128k" + else + if [ -f audio.concat ] ; then + AUDIOFILE="-f concat -i audio.concat" + AUDIOOPTS="-c:a aac -b:a 128k" + else + echo "neither soundtrack.m4a, .wav, nor audio.concat found. abort." + exit 1 + fi + fi +fi + +ffmpeg -f concat -i slides.concat ${AUDIOFILE} \ + -vsync cfr -vf fps=25 \ + -c:v libx264 -preset slow -tune stillimage -crf 23 -pix_fmt yuv420p \ + ${AUDIOOPTS} \ + -movflags +faststart -brand mp42 "${OUTFILE}" + +############################################################################ diff --git a/doc/muxsa-blockdiagram.png b/doc/muxsa-blockdiagram.png new file mode 100644 index 0000000..25af68d Binary files /dev/null and b/doc/muxsa-blockdiagram.png differ