Vimeo download escalation

Vimeo has recently deployed countermeasures that makes it harder to download videos, unless the uploader has marked them as downloadable.

This affects both my youtubedown script, and Miro. Since Miro is what I use to subscribe to most of the blogs through which I discover new music videos, this negatively impacts my mixtapes, which is how it impacts you.

So help me reverse-engineer their new countermeasures, ok? If we fix this bug, my mixtapes get better.

Here's an example:

As of their new site roll-out a month or two ago, the new way to download that video would be to load "" with the header "X-Requested-With: XMLHttpRequest", but with private videos, that's a 404. However, if you request that style URL on a downloadable video (here's one) then you get HTML with links to MP4 files.

On a private video, when you hit "Play" in either the Flash player or the HTML5 player, it loads "" which returns the full MP4. Those URLs go 403 after some small number of minutes, and it loads a URL with different hex each time you hit play (though the decimal numbers stay the same), so presumably the ctime is a part of the hash.

The fact that this works in the HTML5 player means that they are computing those URLs from Javascript somehow, rather than with a secret key that is baked into their Flash player, so that's promising. But I don't have a lot of experience reverse-engineering gigantic Javascript apps.

Since it will be the first thing you find when googling, let me point out that the old moogaloop URLs like "" are 404. You used to be able to use those to get a signature, then construct a download URL like: "", but no more.


Tags: , , , , ,

14 Responses:

  1. This jumped out at me.

    getVideoUrl: function(a) {
    var b = this.config, e = this.controller.playableFiles,
    d = e[a], c = null;
    if (!e[a]) {
    u.warn("No playable file of quality", a);
    return a == S && g.mobileCanPlaySD() ? this.getVideoUrl(Da) : c
    if (((new Date).getTime() / 1E3).round() - this.loadedTime > 21E3) {"Old signature expired, getting a new one!", b.request.signature);
    e = "//" + b.request.player_url + "/config/" + + "/signature?prev_signature=" + b.request.signature + "&prev_timestamp=" + b.request.timestamp;
    e = (new XHR).getWithCredentials(e);
    b.request.signature = e.signature;
    b.request.timestamp = e.timestamp;
    this.loadedTime =
    ((new Date).getTime() / 1E3).round();"New signature:", b.request.signature)
    if (d) {
    c = "//" + b.request.player_url + "/play_redirect";
    c = c + ("?quality=" + a) + ("&codecs=" + d) + ("&clip_id=" +;
    c = c + ("&time=" + b.request.timestamp);
    c = c + ("&sig=" + b.request.signature);
    c = c + ("&type=html5_" + this.controller.playerMode + "_" + (b.embed.on_site ? "local" : "embed"))
    u.debug("Video URL: " + c);
    return c

    • That URL is constructed like (to continue with your example)

      Then it's a 302 redirect to the raw MP4 on their CDN. (

  2. timdoug says:

    rg3's youtube-dl works fine with Vimeo links:

    $ youtube-dl
    [vimeo] 43940325: Downloading webpage
    [vimeo] 43940325: Extracting information
    [download] Destination: 43940325.mp4
    [download] 100.0% of 74.49M at 835.22k/s ETA 00:00

    The Vimeo-specific code here might help.

    • jwz says:

      Wow, that was fast! Thanks!

      I had looked at the HTML in the page before, but plugging those values in to the URL didn't work; I hadn't realized that the play_redirect URL was key.

      youtubedown updated, Miro notified!

  3. Pedro says:

    I see your problem has already been solved, but I'll leave this neat trick here anyway because it neatly circumvents this kind of website silliness by directly grabbing the local temp file created by the flash player plugin. (Caveat: you will need linux).

    Step 1: find the temporary file (which will already be unlinked):

    $ lsof +L1 | grep /tmp/Flash
    plugin-co 17666 paol 20w REG 8,2 78111247 0 130956 /tmp/FlashXXfU7zHE (deleted)

    Step 2: since the file is unlinked we can't open it by name, but with the PID and FD returned by lsof we can get it from the /proc filesystem:

    $ cp /proc/17666/fd/20 whatever.mp4

    Step 3: profit!

  4. Frank says:

    If any Windows user is looking for a solution: StreamTransport ( seems to work fine. The downloaded video has the filename "404 Not Found.mp4" but it works fine.

  5. Roger says:

    #! /bin/bash


    [ x$clip == x ] && clip=43940325

    rm -f $clip

    cat > cookie.txt &lt&ltEOF
    html_player=1; clips=$clip

    wget --load-cookie=cookie.txt --save-cookie=xxx$clip
    signature=$(cat $clip | tr "," "\n" | grep \"signature | cut -d\" -f4)
    timestamp=$(cat $clip | tr "," "\n" | grep \"timestamp | cut -d: -f2)

    echo $signature $timestamp

    wget --load-cookie=xxx "$clip&time=$timestamp&sig=$signature&type=htm\

  6. austin says:

    i was going to leave a comment about how the vimeo thing worked but it looks like everyone beat me too it (makes sense it didnt take long to figure out)

  7. dude says:

    Well you can also use firefox with the FlashGot add-on. It grabs most of the flv out there (youtube, vimeo,etc)

  8. antoine says:

    Either way the bastards should be reported to the W3C for flagrant violation of HTTP status code semantics.