Smart grouping for marathon style channels - #1678
Conversation
Enable marathon-style playback by grouping programs by their tags (e.g., "Harry Potter Collection"). Groups are ordered by the slot's existing order setting, while programs within each group always play in chronological order. Configurable options for untagged programs (include/exclude) and multi-tag behavior (first match/all groups). Co-Authored-By: Claude Opus 4.6 <[email protected]>
Programs with multiple tags were appearing multiple times in the lineup when multiTagBehavior was set to 'all'. Now tracks seen UUIDs during the flatten step so each program only appears once, in whichever group comes first in the ordering. Co-Authored-By: Claude Opus 4.6 <[email protected]>
chrisbenincasa
left a comment
There was a problem hiding this comment.
Hey! Thanks for the PR. I generally like the idea, and it is something that has been asked for in different ways by users. I do think that there are some gaps in behavior and higher - level questions I have, in addition to some improvements in the code itself to match the style of Tunarr and reuse existing functions, etc for consistent behavior. I've left a first round of comments.
| } | ||
|
|
||
| // Sort programs within each group chronologically by year | ||
| const sortChronologically = ( |
There was a problem hiding this comment.
These orderers all already exist in ProgramIterator.ts (and are more battle tested / correct)
| * When order is 'next' or 'chronological', groups are sorted by earliest year. | ||
| * When order is 'ordered_shuffle', groups are sorted then rotated randomly. | ||
| */ | ||
| export function groupProgramsByTag( |
There was a problem hiding this comment.
is this used outside of this file? can we remove the export?
| const ungrouped: SlotSchedulerProgram[] = []; | ||
|
|
||
| for (const program of programs) { | ||
| const tags = program.tags?.map((t) => t.tag.tag).filter(Boolean) ?? []; |
There was a problem hiding this comment.
Tunarr has a convenience method for this operation: seq.collect (seq is imported from !tunarr/shared/util
| <> | ||
| <FormHelperText> | ||
| Group programs by shared tags for marathon-style playback. Programs | ||
| within each group play in chronological order. |
There was a problem hiding this comment.
Is this true? The code above to create the grouped chunks takes in the slot's configured order and outputs the groupBy program list using that orderer
| }); | ||
|
|
||
| export const SlotGroupBySchema = z.object({ | ||
| type: z.literal('tag'), |
There was a problem hiding this comment.
This is only really necessary if we are using a discriminated union and there are other types of "group by"s we need to think about. If you think there will be more in the future, we could preemptively make this a discriminated union -- otherwise I think we can omit this field entirely.
| export function groupProgramsByTag( | ||
| programs: SlotSchedulerProgram[], | ||
| groupBy: SlotGroupBy, | ||
| order: string, |
There was a problem hiding this comment.
Unfortunately we don't have a named type here, but let's prefer BaseSlot['order'] or something similar for tighter type checking.
| const tags = program.tags?.map((t) => t.tag.tag).filter(Boolean) ?? []; | ||
|
|
||
| if (tags.length === 0) { | ||
| if (groupBy.ungrouped === 'include') { |
There was a problem hiding this comment.
[nit] can be consolidated to a single conditional claude tags.length === 0 && groupBy.ungrouped === 'include'
| } | ||
|
|
||
| // Flatten groups into final program list, deduplicating programs | ||
| // that appear in multiple groups (when multiTagBehavior === 'all') |
There was a problem hiding this comment.
I'm a little confused by this - if we use multiTagBehavior 'all' and a program appears under multiple tags, it'll only play in the marathon for the first tag? I understand removing repeats, but the behavior still strikes me as a little surprising. Additionally, I assume the tags don't have a consistent ordering?
|
Friendly ping :) |
I wanted to make a channel where I had "marathon" style playback so different movie series would get grouped together in chronological release order. The only way I saw this working was with the tags attribute on each movie, in my case with plex they include enough information to link collections together as each movie series has the same collection tag.
I'm not too familiar with this repo yet but was able to add this to the slots editor functionality with help of claude. Please excuse any mistakes, will work on any suggestions and improvements as necessary. Would love for help in testing and making this feature better, I have pushed a tagged build on my fork
ghcr.io/stasadance/tunarr:smart-groupingSummary
programs sharing a tag (e.g., "Harry Potter Collection") play back-to-back
programs within each group always play in chronological/release order
match or all matching groups, deduplicated)
Changes
SlotGroupBySchemawithtype,ungrouped, andmultiTagBehaviorfields; added optionalgroupByto movie and smart-collection slot schemasgetChannelAndProgramsandgetProgramGroupingDescendantsso tagsare available during scheduling
groupProgramsByTag()pre-processing step that buckets programs by tag, sorts withingroups chronologically, orders groups by slot order, and deduplicates across groups
SlotGroupByFormControlcomponent with toggle switch and dropdown options, added to movie slotand smart collection slot editors