DEV Community

Peiguan
Peiguan

Posted on

Who Should Play Lead in Our Tiny Desk AI Video? Seating Five Friends by Preference in 60 Lines of Python

Checked November 5, 2026 (12:00 CST, UTC+8) against the live pages linked below. I build tinydeskoutstanding.video, an independent studio that turns one to five photos into a 15-second vertical parody of the "Outstanding" Tiny Desk meme. The script was written for this post and tested with made-up names.

A question that comes up as soon as a group decides to make one of these clips: "Who gets to be the lead?" Then, a few messages later: "I'm not doing the sax."

The studio has five fixed spots: back left, back right, lead, keys and sax. Each filled spot takes one photo of one adult who agreed, or one pet, and "empty spots keep the original performers" (FAQ). The price doesn't change with the number of faces, so the money isn't the argument. The seating is.

This is a small assignment problem, and it's a nice excuse to show that for five seats you don't need a solver.

The problem, stated like a programmer

  • Up to five people (a dog counts as a person here).
  • Each person scores the spots they want, 0 to 5. Unscored spots count as 0.
  • Anyone can veto a spot: never seat them there.
  • Pick the seating with the highest total score. If two seatings tie, prefer the one where the least happy person is happier. If they still tie, break it the same way every time, so re-running in the group chat doesn't produce a different answer and a new argument.

The textbook tool is the Hungarian algorithm (scipy.optimize.linear_sum_assignment). But with five people and five spots there are at most 5! = 120 seatings. Python checks all of them in well under a millisecond. Brute force here is not lazy, it's obviously correct, and you can explain it to the friend who lost the lead.

The script

#!/usr/bin/env python3
"""Seat one to five friends in the five Tiny Desk spots by preference.

Input: a JSON file like
  {"people": {"Ana": {"lead": 5, "sax": 3, "keys": 1},
              "Ben": {"lead": 4, "back_left": 2, "keys": "veto"}}}
Scores are 0-5 (higher = wants it more). A missing spot counts as 0.
"veto" means never put this person there. Pets are people here too.

With at most five people and five spots there are at most 5! = 120 ways
to seat everyone, so we simply try them all: exact, and no SciPy needed.
"""
import itertools, json, sys

SPOTS = ["back_left", "back_right", "lead", "keys", "sax"]
VETO = "veto"

def score_of(prefs, person, spot):
    v = prefs[person].get(spot, 0)
    if v == VETO:
        return None
    if not isinstance(v, (int, float)) or not 0 <= v <= 5:
        raise ValueError(f"{person}/{spot}: score must be 0-5 or 'veto', got {v!r}")
    return v

def assign(prefs):
    people = sorted(prefs)  # sorted -> same input, same answer
    if not 1 <= len(people) <= 5:
        raise ValueError("the band has 1 to 5 spots to fill; got %d people" % len(people))
    for p in people:
        bad = set(prefs[p]) - set(SPOTS)
        if bad:
            raise ValueError(f"{p}: unknown spot(s) {sorted(bad)}; use {SPOTS}")
    best = None
    for seats in itertools.permutations(SPOTS, len(people)):
        scores = [score_of(prefs, p, s) for p, s in zip(people, seats)]
        if None in scores:
            continue
        total = sum(scores)
        worst = min(scores)
        # maximise total happiness, then protect the least happy person,
        # then fall back to a fixed order so ties are reproducible
        key = (total, worst, tuple(-SPOTS.index(s) for s in seats))
        if best is None or key > best[0]:
            best = (key, dict(zip(people, seats)), dict(zip(people, scores)))
    if best is None:
        raise ValueError("no seating respects every veto")
    (total, worst, _), seating, scores = best
    empty = [s for s in SPOTS if s not in seating.values()]
    return {"seating": seating, "scores": scores, "total": total,
            "least_happy_score": worst, "empty_spots_keep_original": empty}

def main(path):
    with open(path, encoding="utf-8") as f:
        prefs = json.load(f)["people"]
    out = assign(prefs)
    for person, spot in sorted(out["seating"].items(), key=lambda kv: SPOTS.index(kv[1])):
        print(f"{spot:<11} {person}  (score {out['scores'][person]})")
    for spot in out["empty_spots_keep_original"]:
        print(f"{spot:<11} (empty - original performer stays)")
    print(f"total {out['total']}, least happy {out['least_happy_score']}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        sys.exit("usage: assign_spots.py prefs.json")
    main(sys.argv[1])
Enter fullscreen mode Exit fullscreen mode

Three choices worth pointing out:

  1. sorted(prefs) makes the result independent of the order people replied in. Dicts keep insertion order, so without this "Maya answered first" could quietly change the outcome of a tie.
  2. The tie-break key is (total, worst, fixed order). Maximising the total alone can leave one person with a 0 when an equally good seating gives everyone at least a 2. The second element fixes that.
  3. Vetoes are hard constraints, not big negative numbers. A -1000 penalty works until someone types "lead": 9999 in the JSON; skipping vetoed seatings can't be outvoted.

Running it

prefs.json for a group of three friends and a dog:

{"people": {
  "Maya":   {"lead": 5, "keys": 3, "sax": 1},
  "Jordan": {"lead": 5, "sax": 4, "back_left": "veto"},
  "Priya":  {"keys": 5, "lead": 2},
  "Biscuit (dog)": {"back_right": 3, "back_left": 3, "lead": 1}
}}
Enter fullscreen mode Exit fullscreen mode
$ python3 assign_spots.py prefs.json
back_left   Biscuit (dog)  (score 3)
lead        Maya  (score 5)
keys        Priya  (score 5)
sax         Jordan  (score 4)
back_right  (empty - original performer stays)
total 17, least happy 3
Enter fullscreen mode Exit fullscreen mode

Maya and Jordan both gave lead a 5. Jordan also gave the sax a 4 and Maya gave it a 1, so Maya on lead and Jordan on sax beats the reverse (5 + 4 against 5 + 1 for those two seats). The dog scored both back spots 3, so the fixed-order tie-break puts it in back left (first in the spot list), and back right stays empty, which keeps the original performer there.

Testing it

The tests check the obvious cases, the veto, the six-person error, input-order independence, and then compare the total against an independent brute force on 300 random groups:

import itertools, random, unittest
from assign_spots import assign, SPOTS

class T(unittest.TestCase):
    def test_obvious(self):
        out = assign({"Ana": {"lead": 5}, "Ben": {"sax": 5}})
        self.assertEqual(out["seating"], {"Ana": "lead", "Ben": "sax"})
        self.assertEqual(out["empty_spots_keep_original"], ["back_left", "back_right", "keys"])

    def test_conflict_goes_to_bigger_total(self):
        # both want lead; Ben barely cares about anything else, Ana loves keys too
        out = assign({"Ana": {"lead": 5, "keys": 4}, "Ben": {"lead": 5, "keys": 0}})
        self.assertEqual(out["seating"], {"Ana": "keys", "Ben": "lead"})

    def test_veto_respected(self):
        out = assign({"Ana": {"lead": 5}, "Ben": {"lead": 5, "sax": "veto"},
                      "Cy": {"lead": 5, "sax": 5}})
        self.assertNotEqual(out["seating"]["Ben"], "sax")

    def test_impossible_veto(self):
        with self.assertRaises(ValueError):
            assign({"Ana": {s: "veto" for s in SPOTS}})

    def test_six_people_rejected(self):
        with self.assertRaises(ValueError):
            assign({str(i): {} for i in range(6)})

    def test_bad_spot_name(self):
        with self.assertRaises(ValueError):
            assign({"Ana": {"drums": 5}})

    def test_deterministic_regardless_of_input_order(self):
        a = {"Ana": {}, "Ben": {}, "Cy": {}}
        b = {"Cy": {}, "Ben": {}, "Ana": {}}
        self.assertEqual(assign(a), assign(b))

    def test_matches_independent_brute_force(self):
        rng = random.Random(7)
        for _ in range(300):
            n = rng.randint(1, 5)
            prefs = {f"p{i}": {s: rng.randint(0, 5) for s in SPOTS if rng.random() < .7}
                     for i in range(n)}
            best = max(sum(prefs[p].get(s, 0) for p, s in zip(sorted(prefs), seats))
                       for seats in itertools.permutations(SPOTS, n))
            self.assertEqual(assign(prefs)["total"], best)

if __name__ == "__main__":
    unittest.main(verbosity=1)
Enter fullscreen mode Exit fullscreen mode
$ python3 test_assign_spots.py
........
Ran 8 tests in 0.043s

OK
Enter fullscreen mode Exit fullscreen mode

The randomised test is the useful one. It caught nothing this time, but it's the test that would catch a broken tie-break key that accidentally prefers a lower total.

When would you need the Hungarian algorithm?

If the band had 12 seats, 12! is 479 million seatings and brute force stops being instant. This meme's scene has five, and that's fixed by the scene itself: the band shot, the laughing close-up of the lead, and the sax lift at the end (how the parody runs). Pick the simplest algorithm the input size allows.

Things the script can't decide for you

  • Consent. The scores mean nothing if someone didn't agree to be in it. The site's rule is yourself, an adult who said yes, or a pet. Never a child, and not a coach or celebrity from an image search (acceptable use). Take anyone who hasn't replied out of the JSON instead of scoring them.
  • Whose photo can carry the close-up. The lead is small in the band shot and large in the laughing close-up, so the lead needs the clearest front-facing photo (photo guide). If the winner of the lead only has a sunglasses selfie, maybe re-run with their lead score lowered.
  • Pets. A pet can take a spot. Give the pet a 0 for lead if the owner's photo of it is a distant one.

What the clip costs once you've seated everyone

As of today, one 15-second clip is $9.98 at 480p, $12.98 at 720p, $24.98 at 1080p or $39.98 at 4k, the same for one face or five (pricing). There's no free clip and no subscription. So the seating chart is the only thing that changes with group size, which is exactly why it's worth ten minutes of Python instead of fifty group-chat messages.

Top comments (0)