Daemon that watches a Google Drive folder and automatically uploads new audio files to iBroadcast. Also includes a one-shot script to bulk-embed artwork into existing tracks.
Polls a Google Drive folder every N seconds, uploads any new audio file not already present in iBroadcast (checked by MD5), and optionally trashes removed files.
sync.py # daemon (infinite loop)
sync.py --once # single pass and exit
sync.py --dry-run # simulate without uploading or trashing
sync.py --setup # interactive setup wizard
Config file: ~/.config/ib-drive-sync/config.json
{
"ib_email": "you@example.com",
"ib_password": "...",
"drive_token_file": "/home/you/.config/google/drive_token.json",
"drive_folder_id": "<Drive folder ID>",
"auto_trash": false,
"poll_interval": 300
}Run sync.py --setup to create this file interactively.
iBroadcast's free tier caps storage at 5 GB. Spoken word survives a low bitrate well, so sync.py can re-encode files to Opus before uploading them, leaving the Drive originals untouched:
"transcode": {
"bitrate": "32k",
"channels": 1,
"keep_original": ["Bach", "Gregorian"],
"workers": 3,
"cache_dir": "~/.cache/ib-drive-sync/opus"
}keep_original: top-level Drive folders uploaded as-is, typically music.workers: parallel download and transcode jobs. Uploads stay sequential.- Tags are kept, and the embedded cover is carried over with mutagen, since ffmpeg 6 does not write pictures into Ogg.
- Needs
ffmpegbuilt withlibopus. cache_dir(optional): keeps every Opus file it encodes, keyed by a hash of the audio stream and the settings. When only the tags or the cover change on Drive, the cached audio gets the new tags in a second instead of a full re-encode.sync.py --warm-cachefills it for the whole folder without uploading anything.
Since a transcoded file no longer has the Drive MD5, state.db decides for every file it knows, rejected uploads included, and the iBroadcast MD5 list only for unknown files. That list also keeps deleted files for hours. A file modified on Drive has its old track trashed before the new upload, otherwise iBroadcast rejects it as a tag duplicate.
The Drive token (drive_token_file) is an OAuth2 token in the format produced by tools like rclone or the Google API Python client. It must have drive.readonly scope at minimum.
Uses the iBroadcast web login form (https://login.ibroadcast.com/) — the legacy mode=status API is broken server-side. Credentials are read from the config file.
# Install dependencies
pip install -r requirements.txt
# Run as a systemd user service
cp ib-drive-sync.service ~/.config/systemd/user/
systemctl --user enable --now ib-drive-syncFor deployment to a remote server (e.g. an OCI ARM instance):
./deploy.sh <ssh-host>
ssh <ssh-host> 'systemctl --user enable --now ib-drive-sync'After each complete pass, and after each upload during a long one, the daemon writes a heartbeat line (<epoch> <threshold in minutes> <label>) to ~/.config/battements/ib-drive-sync, when that directory exists, for an external watchdog.
One-shot script tailored for the Psaumes dans la Ville audio collection (retraitedanslaville.org).
For each of the 188 psalm MP3s in a Drive folder:
- Downloads the corresponding medieval illumination from the IRHT/CNRS collection (upscaled to 500×500)
- Embeds it as an
APICID3 tag in the MP3 - Re-uploads to iBroadcast
- Trashes the original (artwork-less) track
The illuminations are miniatures from digitised medieval psalters, sourced from the IRHT (CNRS) via the Retraite dans la Ville website.
Credentials are read from ~/.config/ib-drive-sync/config.json (shared with sync.py), or from IB_EMAIL / IB_PASSWORD environment variables.
google-api-python-client
google-auth-oauthlib
requests
mutagen # for embed-artwork.py
Pillow # for embed-artwork.py
.mp3, .flac, .m4a, .ogg, .wav, .aac, .opus, .wma