diff --git a/offlineu_core.py b/offlineu_core.py index 278bed2..7305fd3 100644 --- a/offlineu_core.py +++ b/offlineu_core.py @@ -526,25 +526,26 @@ def find_course_thumbnail(course_path: str) -> Optional[str]: _SECTION_NAME_RE = re.compile( - r'^(section|module|chapter|part|unit|lesson)\b', re.IGNORECASE + r'^(section|module|chapter|part|unit|lesson)\b|^\d+[\s_.\-]', re.IGNORECASE ) def _looks_like_course(directory: Path) -> bool: """ - Heuristic for 'this folder is a course, stop recursing into it': - it has media files directly, or at least two of its immediate - subfolders do (covers the common Section 1/, Section 2/... layout). + Heuristic for 'this folder is a course, stop recursing into it': it has + media files directly, or its media-holding immediate subfolders all + read as chapter/section labels ("Section 1", "Module 2", "01 - + Introduction", ...) rather than course titles - covers the common + Section 1/, Section 2/... (or numbered-chapter) layout. - Requiring 2+ matching subfolders (rather than just 1) avoids mistaking - a publisher/grouping folder that holds a single course - e.g. - Pluralsight/Docker Deep Dive/lesson.mp4 - for the course itself. - - The one exception: a directory with exactly one media-holding subfolder - whose name reads as a section label ("Section 1", "Module 2", ...) - rather than a course title. That's still a course with just one - section, not a publisher folder - Pluralsight/Docker Deep Dive doesn't - get named "Section 1", so this doesn't reopen the ambiguity above. + Requiring *every* media-holding subfolder to match, not just one, is + what keeps a publisher/category folder holding several unrelated + courses - e.g. Claude/Pluralsight.X.../*.mp4 next to + Claude/Linkedin.Learning.Y.../*.mp4 - from being mistaken for a single + course just because more than one of its subfolders happens to store + episodes directly rather than nested under their own chapter folder. + Real course titles ("Pluralsight.Docker.Deep.Dive...") don't read as + chapter labels, so this doesn't reopen that ambiguity. """ if _has_direct_media(directory): return True @@ -556,14 +557,9 @@ def _looks_like_course(directory: Path) -> bool: except (PermissionError, OSError): return False children_with_media = [child for child in children if _has_direct_media(child)] - if len(children_with_media) >= 2: - return True - if ( - len(children_with_media) == 1 - and _SECTION_NAME_RE.match(children_with_media[0].name.strip()) - ): - return True - return False + if not children_with_media: + return False + return all(_SECTION_NAME_RE.match(child.name.strip()) for child in children_with_media) HIDDEN_PATHS_FILE = os.path.join(DATA_DIR, 'hidden_paths.json')