diff --git a/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt b/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt index c3737d0..e38df02 100644 --- a/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt +++ b/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt @@ -850,7 +850,8 @@ internal fun extendWeighted(pool: List, cursor: ScheduleCursor, from } val open = unclashed.ifEmpty { available }.let { shows -> if (stories) shows.filter(::storyFits).ifEmpty { shows } else shows } // Inside a shorts slot only the shorts that fit it take part; none fits, and the slot closes where it is. - val inSlot = slot?.let { s -> unclashed.filter { it.id != last && s.takes(start, following(it).durationMs) } }.orEmpty() + fun slotShows(s: ShortsSlot) = unclashed.filter { it.id != last && s.takes(start, following(it).durationMs) } + val inSlot = slot?.let { s -> slotShows(s).ifEmpty { if (s.stretchIfShort(start)) slotShows(s) else emptyList() } }.orEmpty() if (inSlot.isEmpty()) slot = null val clear = if (continuing == null && inSlot.isNotEmpty()) inSlot else open val rules = listOf<(PoolSeries) -> Boolean>( @@ -861,7 +862,8 @@ internal fun extendWeighted(pool: List, cursor: ScheduleCursor, from val low = eligible.minOf { tally[it.id]!! } clear.filter { it.id != last && it !in eligible }.forEach { tally[it.id] = maxOf(tally[it.id]!!, low) } val byTally = compareBy { tally[it.id]!! }.thenBy { pool.indexOf(it) } - val preferred = eligible.minWith(byTally) + // In a shorts slot, a show it hasn't had yet goes first when it's close behind in turn. + val preferred = slot?.takeIf { continuing == null && inSlot.isNotEmpty() }?.varied(eligible.sortedWith(byTally)) { it.id } ?: eligible.minWith(byTally) // Before an appointment, the eligible show that leaves it on time, then any clear one, in tally order. val appointment = if (part?.onTime == true) null else nextAppointment(start, zone, partAt = ::partAt) val fitted = if (continuing != null) null else appointment?.let { at -> fun ends(show: PoolSeries) = start + lengthOf(show) @@ -999,7 +1001,8 @@ internal fun extendSchedule(pool: List, cursor: ScheduleCursor, from // A block carries on from its own last show. Starting from the channel's last show would begin each block at the same place every day. val after = if (blockRotation) blockLast[block] ?: last else last // Inside a shorts slot, the next short in turn that fits it; none fits, and the slot closes where it is. - val inSlot = slot?.let { open -> turnOrder(pool, after).firstOrNull { show -> show.id != last && allowed(show) && open.takes(start, following(show).durationMs) && !avoid(show, start, start + following(show).durationMs) } } + fun slotPick(open: ShortsSlot) = open.varied(turnOrder(pool, after).filter { show -> show.id != last && allowed(show) && open.takes(start, following(show).durationMs) && !avoid(show, start, start + following(show).durationMs) }) { it.id } + val inSlot = slot?.let { open -> slotPick(open) ?: if (open.stretchIfShort(start)) slotPick(open) else null } if (inSlot == null) slot = null val picked = inSlot ?: turnOrder(pool, after).firstOrNull { show -> when { diff --git a/android/app/src/main/java/tv/orbit/classic/Shorts.kt b/android/app/src/main/java/tv/orbit/classic/Shorts.kt index 05bdc91..522bbb2 100644 --- a/android/app/src/main/java/tv/orbit/classic/Shorts.kt +++ b/android/app/src/main/java/tv/orbit/classic/Shorts.kt @@ -24,6 +24,8 @@ import java.time.ZoneId internal const val SHORTS_MAX_MS = 8 * 60_000L internal const val SHORTS_SLOT_MIN_MS = 15 * 60_000L internal const val SHORTS_OVERRUN_MS = 2 * 60_000L +/** How far down the turn order a slot looks for a show it hasn't had yet, before taking a show back. */ +internal const val SHORTS_VARIETY_TURNS = 1 /** A channel's shorts slots: what each is called at a given time. */ internal class ShortsRule(val label: (startMs: Long, zone: ZoneId) -> String) @@ -35,8 +37,21 @@ internal val ToonZShorts = ShortsRule { _, _ -> "Cartoon Shorts" } internal fun isShort(durationMs: Long) = durationMs in 1..SHORTS_MAX_MS /** One slot being filled: when it aims to end, and the tag its shorts carry. */ -internal class ShortsSlot(val startMs: Long, val targetMs: Long, val label: String) { +internal class ShortsSlot(val startMs: Long, var targetMs: Long, val label: String) { val tag get() = shortsTag(startMs, label) + /** The shows already in this slot: a show comes back only when no other short fits (Paul, 6 November 2026: variety first). */ + val shows = mutableSetOf() + /** + * Variety without jumping the queue: [first] in turn order stands, unless it's already in the slot and a show that isn't + * is close behind it (within [SHORTS_VARIETY_TURNS] places of [ordered]). + */ + fun varied(ordered: List, id: (T) -> String): T? = ordered.firstOrNull()?.let { first -> + if (id(first) !in shows) first else ordered.take(SHORTS_VARIETY_TURNS + 1).firstOrNull { id(it) !in shows } ?: first } + /** + * A slot still under [SHORTS_SLOT_MIN_MS] that can't fit its next short by its target aims for the half hour after + * instead, rather than closing as a fragment with another slot straight after it. + */ + fun stretchIfShort(atMs: Long): Boolean = (atMs - startMs < SHORTS_SLOT_MIN_MS).also { if (it) targetMs += 30 * 60_000L } /** Whether a short of [durationMs] starting at [atMs] belongs in this slot. */ fun takes(atMs: Long, durationMs: Long) = isShort(durationMs) && atMs < targetMs && atMs + durationMs <= targetMs + SHORTS_OVERRUN_MS } @@ -80,6 +95,7 @@ internal fun shortsBlocks(entries: List): List = bui internal fun shortsTagFor(rule: ShortsRule?, slot: ShortsSlot?, series: PoolSeries, episode: PoolEpisode, inBlock: Boolean, startMs: Long, zone: ZoneId): Pair { if (rule == null || series.isFilm || !inBlock || !isShort(episode.durationMs)) return "" to null val open = slot?.takeIf { it.takes(startMs, episode.durationMs) } ?: openShortsSlot(rule, startMs, zone) + open.shows += series.id return open.tag to open } diff --git a/android/app/src/test/java/tv/orbit/classic/ShortsReplayTest.kt b/android/app/src/test/java/tv/orbit/classic/ShortsReplayTest.kt index d4b1f14..8b594e3 100644 --- a/android/app/src/test/java/tv/orbit/classic/ShortsReplayTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/ShortsReplayTest.kt @@ -89,12 +89,19 @@ class ShortsReplayTest { val loose = after.count { isShort(it.media.durationMs) && it.group.isEmpty() } val lengths = blocks.map { it.endMs - it.startMs }.sorted() val repeats = blocks.sumOf { b -> b.entries.zipWithNext().count { (a, c) -> a.seriesId == c.seriesId } } + assertEquals("${programming.name}: no show twice in a row in a slot", 0, repeats) + val returns = blocks.sumOf { b -> b.entries.size - b.entries.map { it.seriesId }.distinct().size } + val couldVary = blocks.count { b -> b.entries.size > b.entries.map { it.seriesId }.distinct().size } + val shortSlots = blocks.filter { it.endMs - it.startMs < SHORTS_SLOT_MIN_MS } out.append("\n${programming.name} ($number)\n") out.append(" Programmes booked: ${before.size} before, ${after.size} after; shorts among them: $shortsBefore before, $shortsAfter after\n") out.append(" Shorts slots: ${blocks.size}, holding ${blocks.sumOf { it.entries.size }} shorts; $loose short(s) left on their own (a slot of one, or the block changed)\n") if (lengths.isNotEmpty()) out.append(" Slot length: shortest ${mins(lengths.first())} min, median ${mins(lengths[lengths.size / 2])}, longest ${mins(lengths.last())}; " + "${blocks.count { onHalfHour(it.endMs) }} of ${blocks.size} end within 2 min of :00 or :30\n") - out.append(" Shows per slot: average ${"%.1f".format(blocks.map { it.shows.size }.average())}; the same show twice in a row inside a slot: $repeats time(s)\n") + out.append(" Shows per slot: average ${"%.1f".format(blocks.map { it.shows.size }.average())}; the same show twice in a row inside a slot: $repeats time(s); a show back later in the same slot: $returns time(s), in $couldVary slot(s)\n") + out.append(" Slots under 15 minutes: ${shortSlots.size}${if (shortSlots.isEmpty()) "" else ": " + shortSlots.take(6).joinToString { blk -> + val next = after.firstOrNull { it.startMs == blk.endMs } + "${clock(blk.startMs)} ${mins(blk.endMs - blk.startMs)} min, then ${next?.let(::name) ?: "?"} (${next?.let { mins(it.media.durationMs) } ?: "?"} min)" }}\n") out.append(" Programmes starting within 2 min of :00 or :30: ${before.count { onHalfHour(it.startMs) }} of ${before.size} before, ${after.count { onHalfHour(it.startMs) }} of ${after.size} after\n") out.append(" Labels: ${blocks.groupingBy { it.label }.eachCount()}\n") val airtime = { run: List -> run.groupBy(::name).mapValues { (_, v) -> v.sumOf { it.media.durationMs } / 60_000 } } diff --git a/design/shorts/build-shorts.py b/design/shorts/build-shorts.py new file mode 100644 index 0000000..c793c6e --- /dev/null +++ b/design/shorts/build-shorts.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Shorts slots in the guide, design proposal (Paul, 6 November 2026): how a slot of shorts shows in the TV Guide, what OK on +it opens, and Tots Story Time as the evening's identity for single bedtime stories. Every short is still its own programme; +the slot is only how they're shown. Times and titles from the seven-day replay of the box's own plans. For approval only.""" +import ast +from pathlib import Path +HERE = Path(__file__).resolve().parent +SRC = HERE.parent / 'profiles' / 'build-profiles.py' +tree = ast.parse(SRC.read_text()) +keep = [] +for n in tree.body: + if isinstance(n, ast.Expr) and isinstance(n.value, ast.Constant): continue + if isinstance(n, ast.Expr) and isinstance(n.value, ast.Call) and getattr(n.value.func, 'id', '') == 'screen': break + keep.append(n) +ns = {'__file__': str(SRC)} +exec(compile(ast.Module(keep, []), str(SRC), 'exec'), ns) +globals().update({k: v for k, v in ns.items() if k not in ('HERE', '__file__', 'save')}) +def save(name): ns['im'].resize((W, H), Image.LANCZOS).save(HERE / name); print(name) +def D(): return ns['d'] +from PIL import Image +GREEN = (110, 220, 170); LATER = (15, 44, 92); PINK = (255, 122, 198); LUNA = (167, 123, 255) + +def stack(x, y, s=18, col=YELLOW): + """The shorts mark: three short bars, a slot of several programmes.""" + for k in range(3): D().rounded_rectangle((S(x), S(y + k * s * .38), S(x + s * (1 - k * .18)), S(y + k * s * .38 + s * .24)), S(3), fill=col) + +def moon(x, y, s=16, col=LUNA): + D().ellipse((S(x), S(y), S(x + s), S(y + s)), fill=col); D().ellipse((S(x + s * .35), S(y - s * .1), S(x + s * 1.2), S(y + s * .8)), fill=PANEL) + +def guide_header(times, y=150, x0=420, step=290, day='Thursday'): + box(60, y, 1800, 48, fill=BLUE, r=0); text((80, y + 8), day, semi(28), WHITE) + for i, t in enumerate(times): text((x0 + i * step, y + 8), t, semi(28), WHITE) + +def fit(s, f, w): + """Cut to fit [w] pixels, with an ellipsis, as the guide does.""" + if D().textlength(s, font=f) / SS <= w: return s + while s and D().textlength(s + '…', font=f) / SS > w: s = s[:-1] + return (s.rstrip() + '…') if s else '' + +def cell(x, w, y, title, sub=None, fill=PANEL, focus=False, mark=None, h=100): + box(x, y, w - 6, h, fill=NAVY if focus else fill, r=4, outline=YELLOW if focus else None, width=4) + tx = x + 18 + if mark == 'stack': stack(tx, y + 22); tx += 30 + if mark == 'moon': moon(tx, y + 24); tx += 30 + text((tx, y + 18), fit(title, semi(26), x + w - 24 - tx), semi(26), WHITE) + if sub: text((x + 18, y + 58), fit(sub, reg(21), w - 36), reg(21), MUTED) + +def mins(m, step=290): return m / 30 * step + +# 1. The guide: a ToonZ slot, a Tots slot, Tots' evening stories one by one, and a slot cut short by a block change. +screen() +text((80, 70), 'TV GUIDE', anton(44), WHITE); text((300, 84), 'A slot of shorts is one cell; the shorts in it are still each their own programme.', reg(26), MUTED) +guide_header(['13:30', '14:00', '14:30', '15:00', '15:30']) +x0 = 410; t0 = 13 * 60 + 30 +def X(hm): return x0 + mins(hm - t0) +rows = [('605', 'TOONZ', [(13*60+30, 13*60+51, 'The Flintstones', 'S3 E12', None), (13*60+51, 14*60+31, 'Cartoon Shorts', 'Tom and Jerry, Yogi Bear, Popeye and 3 more · 40 min', 'stack'), + (14*60+31, 14*60+55, 'Looney Tunes', 'The Looney Tunes Show', None), (14*60+55, 15*60+58, 'Wacky Races', 'S1 E4', None)]), + ('614', 'TOTS', [(13*60+30, 13*60+41, 'Fireman Sam', 'S9 E3', None), (13*60+41, 14*60+50, 'Andy\'s Wild Adventures', 'S2 E8', None), + (14*60+50, 15*60+28, 'Tots Little Mix', 'Bluey, Numberblocks, Peppa Pig and 3 more · 38 min', 'stack'), (15*60+28, 16*60, 'Andy\'s Wild Adventures', 'S2 E9', None)])] +for r, (num, name, progs) in enumerate(rows): + y = 210 + r * 120 + box(60, y, 340, 100, fill=PANEL, r=0); text((80, y + 30), num, semi(30), YELLOW); text((170, y + 30), name, anton(34), WHITE) + for a, b, title, sub, mark in progs: + a2, b2 = max(a, t0), min(b, t0 + 150) + if b2 <= a2: continue + cell(X(a2), mins(b2 - a2), y, title, sub, focus=(mark == 'stack' and r == 0), mark=mark) +# Evening: single bedtime stories carry Tots Story Time; a slot cut short by a block change shows its real length. +guide_header(['18:00', '18:30', '19:00', '19:30', '20:00'], y=470) +y = 530; box(60, y, 340, 100, fill=PANEL, r=0); text((80, y + 30), '614', semi(30), YELLOW); text((170, y + 30), 'TOTS', anton(34), WHITE) +t1 = 18 * 60 +def X1(hm): return x0 + mins(hm - t1) +for a, b, title, sub, mark in [(18*60, 18*60+5, 'Story', None, 'moon'), (18*60+5, 18*60+25, 'In The Night Garden', 'S1 E14', None), (18*60+25, 18*60+31, 'Story', None, 'moon'), + (18*60+31, 18*60+50, 'Teletubbies', 'S2 E40', None), (18*60+50, 19*60+2, 'BeddyByes', 'S1 E9', None), (19*60+2, 19*60+7, 'Story', None, 'moon'), + (19*60+7, 19*60+30, 'Clangers', 'S2 E3', None), (19*60+30, 20*60+30, 'In The Night Garden', 'S1 E15 · Tots Dreamtime from 19:00', None)]: + cell(X1(a), mins(b - a), y, title if title != 'Story' else '', sub, mark=mark) +box(420, 650, 1440, 110, fill=(10, 30, 66), r=10) +moon(446, 676, 20) +text((482, 668), 'Tots Story Time: each evening bedtime story is its own short programme, with the moon and the story\'s name when focused,', reg(23), MUTED) +text((482, 700), 'for example "Tots Story Time: Kipper\'s Snowy Day, 5 min". No evening block: In The Night Garden and the wind-down keep their turns.', reg(23), MUTED) +guide_header(['21:30', '22:00', '22:30', '23:00', '23:30'], y=790) +y = 850; box(60, y, 340, 100, fill=PANEL, r=0); text((80, y + 30), '605', semi(30), YELLOW); text((170, y + 30), 'TOONZ', anton(34), WHITE) +t2 = 21 * 60 + 30 +def X2(hm): return x0 + mins(hm - t2) +for a, b, title, sub, mark, fill in [(21*60+30, 21*60+45, 'Danger Mouse', 'S4 E2', None, PANEL), (21*60+45, 22*60, 'Cartoon Shorts', '21:45 to 22:00 · 15 min', 'stack', PANEL), + (22*60, 22*60+22, 'Futurama', 'ToonZ Late', None, (60, 30, 70)), (22*60+22, 23*60, 'King of the Hill', 'ToonZ Late', None, (60, 30, 70))]: + cell(X2(a), mins(b - a), y, title, sub, fill=fill, mark=mark) +text((80, 962), 'A slot that ends early (here at 22:00, when ToonZ Late takes over and has no shorts) is drawn at its real length, with its real times.', reg(24), MUTED) +save('shorts-1-guide.png') + +# 2. OK on the slot: every short, its times, and what's watchable. +screen() +for k in range(6): box(60, 160 + k * 120, 1800, 100, fill=(10, 27, 60), r=0) +overlay = Image.new('RGBA', ns['im'].size, (7, 22, 50, 170)); ns['im'].paste(Image.alpha_composite(ns['im'].convert('RGBA'), overlay).convert('RGB')); ns['d'] = __import__('PIL.ImageDraw', fromlist=['x']).Draw(ns['im']) +box(360, 120, 1200, 820, fill=DEEP, r=16, outline=BLUE, width=3) +stack(400, 160, 26); text((446, 150), 'CARTOON SHORTS', anton(44), WHITE) +text((400, 214), '605 ToonZ · Thursday 13:51 to 14:31 · 40 min · 6 shorts', semi(28), MUTED) +shorts = [('13:51', 'Tom and Jerry', 'Mouse Trouble', '6 min', 'aired'), ('13:57', 'The Yogi Bear Show', 'Pie Pirates', '7 min', 'aired'), + ('14:04', 'Popeye the Sailor', 'Sea Sick Sailors', '6 min', 'now'), ('14:10', 'The Huckleberry Hound Show', 'Lion Hearted Huck', '7 min', 'next'), + ('14:18', 'The Pink Panther', 'Pink Pajamas', '6 min', 'next'), ('14:24', 'Looney Tunes', 'Rabbit Seasoning', '7 min', 'next')] +for i, (t, show, ep, ln, state) in enumerate(shorts): + y = 280 + i * 92; focus = i == 1 + box(400, y, 1120, 80, fill=NAVY if focus else PANEL, r=8, outline=YELLOW if focus else None, width=3) + text((424, y + 22), t, semi(30), YELLOW) + text((540, y + 10), show, semi(30), WHITE); text((540, y + 46), ep, reg(22), MUTED) + text((1180, y + 22), ln, reg(26), MUTED) + if state == 'aired': box(1290, y + 20, 200, 40, fill=GREEN, r=6); text((1390, y + 26), 'CATCH UP', semi(22), NAVY, 'ma') + if state == 'now': box(1290, y + 20, 200, 40, fill=YELLOW, r=6); text((1390, y + 26), 'ON NOW', semi(22), NAVY, 'ma') +text((400, 846), 'Each short is its own programme: Catch Up, where you\'ve got to, and Kids rules work short by short.', reg(24), MUTED) +text((400, 880), 'The list is what\'s actually booked, so the title, times and shows always match what airs.', reg(24), MUTED) +keys([('OK', 'Watch from this short'), ('▲▼', 'Shorts'), ('Back', 'Guide')]) +save('shorts-2-details.png') + +# 3. While it's on: the info banner names the short and where it sits in the slot. +screen() +box(0, 0, W, H, fill=(30, 30, 30), r=0) +for k in range(18): box(80 + k * 100, 120 + (k % 4) * 60, 60, 40, fill=(45, 45, 45), r=6) +box(0, 780, W, 300, fill=(7, 22, 50), r=0) +text((80, 810), '605', semi(36), YELLOW); text((170, 806), 'TOONZ', anton(40), WHITE) +stack(80, 880, 22); text((118, 872), 'Cartoon Shorts · 3 of 6', semi(28), MUTED) +text((80, 920), 'Popeye the Sailor: Sea Sick Sailors', semi(40), WHITE) +text((80, 975), '14:04 to 14:10 · next: The Huckleberry Hound Show 14:10', reg(26), MUTED) +box(1100, 900, 700, 10, fill=(40, 60, 100), r=5); box(1100, 900, 300, 10, fill=YELLOW, r=5) +text((1100, 930), 'The short\'s own progress, not the slot\'s', reg(24), MUTED) +save('shorts-3-banner.png') diff --git a/design/shorts/shorts-1-guide.png b/design/shorts/shorts-1-guide.png new file mode 100644 index 0000000..3ea88c1 Binary files /dev/null and b/design/shorts/shorts-1-guide.png differ diff --git a/design/shorts/shorts-2-details.png b/design/shorts/shorts-2-details.png new file mode 100644 index 0000000..81f1d3f Binary files /dev/null and b/design/shorts/shorts-2-details.png differ diff --git a/design/shorts/shorts-3-banner.png b/design/shorts/shorts-3-banner.png new file mode 100644 index 0000000..f06a5e2 Binary files /dev/null and b/design/shorts/shorts-3-banner.png differ diff --git a/docs/replays/shorts-replay-2026-11-06.txt b/docs/replays/shorts-replay-2026-11-06.txt index a8ee84e..984021b 100644 --- a/docs/replays/shorts-replay-2026-11-06.txt +++ b/docs/replays/shorts-replay-2026-11-06.txt @@ -1,13 +1,14 @@ Shorts grouping: seven days of new bookings after each committed plan, from the box's files (2026-11-06). Tots (614) - Programmes booked: 762 before, 767 after; shorts among them: 255 before, 263 after - Shorts slots: 35, holding 156 shorts; 107 short(s) left on their own (a slot of one, or the block changed) - Slot length: shortest 10.0 min, median 25.0, longest 42.1; 22 of 35 end within 2 min of :00 or :30 - Shows per slot: average 3.0; the same show twice in a row inside a slot: 0 time(s) - Programmes starting within 2 min of :00 or :30: 136 of 762 before, 133 of 767 after - Labels: {Tots Little Mix=35} - Biggest airtime changes over the week (minutes): Teletubbies -21, Something Special -20, Bluey (2018) +15, Ben and Holly's Little Kingdom -11, Numberblocks +10, CBeebies Bedtime Stories +9, Hey Duggee +7, Peppa Pig +5 + Programmes booked: 762 before, 770 after; shorts among them: 255 before, 265 after + Shorts slots: 32, holding 159 shorts; 106 short(s) left on their own (a slot of one, or the block changed) + Slot length: shortest 17.0 min, median 29.7, longest 44.2; 22 of 32 end within 2 min of :00 or :30 + Shows per slot: average 3.3; the same show twice in a row inside a slot: 0 time(s); a show back later in the same slot: 52 time(s), in 26 slot(s) + Slots under 15 minutes: 0 + Programmes starting within 2 min of :00 or :30: 136 of 762 before, 117 of 770 after + Labels: {Tots Little Mix=32} + Biggest airtime changes over the week (minutes): In The Night Garden -28, Bluey (2018) +22, Teletubbies -21, Something Special -20, Numberblocks +15, Andy's Wild Adventures -15, Floogals +11, Bob the Builder +10 Example slot Thu 14:50 to 15:28 "Tots Little Mix: Bluey (2018), Numberblocks, Peppa Pig and more" 14:50 Bluey (2018) (7.3 min) 14:58 Numberblocks (5.0 min) @@ -32,13 +33,14 @@ Tots (614) After that first slot: 15:28 Andy's Wild Adventures, straight after the last short ToonZ (605) - Programmes booked: 757 before, 758 after; shorts among them: 416 before, 419 after - Shorts slots: 94, holding 419 shorts; 0 short(s) left on their own (a slot of one, or the block changed) - Slot length: shortest 12.6 min, median 32.3, longest 46.4; 58 of 94 end within 2 min of :00 or :30 - Shows per slot: average 3.6; the same show twice in a row inside a slot: 0 time(s) - Programmes starting within 2 min of :00 or :30: 121 of 757 before, 136 of 758 after - Labels: {Cartoon Shorts=94} - Biggest airtime changes over the week (minutes): Duckman +22, Hong Kong Phooey -21, Wacky Races -12, Looney Tunes -9, Tom and Jerry +7, The Yogi Bear Show +7, Popeye the Sailor +7, The Pink Panther -6 + Programmes booked: 757 before, 754 after; shorts among them: 416 before, 413 after + Shorts slots: 87, holding 413 shorts; 0 short(s) left on their own (a slot of one, or the block changed) + Slot length: shortest 13.1 min, median 33.7, longest 49.0; 66 of 87 end within 2 min of :00 or :30 + Shows per slot: average 3.9; the same show twice in a row inside a slot: 0 time(s); a show back later in the same slot: 70 time(s), in 48 slot(s) + Slots under 15 minutes: 3: Sat 21:45 14.9 min, then Futurama (21.7 min), Mon 08:24 13.9 min, then Looney Tunes (23.8 min), Tue 21:48 13.1 min, then Aqua Teen Hunger Force (11.5 min) + Programmes starting within 2 min of :00 or :30: 121 of 757 before, 139 of 754 after + Labels: {Cartoon Shorts=87} + Biggest airtime changes over the week (minutes): The Jetsons -48, The Looney Tunes Show +44, The Flintstones -26, Danger Mouse +24, The Critic +23, Duckman +22, Tom and Jerry -14, Popeye the Sailor -13 Example slot Thu 13:51 to 14:31 "Cartoon Shorts: Tom and Jerry, The Yogi Bear Show, Popeye the Sailor and more" 13:51 Tom and Jerry (6.4 min) 13:57 The Yogi Bear Show (7.1 min) @@ -50,8 +52,8 @@ ToonZ (605) 16:08 Looney Tunes (6.7 min) 16:15 Tom and Jerry (6.4 min) 16:21 The Yogi Bear Show (7.2 min) - Example slot Thu 17:42 to 17:59 "Cartoon Shorts: Popeye the Sailor, Bananaman" + Example slot Thu 17:42 to 18:00 "Cartoon Shorts: Popeye the Sailor, Bananaman, Looney Tunes" 17:42 Popeye the Sailor (6.2 min) 17:48 Bananaman (4.6 min) - 17:53 Popeye the Sailor (6.7 min) + 17:53 Looney Tunes (7.8 min) After that first slot: 14:31 Looney Tunes, straight after the last short