Create a gist now

Instantly share code, notes, and snippets.

Attempt to repair an interrupted QuickTime Player audio recording
<?xml version="1.0" encoding="UTF-8"?>
<ufwb version="1.14">
<grammar name="AIFC grammar" start="id:530" author="Andre LaBranche" uti="public.aifc-audio">
<description>Grammar for AIFC files</description>
<structure name="AIFC file" id="530" length="Size + 4" alignment="0" encoding="ISO_8859-1:1987" endian="big" signed="no" valueexpression="Size">
<string name="Type ID" mustmatch="yes" id="531" fillcolor="FFD8B2" type="fixed-length" length="4" encoding="ISO_8859-1:1987">
<fixedvalues>
<fixedvalue name="TypeID" value="FORM"/>
</fixedvalues>
</string>
<number name="Size" id="532" fillcolor="D8D8FE" type="integer" length="4"/>
<string name="Form Type" mustmatch="yes" id="533" fillcolor="FFB2FE" type="fixed-length" length="4">
<fixedvalues>
<fixedvalue name="Form Type" value="AIFC"/>
<fixedvalue name="Form Type" value="AIFF"/>
</fixedvalues>
</string>
<structure name="AIFC Version" id="534" length="0" alignment="2" repeatmin="0" valueexpression="timeStamp">
<string name="Type ID" mustmatch="yes" id="535" fillcolor="FFD8B2" type="fixed-length" length="4">
<fixedvalues>
<fixedvalue name="Version" value="FVER"/>
</fixedvalues>
</string>
<number name="Size" mustmatch="yes" id="536" fillcolor="D8D8FE" type="integer" length="4">
<fixedvalues>
<fixedvalue name="Size" value="4"/>
</fixedvalues>
</number>
<number name="timeStamp" mustmatch="yes" id="537" fillcolor="FFB2FE" type="integer" length="4">
<fixedvalues>
<fixedvalue name="Version" value="2726318400"/>
</fixedvalues>
</number>
</structure>
<structure name="Common" id="539" length="Size + 8" alignment="2" valueexpression="Desc">
<string name="Type ID" mustmatch="yes" id="540" fillcolor="FFD8B2" type="fixed-length" length="4">
<fixedvalues>
<fixedvalue name="Type ID" value="COMM"/>
</fixedvalues>
</string>
<number name="Size" id="541" fillcolor="D8D8FE" type="integer" length="4"/>
<number name="NumChannels" id="542" fillcolor="B2FDFF" type="integer" length="2"/>
<number name="numSampleFrames" id="543" fillcolor="FFFAB2" type="integer" length="4"/>
<number name="sampleSize" id="544" fillcolor="D8D8FE" type="integer" length="2"/>
<structure name="SampleRate" id="545" length="10" alignment="0" repeatmin="0" valueexpression="Rate">
<number name="Rate" id="546" fillcolor="FFE4B2" type="float" length="64" lengthunit="bit"/>
</structure>
<string name="Compression" id="548" fillcolor="F3FFB2" repeatmin="0" type="fixed-length" length="4"/>
<string name="Desc" id="549" fillcolor="B2D4FF" repeatmin="0" type="pascal" length="actual"/>
</structure>
<structure name="Chan" id="551" alignment="0" repeatmin="0" fillcolor="D5D5D5">
<string name="Type ID" mustmatch="yes" id="552" fillcolor="FFD8B2" type="fixed-length" length="4">
<fixedvalues>
<fixedvalue name="Type ID" value="CHAN"/>
</fixedvalues>
</string>
<number name="Size" id="553" fillcolor="D8D8FE" type="integer" length="4"/>
<structure name="ChanData" id="554" length="Size" alignment="2"/>
</structure>
<structure name="Filler" id="557" length="0" alignment="0" repeatmin="0" fillcolor="E6E6E6" valueexpression="Size">
<string name="Type ID" mustmatch="yes" id="558" type="fixed-length" length="4">
<fixedvalues>
<fixedvalue name="Type ID" value="FLLR"/>
</fixedvalues>
</string>
<number name="Size" id="559" type="integer" length="4"/>
<structure name="Filler" id="560" length="Size" alignment="0" valueexpression="Size"/>
</structure>
<structure name="Extra" id="563" length="Size + 8" alignment="2" repeatmin="0" repeatmax="-1">
<string name="Type ID" mustmatch="yes" id="564" fillcolor="FFD8B2" type="fixed-length" length="4">
<fixedvalues>
<fixedvalue name="Type" value="AUTH"/>
<fixedvalue name="Type" value="NAME"/>
<fixedvalue name="Type" value="ANNO"/>
</fixedvalues>
</string>
<number name="Size" id="565" fillcolor="D8D8FE" type="integer" length="4"/>
<string name="Value" id="566" fillcolor="FFB2FE" type="fixed-length" length="Size"/>
</structure>
<structure name="SSND" id="568" length="Size - 8" alignment="2" repeatmin="0" repeatmax="-1" valueexpression="Size">
<string name="Type ID" mustmatch="yes" id="569" fillcolor="FFD8B2" type="fixed-length" length="4">
<fixedvalues>
<fixedvalue name="Type ID" value="SSND"/>
</fixedvalues>
</string>
<number name="Size" id="570" fillcolor="D8D8FE" type="integer" length="4"/>
<number name="Offset" id="571" fillcolor="FFB2FE" type="integer" length="4"/>
<number name="blockSize" id="572" fillcolor="D8D8FE" type="integer" length="4"/>
<structure name="Sound Data" id="573" length="prev.Size - 4" alignment="0" fillcolor="D8F9C7" valueexpression="Size"/>
</structure>
</structure>
</grammar>
</ufwb>
#!/bin/zsh
#set -e
#set -x
# dre@mac.com
#
# This script attempts to repair a partial AIFC recording generated by
# QuickTime Player. This happens when QTP is interrupted while recording.
# Look for partial files with:
# find ~/Library/Containers/com.apple.QuickTimePlayerX -name "*.aifc"
# AIFC Reference: http://muratnkonar.com/aiff/index.html
#
# NOTE! This script edits the target file in place.
unset s # file size
unset form # FORM offset
unset ssnd # SSND offset
unset a_sz # AIFC data size
unset s_sz # SSND data size
# Check command line input
if [ -z $1 ] ; then
echo 'Supply the (quoted!) path of a broken AIFC recording as the only '
echo 'argument.'
exit 1
else
if [ ! -f $1 ] ; then
echo "Can't find file $1!"
exit 1
fi
file=${1}
fi
# Examine this many bytes in the file for headers to fix
peek=10000
# Error handling
checkNull() {
if [[ $# -ne 2 ]] ; then
echo "Incorrect number of args!"
exit 1
fi
if [ -z ${1} ] ; then
echo "Function description missing!"
exit 1
fi
if [ -z ${2} ] ; then
echo "Failed to ${1} - bailing."
exit 1
else
printf "%36s : %s \n" ${1} ${2}
fi
}
checkFail() {
if [[ $# -ne 2 ]] ; then
echo "Incorrect number of args!"
exit 1
fi
if [ -z ${1} ] ; then
echo "Function description required!"
exit 1
fi
if [[ ${2} -ne 0 ]] ; then
echo "Error! Could not ${1}!"
exit 1
else
printf "%36s : ... done\n" ${1}
fi
}
# get file size
s=$(stat -f "%z" ${file})
checkNull "find file size" ${s}
#### FORM chunk
#
# get start of FORM chunk
form=$(xxd -l ${peek} -c 4 ${file} | grep "FORM")
checkFail "find FORM offset" $?
form=$(echo $form | awk -F: '{print $1}')
checkFail "capture the FORM offset" $?
form=$(printf "%d" \0x${form})
checkNull "convert FORM offset to decimal" ${form}
# offset by 4 (size of "FORM")
form=$(( $form + 4 ))
checkNull "find end of FORM header" ${form}
# calculate size of AIFC data chunk
a_sz=$(( $s - $form ))
checkNull "calculate AIFC data size" ${a_sz}
# convert the AIFC byte size from decimal to hex
b=$(printf "%08x" $a_sz) # aabbccdd
checkNull "convert AIFC size to hex" $b
# prepend each hex digit with '\x'
c=''
for (( i=0; i<${#b}; i=i+2 )); do
c=${c}$(echo -en \\x${b:$i:2})
done
# write the calculated FORM size
echo -ne "$c" | \
dd conv=notrunc of=${file} bs=1 seek=${form} count=4 &> /dev/null
checkFail "write new AIFC size header" $?
#### SSND chunk
#
# find start of SSND chunk
ssnd=$(xxd -l ${peek} -c 4 ${file} | grep "SSND")
checkFail "find SSND offset" $?
ssnd=$(echo $ssnd | awk -F: '{print $1}')
checkFail "capture the SSND offset" $?
ssnd=$(printf "%d" \0x${ssnd})
checkNull "convert SSND offset to decimal" ${ssnd}
# offset by 4 (size of "SSND")
ssnd=$(( $ssnd + 4 ))
checkNull "find end of SSND header" ${ssnd}
# calculate size of SSND data chunk
s_sz=$(( $s - $ssnd ))
checkNull "calculate SSND data size" ${s_sz}
# convert the AIFC byte size from decimal to hex
b=$(printf "%08x" $s_sz)
checkNull "convert SSND size to hex" $b
# prepend each hex digit with '\x'
c=''
for (( i=0; i<${#b}; i=i+2 )); do
c=${c}$(echo -en \\x${b:$i:2})
done
# write the calculated SSND size
echo -ne "$c" | \
dd conv=notrunc of=${file} bs=1 seek=${ssnd} count=4 &> /dev/null
checkFail "write new SSND size header" $?
echo '\nAll done!'
@dreness
Owner
dreness commented Feb 1, 2016
╭─ andre@flux ~/work/aifc_hacking
╰─ $ ~/bin/fix-aifc.sh test.aifc 
                      find file size : 1831936 
                    find FORM offset : ... done
             capture the FORM offset : ... done
      convert FORM offset to decimal : 0 
             find end of FORM header : 4 
            calculate AIFC data size : 1831932 
            convert AIFC size to hex : 001bf3fc 
          write new AIFC size header : ... done
                    find SSND offset : ... done
             capture the SSND offset : ... done
      convert SSND offset to decimal : 4080 
             find end of SSND header : 4084 
            calculate SSND data size : 1827852 
            convert SSND size to hex : 001be40c 
          write new SSND size header : ... done

All done!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment