Buy Me a Coffee

Buy Me a Coffee!

Friday, October 16, 2026

Does Plex Agree With IMDb? Auditing 919 Movie Runtimes with PowerShell

I have a lot of movies in Plex. I also have a nagging suspicion that some of them are wrong: a rip that got cut short, a theatrical cut when I thought I had the director's cut, a file that is really just a trailer wearing a movie's name. Plex will happily play all of them and never say a word.

So I decided to audit the whole library. The idea is simple: pull every movie's runtime from Plex, look up the runtime IMDb has for the same title, and flag anything that doesn't line up. It should take an afternoon.

It did not take an afternoon. It's a good example of how three small bugs can stack up behind each other, so I'm going to walk through the failures as well as the finished script.

The plan

The pieces are straightforward:

  1. Ask the Plex API for every item in my Movies library.
  2. Pull the IMDb ID out of each item's metadata (Plex stores these as imdb://tt... GUIDs).
  3. Ask the OMDb API for that IMDb ID and read back the runtime.
  4. Subtract, export to CSV, and sort by the biggest differences.

Plex reports duration in milliseconds, so that's a divide by 60,000. OMDb returns a string like 142 min, so that gets a regex. Nothing exotic.

First run: 919 movies, 919 "Missing Match"

The script ran cleanly, with no errors and a nice progress bar. Then I opened the CSV and every single row said Missing Match in the IMDb ID column and N/A for the IMDb runtime.

A script that fails loudly is a gift. A script that finishes happily and gives you nothing is the one that wastes your evening.

Bug 1: Plex doesn't hand out GUIDs by default

The library listing endpoint (/library/sections/{id}/all) leaves out the Guid array unless you ask for it. The fix is one query-string parameter:

$PlexApiUrl = "$PlexUrl/library/sections/$LibraryId/all?includeGuids=1"

Without includeGuids=1, my loop looking for imdb:// never matched anything, so no movie ever got looked up.

Bug 2: the OMDb URL had no query string

This one was sitting right behind the first. My OMDb call looked like this:

$OmdbUrl = "http://omdbapi.com"

No IMDb ID. No API key. The comment above it even said # FIXED: added required parameters, which tells you how much to trust comments. The URL had gotten mangled somewhere along the way, and I never noticed, because bug 1 meant that line never executed.

The corrected version:

$OmdbUrl = "https://www.omdbapi.com/?i=$ImdbId&apikey=$OmdbApiKey"

It also would have failed silently. I had wrapped the call in a try/catch with an empty catch block and a comment about "transient network issues." That's how you get a CSV full of N/A and no clue why. The catch block now logs a warning and records the reason in a Status column.

Bug 3: the response wasn't JSON, as far as PowerShell was concerned

With includeGuids=1 added, I expected victory. Instead I got this:

Found 0 movies. Comparing runtimes...

Plex had clearly answered, because the raw response started with "size":919. But Invoke-RestMethod handed me a plain string instead of an object, so $PlexResponse.MediaContainer.Metadata was empty.

The cause is a nasty little detail. With includeGuids=1, each movie has two keys that differ only by capitalization: guid (a single string like plex://movie/...) and Guid (the array of external IDs). PowerShell's JSON parsing is case-insensitive, so it sees a duplicate key, gives up, and returns the raw text. No exception, just the wrong type.

The fix is to parse the JSON myself into case-sensitive hashtables:

$Raw  = Invoke-WebRequest -Uri $PlexApiUrl -Headers $Headers -Method Get
$Json = if ($Raw.Content -is [byte[]]) { [Text.Encoding]::UTF8.GetString($Raw.Content) } else { $Raw.Content }
$PlexResponse = $Json | ConvertFrom-Json -AsHashtable
$Movies = @($PlexResponse['MediaContainer']['Metadata'] | Where-Object { $_ })

After that, fields come out with $Movie['title'] and $Movie['Guid'] instead of dot notation, and the two keys finally stay separate.

I also added a check that stops the script and prints diagnostics if Plex returns zero movies. That check is what showed me the response was a string, so it paid for itself immediately.

Three small things I'm glad I added

A cache. OMDb's free tier allows 1,000 requests a day. My library has 919 movies. Between debugging runs, I would have burned through the quota before the script even worked. Results now get stored in omdb_cache.json, so re-runs only pay for movies they haven't seen.

A -Limit switch. .\PlexLengthChecker.ps1 -Limit 10 processes ten movies. I used it for every test run.

The token in a header. I moved the Plex token out of the URL and into an X-Plex-Token header, so it doesn't end up in logs or in a screenshot for this post.

Results

  • 905 of 919 movies compared, 85 flagged as differing by more than 3 minutes
  • 14 movies with no IMDb ID in Plex at all
  • Most of the flagged were either extended versions, but I will check them out anyway.  I will be heading to the basement this weekend to pull my physical media and ripping them again.



Even before the comparison, the Plex side of the data had some things that caught my eye:

  • Clue (1985) shows up four times: one at about 97 minutes, and three at roughly 7 to 9 minutes each. That is where I added the film's alternate endings as separate library items.
  • A Bug's Life is listed at about 21 minutes, and Birdman at about 33. Those look like partial rips, which is exactly what I built this for.
  • Several titles appear twice with different runtimes, such as American Gangster, Stripes and Straight Outta Compton. That's usually two cuts of the same film, and it matters here because IMDb lists only one runtime per title. Those will show up as "mismatches" that aren't actually errors, so the flag column is a list of things to look at and not a list of confirmed problems.

What I'd tell myself at the start

  1. Test on ten items first. All three bugs would have been obvious in a ten-item run. A full run just made the failure bigger.
  2. Never leave a catch block empty. Mine hid bug 2 completely.
  3. Check the type of what came back. A string where you expected an object is the whole story of bug 3, and a one-line GetType() would have shown it sooner.
  4. Don't trust your own "FIXED" comments. Run the code.

The script

The full script is here: https://gist.github.com/lsmithmier/20b75e25cbed3971775c0112f7c16c17. You'll need a Plex token, your movie library's section ID, and a free OMDb API key. It requires PowerShell 7, because -AsHashtable isn't available in Windows PowerShell 5.1.

If you try it on your own library, I'd like to hear what it finds. Anyone who has ever ripped their own discs probably has at least one Clue hiding in there.