marco ceppi

iBroadcast-dl

I scratched a small itch. Migrating a music library out of iBroadcast on Linux is impossible. The iBroadcast MediaSync-Lite tool has a Linux client, but only the Windows and OSX app support “Download Library”.

Their API documentation does not include any information on downloading tracks or libraries. I did some digging and wrote a small Python app: https://github.com/marcoceppi/ibroadcast-dl. This is a short thread on how I was able to figure out how the iBroadcast Sync Library works.

The online media player has a “Download Track” feature. If you select all albums and do “Download” it times out. If you do a few tracks it’ll download a “Music.zip” with the tracks in there. After setting some breakpoints in the browser I was able to capture the request. The application creates a form DOM with the following pieces. This led me to download.ibroadcast.com but this was a dead end. I’d have to loop through all the tracks from the API and create a bunch of requests.

I wanted to know what the Windows / OSX application was doing. For that I decided to first try wireshark. I ran it on my local non-linux machine and booted up the app. Set some filters and watched the traffic. While I was getting results, all the traffic was TLS encrypted - I wasn’t able to really sniff what hosts were being hit and what; if any; HTTP requests were being made. Thats when I found MITMProxy. After some hefty distrust I installed and configured it to capture all traffic.

This was pretty successful and very quickly I saw how the application was working. It used the following URL struture: download.ibroadcast.com/?user_id=<id>&token=<token>&offset=0&length=1&container_type=library&mode=downloadlibrary&end_marker=1. It then makes the same request again but with an offset of 1 and length of 50. From that point forward the offset increments by 50.

The first request downloads 1 track and gets the total number of tracks in a response header. Each subsequent request then downloads 50 tracks at a time. container_type being library allows for offset and length. This works because it downloads the tracks in the order they were uploaded. So if you track the last offset used you could “resume” a sync going forward which the MediaSync-Lite app does.

With all that I was able to build a very simple CLI that has resume features for Linux.

# pylint: disable=invalid-name
"""iBroadcastDL Module"""
from io import BytesIO
from zipfile import ZipFile
from pathlib import Path
from urllib.parse import urlunsplit, urlencode
from importlib.metadata import version as pkgversion

import ibroadcast
import requests

SCHEME = "https"
DOMAIN = "download.ibroadcast.com"


def build_url(path: str, query: dict | None = None) -> str:
    """Create a string URL from URLPARTS"""
    query = urlencode(query) if query else ""
    return urlunsplit((SCHEME, DOMAIN, path, query, ""))


class iBroadcastDL(ibroadcast.iBroadcast):
    """iBroadcast Class extension with Download functionality"""

    def __init__(
        self,
        username,
        password,
        log=None,
        client="ibroadcast-python",
        version=None,
    ):
        """Initiate a client and login"""
        version = version or pkgversion("ibroadcast-dl")
        super().__init__(username, password, log, client, version)

    def download_library(
        self, offset: int = 0, length: int = 1, dest: Path | None = None
    ) -> int | None:
        """Download Library"""
        query = {
            "user_id": self.user_id(),
            "token": self.token(),
            "offset": offset,
            "length": length,
            "container_type": "library",
            "mode": "downloadlibrary",
            "end_marker": 1,
        }
        resp = requests.get(build_url("/", query))

        archive = ZipFile(BytesIO(resp.content))
        archive.extractall(dest)
        return resp.headers.get("Response-Count", None)