Inleiding
Tot voor kort maakte ik gebruik van een zelfgebouwde webapplicatie om ChordPro muziekbestanden weer te geven op iPad en tablet-PC. Tijdens bandrepetities en optredens is dat veel praktischer dan het meeslepen van dikke mappen met pakken papier die nooit op volgorde liggen. Toch had ook deze aanpak minimaal één nadeel…
Offline
Als mijn iPad of tablet geen internetverbinding had, kon ik de webapplicatie niet gebruiken. Ik was aangewezen op een WLAN ter plaatse of het gebruik maken van een mobiele hotspot. Toen we echter overstapten van analoge naar digitale geluidstechniek, moest mijn apparaat met het WLAN van de mixer worden verbonden – en dus niet meer met internet. Wat nu?
Songbook Pro
Er zijn een groot aantal apps die het ChordPro-formaat ondersteunen en offline functioneren. Ik koos uiteindelijk voor Songbook Pro, beschikbaar voor iOS, Android en Windows. Na het uitproberen van de testversie en de aankoop kon het grote importeren beginnen. Als eerste voegde ik mijn volledige discografie toe.
Liedjes (songs) worden in mappen (folders) ondergebracht en in sets gegroepeerd. Zo'n set heeft een naam en een datum. Jammer genoeg kon de kalender in de app alléén per maand bladeren - vanaf het huidige jaar gerekend. Mijn oudste opname dateert uit 2001, dus ik zou heel wat tijd kwijt zijn geweest aan het opzoeken van al die data…
Backup van binnen
Zoals het een degelijke applicatie betaamt, kunnen alle gegevens binnen
Songbook Pro worden gebackupt naar een externe locatie. Zo kun je op z'n
minst de app met alle data opnieuw inrichten, mocht dat nodig zijn. Of
de app tussen meerdere apparaten synchroniseren. Ik was nieuwsgierig en
onderzocht zowel het backup- (SongbookPro Backup.sbpbackup
) als het
synchronisatiebestand (sbp.sync
).
Het zijn gecomprimeerde (ZIP-) bestanden, die onder elk gangbaar besturingssysteem kunnen worden geopend. Als er nog geen PDF-documenten in de app zijn geïmporteerd, zijn er slechts twee bestanden in het archief:
$ unzip -l sbp.sync;
Archive: sbp.sync
Length Date Time Name
--------- ---------- ----- ----
512518 2020-01-19 18:26 dataFile.txt
32 2020-01-19 18:26 dataFile.hash
--------- -------
512550 2 files
Aan de grootte van de beide bestanden is te zien, dat het .txt
-bestand
waarschijnlijke de data bevat en het .hash
-bestand niet méér dan –
waarempel! – een 32 byte (128 bit) hash is. Het eerste waar ik aan
dacht was het MD5-algoritme; raak!
$ unzip sbp.sync;
Archive: sbp.sync
inflating: dataFile.txt
inflating: dataFile.hash
$ cat dataFile.hash;
0ab9130e3e306b5f83c39a9671fc9a12
$ md5sum dataFile.txt;
0ab9130e3e306b5f83c39a9671fc9a12 dataFile.txt
dataFile.txt
Het databestand begint met (waarschijnlijk) een versienummer en een CRLF
regeleinde. Daarop volgt, in één regel zonder regeleinde, een verzameling
JSON
-data:
1.0<CR><LF>
{"songs":[ /* whole bunch of JSON data... */ ]}
Hoe kon ik hierin nou mijn set-datums eenvoudig aanpassen?
Script
Ik zocht een manier om het geheel in leesbare vorm in mijn favoriete
editor te kunnen openen en schreef onderstaand script. Het maakt gebruik
van md5sum
, unzip
, zip
en jq
.
#!/usr/bin/env bash
#
# Take apart a SongbookPro backup- or sync-file, open it for editing
# in your favorite texteditor, then put it back together again so the
# app accepts it and can restore it properly.
#
# Saves to a date-stamped filename in the source directory.
EDITOR="$(which geany)";
MD5SUM="$(which md5sum)";
UNZIP="$(which unzip)";
ZIP="$(which zip)";
JQ="$(which jq)";
USAGE="Usage: ${0} (.sync|.sbpbackup)-file";
if [ ${#} -ne 1 ]; then
echo "${USAGE}";
exit 1;
fi
OLDPWD="$(pwd)";
INFILE="${1}";
WORKDIR="$(mktemp --directory)";
# Switch to working dir
cd "${WORKDIR}";
# Unzip
"${UNZIP}" "${INFILE}" 2>&1 >/dev/null;
# Capture original first line, remove trailing CR
FIRSTLINE="$(head -n 1 "dataFile.txt" | tr -d '\r')";
# Save second line to separate file
tail -n +2 "dataFile.txt" > "dataFile.txt.json";
# JSON-prettify
"${JQ}" -M '.' "dataFile.txt.json" > "dataFile.txt.tmp.json";
# Open up for editing
"${EDITOR}" "dataFile.txt.tmp.json";
# Truncate original file, add original first and new second line
echo -en "${FIRSTLINE}\r\n" > "dataFile.txt";
SECONDLINE="$( "${JQ}" -c '.' "dataFile.txt.tmp.json" )";
echo -n "${SECONDLINE}" >> "dataFile.txt";
# Calculate file hash
echo -en "$( "${MD5SUM}" "dataFile.txt" | cut -d' ' -f1 )" > "dataFile.hash";
# Rezip to unique filename
OUTFILE="$(basename "${INFILE}" )";
OUTEXT="${OUTFILE#*.}";
OUTFILE="${OUTFILE%.*}-$( date '+%F-%H-%M-%S' ).${OUTEXT}";
"${ZIP}" -r "$(dirname "${INFILE}" )"/"${OUTFILE}" . -x "dataFile.txt.json" "dataFile.txt.tmp.json" 2>&1 >/dev/null;
# Clean up
rm -r "${WORKDIR}";
# Back to old working dir, exit normally
cd "${OLDPWD}";
exit 0;
Poging in PHP
Na verloop van tijd kwam het idee om de collecties van Songbook Pro en mijn eigen app te synchroniseren. In elk geval wilde ik graag mijn app als een soort van backup achter de hand houden – voor het geval dat. Daarvoor moest ik dan wel de backup- of synchronisatiebestanden van Songbook Pro kunnen inlezen in PHP. Ik schreef het volgende script:
function extractData(string $filename)
{
$zip = new \ZipArchive();
if ($zip->open('sbp.sync') !== true) {
return false;
}
for ($i = 0; $i < $zip->numFiles; $i++) {
if ($zip->getNameIndex($i) == 'dataFile.txt') {
// Get a resource pointer to the compressed datafile
$ptr = $zip->getStream($zip->getNameIndex($i));
// Read the uncompressed file and split it by CRLF
$json = explode("\r\n", stream_get_contents($ptr));
// Disregard first line, keep the second line
$json = array_pop($json);
// Decode the JSON into PHP objects
$json = json_decode($json);
}
}
$zip->close();
return $json;
}
Jammer maar helaas
Toen ik eenmaal alle gegevens kon uitlezen, kwam ik een fundamenteel verschil tussen beide apps op het spoor. Mijn app heeft de volgende (boom-)structuur:
Book > Set > Song2Set > Song
In één Book
zitten één of meerdere Set
s. Meerdere Song
s zijn via
een koppeltabel Song2Set
met Set
verbonden. Songs kunnen dus aan
meerdere sets worden toegevoegd.
Songbook Pro gebruikt daarentegen de volgende structuur:
Folder > Song
Set > Song
Song
s worden rechtstreeks aan een Folder
gekoppeld. Set
s bestaan
uit één of meerdere gekoppelde Songs. De Set- en Folder-koppeling staat
los van elkaar.
Dit verschil in opbouw is niet zomaar te overbruggen. Ik zou ofwel mijn eigen app compleet moeten ombouwen, of de situatie accepteren en verder gaan met andere projecten. Ik koos voor dat laatste :-). Mijn inspanningen zijn zeker niet voor niets geweest; ik heb er een hoop van geleerd!