Skip to content

Instantly share code, notes, and snippets.

@triplingual
Last active February 14, 2024 03:18
Show Gist options
  • Save triplingual/a5aa4f15d8d255cbfd3f574d67f756bc to your computer and use it in GitHub Desktop.
Save triplingual/a5aa4f15d8d255cbfd3f574d67f756bc to your computer and use it in GitHub Desktop.
Script to transform Apple iTunes' .itc files into images (JPG or PNG)
#!/bin/bash
# Script to transform Apple iTunes' .itc files into images (JPG or PNG)
#
# This is a mod of the very useful script by Matthieu Riegler (@kyro38) at
# https://github.com/kyro38/MiscStuff/blob/master/Useless/itc/itcToImageV2.sh
#
# The mods are
# * to use `find` rather than `gfind` (I'm on a Mac and find` works just fine
# and I don't need to install `coreutils` just for this task)
# * to parameterize the source and destination directories for the .itc files
# * to add some communication with the user
# * to count the ITC files being processed in order to get a confirmation from the user
#
# My iTunes folder is usually nonstandard, I want to be able to run this on Album Artwork
# folders copied from others' machines (who don't want me hogging their machine for this
# effort), and I'm not consistent on my own machines whether I use the standard directory
# structure for iTunes.
#
# .itc files may be located in ~/Music/iTunes/Album Artwork unless you, like I,
# have customized iTunes' directory
#
# This script uses (/!\ needs ) ImageMagick's convert, hexdump, printf and dd.
#
# "Newer" (>2012?) iTunes ITC files may contain ARGB content, with as many as 3 sizes
# of the same image in one ITC file. Because of that, and depending on the size
# of your artwork directory, this script might be a little slow. One alternative
# is Simon Kennedy's Python module: http://www.sffjunkie.co.uk/python-itc.html
#
# Have a nice day.
# Test for inputs, use defaults if absent
if [ -z $1 ]; then
# absent any parameters, use defaults
AlbumArtwork="${HOME}/Music/iTunes/Album Artwork"
DestinationDir="$AlbumArtwork/Images"
echo
echo "Using default directories:"
echo " Source: " $AlbumArtwork
echo " Destination:" $DestinationDir
else
# Source dir
AlbumArtwork=$1
# Destination dir
DestinationDir=$2
echo
echo "Using these directories:"
echo " Source: " $AlbumArtwork
echo " Destination:" $DestinationDir
echo
fi
# Get count of ITC files for notice to script executor
FileCount=`find "$AlbumArtwork" -name '*.itc' | wc -l`
read -r -p "About to extract all art from
$AlbumArtwork
to
$DestinationDir.
There are $FileCount .itc files to be processed.
Continue (y/n)? " choice
case "$choice" in
y|Y )
echo "OK! Here we go."
;;
n|N )
echo "Abandoning script."
exit 1
;;
* )
echo "Invalid response, exiting."
exit 1
;;
esac
IFS=$'\n'
if [ ! -d "$DestinationDir" ]; then
mkdir "$DestinationDir"
echo "new Images dir"
fi
for file in `find "$AlbumArtwork" -name '*.itc'`; do
start=0x11C
exit=0;
i=1;
echo $file
while [ 1 ]; do
typeOffset=$(($start+0x30))
imageType=$(hexdump -n 4 -s $typeOffset -e '"0x"4/1 "%02x" "\n"' $file)
#If there is no next byte, jump to the next itc file.
if [[ -z $imageType ]]; then
break
fi
imageOffsetOffset=$(($start+8))
itemSize=$(hexdump -n 4 -s $start -e '"0x"4/1 "%02x" "\n"' $file)
imageOffset=$(hexdump -n 4 -s $imageOffsetOffset -e '"0x"4/1 "%02x" "\n"' $file)
imageStart=$(($start+$imageOffset))
imageSize=$(($itemSize-imageOffset))
imageWidth=$(hexdump -n 4 -s $(($start+56)) -e '"0x"4/1 "%02x" "\n"' $file)
imageWidth=$(printf "%d" $imageWidth)
imageHeight=$(hexdump -n 4 -s $(($start+60)) -e '"0x"4/1 "%02x" "\n"' $file)
imageHeight=$(printf "%d" $imageHeight)
dir=$(dirname "$file")
xbase=${file##*/} #file.etc
xpref=${xbase%.*} #file prefix
#echo $file
#echo itemsize $itemSize
#echo start $start
#echo imageOffset $imageOffset
#echo imageStart $imageStart
#echo imageSize $imageSize
#echo imageWidth $imageWidth
#echo imageHeight $imageHeight
if [[ $imageType -eq 0x504E4766 ]] || [[ $imageType -eq 0x0000000E ]] ; then
targetFile="$DestinationDir/$xpref-$i.png"
if [ ! -f "$targetFile" ]; then
echo PNG
dd skip=$imageStart count=$imageSize if="$file" of="$targetFile" bs=1 &> /dev/null
fi
elif [[ $imageType -eq 0x41524762 ]] ; then
targetFile="$DestinationDir/$xpref-$i.png"
if [ ! -f "$targetFile" ]; then
echo ARGB
dd skip=$imageStart count=$imageSize if="$file" of="$TMPDIR/test$i" bs=1 &> /dev/null
#Using a matrix to convert ARGB to RGBA since imagemagick does only support rgba input
convert -size $imageWidth"x"$imageHeight -depth 8 -color-matrix '0 1 0 0 0 0 1 0 0 0 0 1 1 0 0 0' rgba:"$TMPDIR/test$i" "$targetFile"
fi
elif [[ $imageType -eq 0x0000000D ]] ; then
targetFile="$DestinationDir/$xpref-$i.jpg"
if [ ! -f "$targetFile" ]; then
echo JPG
dd skip=$imageStart count=$imageSize if="$file" of="$targetFile" bs=1 &> /dev/null
fi
else
echo $imageType
exit=1
break;
fi
start=$(($start+$itemSize))
i=$(($i+1))
done
done
@Johnbo
Copy link

Johnbo commented Aug 31, 2022

Thanks for this script, it has been really helpful.

In my case, it fell into the else clause in line 146 (imageType was 0x00002328), so I decided to simply save it as a TIFF file and it worked seamlessly. Then I tried changed it to JPG or PNG and it still worked. So I assume that, as long as you give it a valid image extension, tools like Gimp are able to open it.

@triplingual
Copy link
Author

Glad it was useful for you! It's been so long since I looked at this that the original user's script is gone. My abilities extend only as far as modding the original, so if you have improvements a) they are most welcome and b) I'll need you to spell them out.

@Johnbo
Copy link

Johnbo commented Aug 31, 2022

I made no improvement at all, I simply added your lines 141-145 under 147 in order to always save as JPG if the imageType is not recognized. In other words: make JPG the default extension, even when it does not match with the hexdump.

@triplingual
Copy link
Author

Got it -- thank you for that!

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