Saturday, 29 August 2026

Becoming Led Zeppelin (5 Stars)


I wrote about "Becoming Led Zeppelin" when I saw it in the cinema last year. I don't have much more to say about the film itself. But there was one thing I didn't put into my original review: while I was sitting in the cinema watching it, I realised that this was the best documentary I'd ever seen.

That's not because of the subject matter. Obviously, I'm interested in Led Zeppelin, but it's the way the story is told that makes this documentary so good.

Most documentaries are watched by people who are already interested in the subject, so the people watching them will already be familiar with the details being presented. "Becoming Led Zeppelin" is different. It's genuinely informative. I'm a Led Zeppelin fan, yet it still told me things I didn't know.

I knew that Jimmy Page had played with the Yardbirds, for example, but I didn't know that Page, Robert Plant, John Paul Jones and John Bonham had gone on tour together in 1968 as the Yardbirds. They could have recorded their first album together as the sixth Yardbirds album, but they wanted to make a new start.

I also found the story of their early popularity fascinating. They were playing to sold-out concerts in America, then flying back to England where hardly anybody knew them. It took at least a year for their popularity to pick up in England.

The documentary ends with Led Zeppelin playing at London's Royal Albert Hall on 9th January 1970. This was when they'd truly made it big. This was when they became Led Zeppelin.

Is this really the best documentary ever made?

That's what I thought last year, and it's what I still think today.


One last point. On Wikipedia it's written "Led Zeppelin evolved from a previous band, the Yardbirds, and were originally named the New Yardbirds".

This is incorrect. They were never officially named the New Yardbirds. They were the Yardbirds, as proved by the contract shown above. It can also be verified by the posters for the various stages of their tour. "New Yardbirds" is an unofficial nickname that wasn't given to them until after they officially called themselves Led Zeppelin.

Don't believe everything you read in Wikipedia.

Order from Amazon.com
Order from Amazon.co.uk
Order from Amazon.de

Friday, 28 August 2026

13 Assassins (4½ Stars)


This was the second time I watched Takashi Miike's "13 Assassins". I didn't particularly enjoy it the first time. I found the first hour boring, but this time I paid much closer attention to what was being discussed and, as a result, I got it. That made a considerable difference.

The problem is that the film spends a lot of time introducing the first twelve assassins without really introducing them as characters. We get to know Shinzaemon, his nephew and Koyata, the hunter who later joins them, but most of the others remain almost completely interchangeable. They're named, but we don't learn enough about them to become emotionally attached to them. When some of them die during the battle, I didn't feel anything.

Koyata is the exception. I liked him a lot. He's such a contrast to the other samurai, and his unusual background makes him one of the most memorable characters in the film.

But then we get to the battle.

And what a battle it is!


Once the fighting starts, "13 Assassins" becomes an entirely different film. The action is spectacular, brutal and remarkably inventive, but it's also enormous fun. Despite the battle going on for what seems like an eternity, it absolutely flew by for me. I was enjoying it so much that I wouldn't have minded if it had gone on even longer.

There's also something strange about Naritsugu's attitude towards the battle. Having spent the film as the man everyone wants to kill, he seems to relish the final confrontation, even though he knows he'll die. When Shinzaemon finally kills him, Naritsugu thanks him for it. It reminds me of Friedrich Dürrenmatt's "Romulus der Große", in which the Roman emperor is waiting for the approaching German army because he wants his corrupt empire to come to an end, only to discover that the man who arrives to conquer him doesn't want to conquer anything either. The situations are different, but there's a similar irony in both men finally encountering the destruction they've been expecting.

That's what makes "13 Assassins" such an interesting film. The first hour still feels too long, but once the battle begins Miike more than makes up for the slow build-up. It's an astonishing piece of filmmaking, and by the time it was over I was left wishing there had been even more of it.

Miike has made some genuine masterpieces, including "First Love" and "Lesson of Evil", but "13 Assassins" doesn't quite reach that level. However, the second viewing certainly improved my opinion of it.

Order from Amazon.com
Order from Amazon.co.uk
Order from Amazon.de

Thursday, 27 August 2026

Virgin Psychics (5 Stars)


"Virgin Psychics" is a film I can watch again and again. It's in my top 100 films list, and rightfully so. Admittedly, it's close to the bottom of my list, but there's a lot of hard competition. Being anywhere in the list is a high distinction.


One of the reasons I like this film so much is Ami Tomite. Unlike her serious role in "Antiporno", "Virgin Psychics" is a film that makes no claims to be anything other than a puerile sex fantasy. It's not pornographic in the usual sense, it's more the type of film that would make teenage boys snicker. The film rolls from one upskirt shot to the next.


Ami Tomite is wonderful as Akiko, a very playful kind of villain. She captures women and makes clones of them that she can control, then sends the clones into town to make everyone think about sex. But really, is that so bad? She's hardly your conventional world-conquering supervillain.

And Ami is certainly easy on the eye. The film gives us plenty of opportunities to admire her, including a remarkable number of shots looking up her skirt. She's beautiful, mischievous and clearly having fun with the role.


Isn't she beautiful? And before you ask, Akiko is a virgin.

Wednesday, 26 August 2026

Antiporno (5 Stars)


What an absolutely brilliant film! Sion Sono has made something completely crazy, unpredictable and wonderfully disturbing, but for me the real reason it works so well is Ami Tomite. This is probably her best performance, and she has to do an extraordinary amount with the character of Kyoko.

At first, Kyoko appears to be completely in control. She's a successful young artist, surrounded by colour and luxury, and she treats everyone around her as if she's the most important person in the world. Tomite throws herself into this performance with enormous energy. She's loud, theatrical, arrogant and deliberately over-the-top. I thought she was excellent from the opening scenes.


Then the film turns everything upside down. The Kyoko we've been watching is revealed to be a character in a film being made, and the seemingly confident woman is actually an inexperienced actress who's being treated terribly by the other actresses. The person who appeared to have all the power is suddenly the person who has none.

This is where Tomite is really amazing. She's not simply playing two different versions of Kyoko; she's playing an actress who's playing a character. The exaggerated performance we saw earlier isn't Tomite overacting. She's acting as Kyoko acting in a film. Once we understand that, those wild gestures and outbursts suddenly make perfect sense.

There's an extraordinary extra layer to this, too. In the interview on the disc, Tomite explains that Sion Sono deliberately didn't want her to know what was happening in the film. That's the perfect way to make "Antiporno". Kyoko is constantly trapped in situations she doesn't understand, while Tomite herself was apparently being kept uncertain about where Sono was taking the story. That uncertainty becomes part of the performance.

And Kyoko is trapped everywhere. She's trapped by her family, by her memories, by the film she's making and by the various identities she's forced to adopt. Even when she appears to be completely free, she's performing. The extravagant apartment that initially looks like a playground increasingly feels like a cage.


Tomite captures all these different sides of Kyoko brilliantly. She's confident, aggressive and hysterically funny, but underneath that there's a frightened and vulnerable young woman. Then Sono takes us back to Kyoko as a schoolgirl, and Tomite has to find another character again. She makes all these changes convincingly without ever losing the sense that they're different pieces of the same person.

The scene involving Kyoko's supposed first sexual experience is another example of the film playing with performance and reality. She doesn't actually have sex with the man; she asks him to grind against her so that it will look as though she's having sex on camera. Even here, she's performing an identity rather than simply experiencing something. The film later turns the memory into something even more surreal, with the sexual imagery giving way to an empty forest. It's another reminder that we're never quite sure whether we're watching reality, memory, fantasy or a performance.

That's what makes Tomite's job so difficult. She isn't merely playing Kyoko; she's playing Kyoko playing Kyoko, while the film continually changes the rules around her. Yet she makes it all work.


There are plenty of things in "Antiporno" that I didn't completely understand, such as the film crew appearing in Kyoko's home, but I didn't mind. The film isn't interested in giving us a straightforward explanation of everything that happens. It's interested in showing what it feels like to be trapped inside a world where the boundaries between reality and performance have completely broken down.

And Ami Tomite is at the centre of it all. She can be outrageous one moment, vulnerable the next and completely convincing in both roles. It's a remarkable performance in a remarkable film.

Antiporno is brilliant. And Ami Tomite is absolutely amazing.

Order from Amazon.co.uk
Order from Amazon.de

Tag (5 Stars)


A brilliant film about the objectification of women.




If you buy the Eureka film, please note the following subtitle errors.

Missing subtitles:

55:29  Great posture and steady head.
55:33  She's probably in her best condition.
55:37  They're right behind the leaders. It'll be close.

Wrongly placed subtitle:

At 65:19 there's a subtitle "The Male World". This is the translation of a sign that appears on screen at 67:24.

Order from Amazon.co.uk
Order from Amazon.de

Tuesday, 25 August 2026

Belle of the Nineties (3 Stars)


Mae West plays Ruby Carter, a singer in St Louis in 1892. After becoming involved with prizefighter Tiger Kid, she's persuaded to leave town and moves to New Orleans, where she becomes the star attraction at the Sensation House. Naturally, it isn't long before Tiger Kid follows her.

Mae West is the reason to watch the film. She's completely in command whenever she's on screen, delivering her notorious one-liners with the confidence and suggestiveness that made her unique. Even though the lines aren't particularly outrageous by today's standards, the way she says them makes you understand why she was considered so scandalous in the 1930s.


I have mixed feelings about "Belle of the Nineties". It's a good film. I can't point to anything that's badly done, and Mae West is as entertaining as ever. The problem is simply that the story doesn't appeal to me. Boxing plays a large part in the film, particularly as it approaches its climax, and boxing isn't something that interests me.

Sometimes there's a difference between recognising that a film is good and actually liking it. "Belle of the Nineties" is a perfect example. I can appreciate its quality, and I certainly enjoyed Mae West, but it's not a film that makes me want to watch it again.

Sunday, 23 August 2026

Beta Test: Alphabetical List of Posts




Eleven years ago I released an app to list blog posts in alphabetical order. Since then it's been working fine, apart from one minor bug that most of you probably never noticed. On Friday I asked ChatGPT to write me a new app. The result is amazing. The old app needed 36 seconds to list my 6348 posts. The new app only needs 6 seconds.

This is the new code. Copy it into a blogspot page. No configuration is necessary. If you use it, please leave a comment telling me your blog address.

These are early days. Consider it a beta test. This is a link to the old code, in case you want to go back. If the test goes well, I'll delete the old code from my blog on 1st September.



<!-- Blogger Alphabetical Post Index - Version 2.14
     Paste this into the HTML view of a Blogger Page.

     Version 2.14:
     - Based on the working Version 2.11 retrieval method.
     - Shows publication date before each post title.
     - Sorts alphabetically by title.
     - If titles are identical, older posts come first.
     - Uses Blogger's lightweight JSONP summary feed.
     - Requests 100 posts at a time using fixed start-index positions.
     - Loads 5 batches in parallel for speed.
     - No API key, blog ID or blog URL is required.
-->

<div id="alphabetical-post-index">
  <p id="api-status">Loading posts... 0 found</p>
  <ul id="api-post-list"></ul>
</div>

<style>
#alphabetical-post-index {
  width: 100%;
}

#api-status {
  margin: 0 0 1em 0;
}

#api-post-list {
  margin: 0;
  padding-left: 1.5em;
}

#api-post-list li {
  margin: 0.2em 0;
}

#api-post-list .post-date {
  display: inline-block;
  margin-right: 0.8em;
  white-space: nowrap;
  color: #777;
}

#api-post-list a {
  text-decoration: none;
}

#api-post-list a:hover {
  text-decoration: underline;
}
</style>

<script>
(function () {
  "use strict";

  const BATCH_SIZE = 100;
  const PARALLEL_REQUESTS = 5;
  const RETRIES = 2;

  const status = document.getElementById("api-status");
  const list = document.getElementById("api-post-list");

  const postsByUrl = new Map();

  let callbackCounter = 0;

  function blogRoot() {
    return window.location.protocol + "//" + window.location.host + "/";
  }

  function updateProgress() {
    status.textContent =
      "Loading posts... " +
      postsByUrl.size.toLocaleString("en-GB") +
      " found";
  }

  function canonicaliseUrl(url) {
    try {
      const u = new URL(url, blogRoot());
      u.hash = "";

      if (u.host === window.location.host) {
        u.protocol = window.location.protocol;
      }

      return u.href;
    } catch (error) {
      return url;
    }
  }

  function formatDate(dateString) {
    if (!dateString) {
      return "";
    }

    const date = new Date(dateString);

    if (Number.isNaN(date.getTime())) {
      return "";
    }

    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");

    return year + "-" + month + "-" + day;
  }

  function jsonp(url) {
    return new Promise(function (resolve, reject) {
      callbackCounter++;

      const callbackName =
        "__alphabeticalIndexCallback" + callbackCounter;

      const script = document.createElement("script");
      let finished = false;

      const timer = setTimeout(function () {
        if (finished) {
          return;
        }

        finished = true;
        cleanup();
        reject(new Error("Blogger feed request timed out."));
      }, 15000);

      function cleanup() {
        clearTimeout(timer);

        try {
          delete window[callbackName];
        } catch (error) {
          window[callbackName] = undefined;
        }

        if (script.parentNode) {
          script.parentNode.removeChild(script);
        }
      }

      window[callbackName] = function (data) {
        if (finished) {
          return;
        }

        finished = true;
        cleanup();
        resolve(data);
      };

      script.onerror = function () {
        if (finished) {
          return;
        }

        finished = true;
        cleanup();
        reject(new Error("Blogger feed request failed."));
      };

      script.src =
        url +
        (url.indexOf("?") >= 0 ? "&" : "?") +
        "callback=" +
        encodeURIComponent(callbackName);

      script.async = true;
      document.body.appendChild(script);
    });
  }

  async function jsonpWithRetry(url) {
    let lastError = null;

    for (let attempt = 0; attempt <= RETRIES; attempt++) {
      try {
        return await jsonp(url);
      } catch (error) {
        lastError = error;

        if (attempt < RETRIES) {
          await new Promise(function (resolve) {
            setTimeout(resolve, 300 * (attempt + 1));
          });
        }
      }
    }

    throw lastError;
  }

  function feedUrl(startIndex) {
    return (
      blogRoot() +
      "feeds/posts/summary" +
      "?alt=json" +
      "&start-index=" + startIndex +
      "&max-results=" + BATCH_SIZE
    );
  }

  function getAlternateLink(entry) {
    if (!entry.link) {
      return "";
    }

    for (const link of entry.link) {
      if (link.rel === "alternate") {
        return canonicaliseUrl(link.href);
      }
    }

    return "";
  }

  function addEntries(data) {
    if (!data || !data.feed) {
      throw new Error("Blogger returned an invalid feed.");
    }

    const entries = data.feed.entry || [];

    for (const entry of entries) {
      const title =
        entry.title &&
        typeof entry.title.$t === "string"
          ? entry.title.$t.trim()
          : "";

      const url = getAlternateLink(entry);

      const published =
        entry.published &&
        entry.published.$t
          ? entry.published.$t
          : "";

      if (!title || !url) {
        continue;
      }

      if (!postsByUrl.has(url)) {
        postsByUrl.set(url, {
          title: title,
          url: url,
          published: published
        });
      }
    }

    updateProgress();

    return entries.length;
  }

  function getTotalResults(data) {
    try {
      return Number(
        data.feed["openSearch$totalResults"].$t
      ) || 0;
    } catch (error) {
      return 0;
    }
  }

  async function loadBatch(startIndex) {
    const data =
      await jsonpWithRetry(feedUrl(startIndex));

    addEntries(data);

    return data;
  }

  async function worker(queue, failures) {
    while (queue.length) {
      const startIndex = queue.shift();

      try {
        await loadBatch(startIndex);
      } catch (error) {
        console.warn(
          "Feed batch failed at start-index",
          startIndex,
          error
        );

        failures.push(startIndex);
      }
    }
  }

  function comparePosts(a, b) {
    const result = a.title.localeCompare(
      b.title,
      "en",
      {
        sensitivity: "base",
        numeric: true
      }
    );

    if (result !== 0) {
      return result;
    }

    return new Date(a.published) - new Date(b.published);
  }

  function renderPosts() {
    const posts =
      Array.from(postsByUrl.values());

    posts.sort(comparePosts);

    const fragment =
      document.createDocumentFragment();

    for (const post of posts) {
      const li =
        document.createElement("li");

      const date =
        document.createElement("span");

      date.className = "post-date";
      date.textContent =
        formatDate(post.published);

      const link =
        document.createElement("a");

      link.href = post.url;
      link.textContent = post.title;

      li.appendChild(date);
      li.appendChild(link);
      fragment.appendChild(li);
    }

    list.replaceChildren(fragment);

    status.textContent =
      posts.length.toLocaleString("en-GB") +
      " published posts";
  }

  async function start() {
    const firstData =
      await loadBatch(1);

    const totalResults =
      getTotalResults(firstData);

    if (!totalResults) {
      throw new Error(
        "Blogger did not report the total number of posts."
      );
    }

    const queue = [];

    for (
      let startIndex = 1 + BATCH_SIZE;
      startIndex <= totalResults;
      startIndex += BATCH_SIZE
    ) {
      queue.push(startIndex);
    }

    const failures = [];
    const workers = [];

    const workerCount = Math.min(
      PARALLEL_REQUESTS,
      queue.length
    );

    for (let i = 0; i < workerCount; i++) {
      workers.push(
        worker(queue, failures)
      );
    }

    await Promise.all(workers);

    if (failures.length > 0) {
      const retryList = failures.slice();
      failures.length = 0;

      for (const startIndex of retryList) {
        try {
          await loadBatch(startIndex);
        } catch (error) {
          failures.push(startIndex);
        }
      }
    }

    renderPosts();

    if (failures.length > 0) {
      status.textContent =
        postsByUrl.size.toLocaleString("en-GB") +
        " posts found; " +
        failures.length.toLocaleString("en-GB") +
        " feed batch" +
        (failures.length === 1 ? "" : "es") +
        " couldn't be read.";
    }
  }

  start().catch(function (error) {
    console.error(
      "Alphabetical Post Index:",
      error
    );

    status.textContent =
      "Loading stopped after " +
      postsByUrl.size.toLocaleString("en-GB") +
      " posts.";

    status.style.fontWeight = "bold";
  });
})();
</script>

Saturday, 22 August 2026

Terminator 3 (4½ Stars)


"Terminator 3: Rise of the Machines" has the unenviable task of following two of the greatest science fiction films ever made. It isn't as good as either of them, but that's hardly a criticism. Taken on its own terms, "Terminator 3" is an excellent action film and a worthy continuation of the story.

Ten years have passed since John Connor and his mother prevented Judgment Day. Sarah is now dead and John has disappeared off the grid. He has no permanent home, no telephone and no credit cards. Nick Stahl looks gaunt and weary, very different from Edward Furlong in "Terminator 2", but that fits the character perfectly. John has spent his life preparing to become the leader of humanity, only to be told that the future he was preparing for no longer exists. He doesn't know what to do with himself, but he can't quite believe that the danger has gone away.

Then another Terminator arrives.

Kristanna Loken is superb as the T-X, a new model sent back to kill John and other future Resistance leaders. It would have been a mistake to imitate Robert Patrick's T-1000, and Loken creates something quite different. She's cold and mechanical, but she's also been programmed with distinctly female characteristics which she uses to her advantage. One of my favourite moments comes when she tastes John Connor's blood. Her expression of pleasure is almost sexual. She's found the man she's looking for, and she seems to savour the moment.


Arnold Schwarzenegger is back as another T-800, this time sent to protect John and Kate Brewster. There's more humour than in the previous films, but humour has always been part of the series, particularly in "Terminator 2", and the increase doesn't bother me. Arnold is still completely convincing when the action begins, and "Terminator 3" has some magnificent action sequences. The chase involving the enormous crane is particularly spectacular, relying on a level of physical destruction that still looks impressive today.

Claire Danes' Kate doesn't look like a future Resistance leader, but why should she? At this point she's a veterinarian living an ordinary life. She has years ahead of her to become the woman she's destined to be. John and Kate are both being pushed towards futures that neither of them is ready to accept.

And then comes the ending.

For most of the film we're led to believe that John and Kate are racing against time to stop Skynet and prevent Judgment Day once again. They reach Crystal Peak expecting to find Skynet's central core. It isn't there. There is no central core. Skynet has spread through computer networks and there's nothing left for them to destroy.

They haven't been sent to Crystal Peak to stop Judgment Day. They've been sent there to survive it.

When I first saw the film, this revelation shocked me. I didn't see it coming at all. After two films about preventing the nuclear apocalypse, "Terminator 3" has the courage to let it happen. Judgment Day wasn't prevented in "Terminator 2"; it was merely postponed.

Then, as nuclear missiles begin flying and the old world disappears, radio messages start arriving at Crystal Peak. People are confused and frightened, asking what's happening and looking for someone to tell them what to do. John picks up the radio and answers.

This is what everything has been leading towards. John has spent years running away from the destiny that his mother prepared him for, but when the moment finally arrives he doesn't need anyone to tell him what to do. He takes command. John Connor finally becomes John Connor.


"Terminator 3: Rise of the Machines" isn't "The Terminator" and it isn't "Terminator 2". It doesn't need to be. It's exciting, spectacular and far more intelligent than its reputation suggests. Most importantly, it takes the story somewhere that genuinely matters. The future hasn't been prevented. The war is about to begin.

Success Rate:  + 0.3

Order from Amazon.com
Order from Amazon.co.uk
Order from Amazon.de