diff --git a/internal/panopto/downloader.go b/internal/panopto/downloader.go index 968b4f7..f059785 100644 --- a/internal/panopto/downloader.go +++ b/internal/panopto/downloader.go @@ -17,6 +17,27 @@ import ( "git.directme.in/Joren/CanvasArchiver/internal/utils" ) +// normalizePanoptoURL converts the query-param form that Canvas stores +// (List.aspx?folderID=X) to the fragment form that yt-dlp's PanoptoList +// extractor understands (List.aspx#folderID="X"). Without this yt-dlp +// ignores the folder filter and downloads the entire Panopto instance. +func normalizePanoptoURL(rawURL string) string { + parsed, err := url.Parse(rawURL) + if err != nil { + return rawURL + } + if strings.Contains(parsed.Path, "List.aspx") { + folderID := parsed.Query().Get("folderID") + if folderID != "" { + // Strip query, set fragment: List.aspx#folderID="" + parsed.RawQuery = "" + parsed.Fragment = fmt.Sprintf(`folderID="%s"`, folderID) + return parsed.String() + } + } + return rawURL +} + func getYoutubeDLCommand() string { exePath, err := os.Executable() if err == nil { @@ -192,13 +213,35 @@ func DownloadVideo(httpClient *http.Client, accessToken, courseID, modDir, input fmt.Printf(" [*] Downloading video: %s\n", title) ytCmd := getYoutubeDLCommand() - cmd := exec.Command(ytCmd, - "--no-playlist", - "--cookies", cookieFile, - "--referer", config.BaseURL+"/", - "-P", modDir, - "-o", utils.Sanitize(title)+".%(ext)s", - targetURL) + + // Normalize folder URLs so yt-dlp scopes to the right folder. + normalizedURL := normalizePanoptoURL(targetURL) + + // Folder/list URLs are intentional playlists; don't pass --no-playlist. + isList := strings.Contains(normalizedURL, "List.aspx") + var outputTpl string + var args []string + if isList { + outputTpl = utils.Sanitize(title) + "/%(title)s.%(ext)s" + args = []string{ + "--cookies", cookieFile, + "--referer", config.BaseURL + "/", + "-P", modDir, + "-o", outputTpl, + normalizedURL, + } + } else { + outputTpl = utils.Sanitize(title) + ".%(ext)s" + args = []string{ + "--no-playlist", + "--cookies", cookieFile, + "--referer", config.BaseURL + "/", + "-P", modDir, + "-o", outputTpl, + normalizedURL, + } + } + cmd := exec.Command(ytCmd, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr