Skip to content

Commit 3f766f4

Browse files
committed
chore: Update go-ytdlp dependency to v1.2.0 and clean up scraper code
1 parent 26e6653 commit 3f766f4

4 files changed

Lines changed: 167 additions & 65 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ require (
1919
require (
2020
github.com/charmbracelet/huh v0.7.0
2121
github.com/charmbracelet/log v0.4.2
22-
github.com/lrstanley/go-ytdlp v1.1.1
22+
github.com/lrstanley/go-ytdlp v1.2.0
2323
)
2424

2525
require (

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ github.com/ktr0731/go-ansisgr v0.1.0 h1:fbuupput8739hQbEmZn1cEKjqQFwtCCZNznnF6AN
9292
github.com/ktr0731/go-ansisgr v0.1.0/go.mod h1:G9lxwgBwH0iey0Dw5YQd7n6PmQTwTuTM/X5Sgm/UrzE=
9393
github.com/ktr0731/go-fuzzyfinder v0.9.0 h1:JV8S118RABzRl3Lh/RsPhXReJWc2q0rbuipzXQH7L4c=
9494
github.com/ktr0731/go-fuzzyfinder v0.9.0/go.mod h1:uybx+5PZFCgMCSDHJDQ9M3nNKx/vccPmGffsXPn2ad8=
95-
github.com/lrstanley/go-ytdlp v1.1.1 h1:1L/H/Hj8Bf9rJLPgY0BlSxDiyGMvCR3o4KHY4CR0C00=
96-
github.com/lrstanley/go-ytdlp v1.1.1/go.mod h1:4Mwvk8i5dAeeBDAEoxeJLa46xA/YpkzO5M6zg7MHJa0=
95+
github.com/lrstanley/go-ytdlp v1.2.0 h1:2L5CiDRLEVPncIaZPMdzlQCg2CJn9YssTUH5vaEmblM=
96+
github.com/lrstanley/go-ytdlp v1.2.0/go.mod h1:4Mwvk8i5dAeeBDAEoxeJLa46xA/YpkzO5M6zg7MHJa0=
9797
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
9898
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
9999
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=

internal/player/playvideo.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import (
2020
"github.com/charmbracelet/huh"
2121
)
2222

23+
24+
25+
2326
// ErrUserQuit is returned when the user chooses to quit the application
2427
var ErrUserQuit = errors.New("user requested to quit application")
2528

internal/player/scraper.go

Lines changed: 161 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,10 @@ import (
1919
"github.com/alvarorichard/Goanime/internal/api"
2020
"github.com/alvarorichard/Goanime/internal/models"
2121
"github.com/alvarorichard/Goanime/internal/util"
22+
"github.com/charmbracelet/huh"
2223
"github.com/ktr0731/go-fuzzyfinder"
2324
)
2425

25-
// WINDOWS RELEASE
26-
27-
// func dialMPVSocket(socketPath string) (net.Conn, error) {
28-
// if runtime.GOOS == "windows" {
29-
// // //Attempt to connect using named pipe on Windows
30-
// conn, err := winio.DialPipe(socketPath, nil)
31-
// if err != nil {
32-
// return nil, fmt.Errorf("failed to connect to named pipe: %w", err)
33-
// }
34-
// return conn, nil
35-
// } else {
36-
// // Unix-like system uses Unix sockets
37-
// conn, err := net.Dial("unix", socketPath)
38-
// if err != nil {
39-
// return nil, fmt.Errorf("failed to connect to Unix socket: %w", err)
40-
// }
41-
// return conn, nil
42-
// }
43-
// }
44-
4526
// DownloadFolderFormatter formats the anime URL to create a download folder name.
4627
//
4728
// This function extracts a specific part of the anime video URL to use it as the name
@@ -226,14 +207,44 @@ func SelectEpisodeWithFuzzyFinder(episodes []models.Episode) (string, string, er
226207

227208
// ExtractEpisodeNumber extracts the episode number from the episode string
228209
func ExtractEpisodeNumber(episodeStr string) string {
229-
// Handle formats like "Episode 100" or "100"
210+
// Try to extract numeric episode number from various patterns
211+
patterns := []string{
212+
`(?i)epis[oó]dio\s+(\d+)`, // "Episódio 1"
213+
`(?i)episode\s+(\d+)`, // "Episode 1"
214+
`(?i)ep\.?\s*(\d+)`, // "Ep 1" or "Ep. 1"
215+
`(?i)cap[íi]tulo\s+(\d+)`, // "Capítulo 1"
216+
`\b(\d+)\b`, // Any standalone number
217+
}
218+
219+
for _, pattern := range patterns {
220+
re := regexp.MustCompile(pattern)
221+
matches := re.FindStringSubmatch(episodeStr)
222+
if len(matches) >= 2 {
223+
return matches[1]
224+
}
225+
}
226+
227+
// Handle simple numeric cases by splitting
230228
parts := strings.Split(strings.TrimSpace(episodeStr), " ")
231229
for _, part := range parts {
232-
if num, err := strconv.Atoi(part); err == nil {
230+
// Clean common separators and try to parse
231+
cleanPart := strings.Trim(part, "()[]{}:.-_")
232+
if num, err := strconv.Atoi(cleanPart); err == nil && num > 0 {
233233
return fmt.Sprintf("%d", num)
234234
}
235235
}
236-
return episodeStr // Fallback to original string if no number is found
236+
237+
// For movies, OVAs, and specials that don't have episode numbers, return "1"
238+
lowerStr := strings.ToLower(episodeStr)
239+
if strings.Contains(lowerStr, "filme") ||
240+
strings.Contains(lowerStr, "movie") ||
241+
strings.Contains(lowerStr, "ova") ||
242+
strings.Contains(lowerStr, "special") {
243+
return "1"
244+
}
245+
246+
// If no number found at all, return "1" as fallback (for single episodes/movies)
247+
return "1"
237248
}
238249

239250
// GetVideoURLForEpisode gets the video URL for a given episode URL
@@ -405,6 +416,7 @@ func findBloggerLink(content string) (string, error) {
405416
}
406417
}
407418

419+
// extractActualVideoURL processes the video source and allows the user to select quality
408420
func extractActualVideoURL(videoSrc string) (string, error) {
409421
if util.IsDebug {
410422
util.Debugf("Processing video source: %s", videoSrc)
@@ -414,7 +426,7 @@ func extractActualVideoURL(videoSrc string) (string, error) {
414426
return videoSrc, nil
415427
}
416428

417-
// If the URL is from animefire.plus, we need to fetch it first
429+
// If the URL is from animefire.plus, fetch the content
418430
if strings.Contains(videoSrc, "animefire.plus/video/") {
419431
if util.IsDebug {
420432
util.Debugf("Found animefire.plus video URL, fetching content...")
@@ -437,7 +449,7 @@ func extractActualVideoURL(videoSrc string) (string, error) {
437449
return "", fmt.Errorf("failed to read video page: %w", err)
438450
}
439451

440-
// Try to parse as JSON first
452+
// Try to parse as JSON
441453
var videoResponse VideoResponse
442454
err = json.Unmarshal(body, &videoResponse)
443455
if err == nil && len(videoResponse.Data) > 0 {
@@ -447,24 +459,60 @@ func extractActualVideoURL(videoSrc string) (string, error) {
447459
util.Debugf("Available quality: %s -> %s", v.Label, v.Src)
448460
}
449461
}
450-
// If we have multiple qualities, pick the highest automatically
451-
if len(videoResponse.Data) > 1 {
452-
best := videoResponse.Data[0]
453-
bestQ, _ := strconv.Atoi(best.Label)
462+
463+
// If only one quality, use it directly
464+
if len(videoResponse.Data) == 1 {
465+
return videoResponse.Data[0].Src, nil
466+
}
467+
468+
// Use global quality preference only if it's a specific quality (not "best")
469+
if util.GlobalQuality != "" && util.GlobalQuality != "best" {
470+
selectedSrc := selectQualityFromOptions(videoResponse.Data, util.GlobalQuality)
471+
if selectedSrc != "" {
472+
if util.IsDebug {
473+
util.Debugf("Using global quality preference: %s -> %s", util.GlobalQuality, selectedSrc)
474+
}
475+
return selectedSrc, nil
476+
}
477+
}
478+
479+
// Always prompt user for quality selection to maintain the 360p, 720p, 1080p options
480+
// Create options for huh.Select
481+
var options []huh.Option[string]
482+
for _, v := range videoResponse.Data {
483+
options = append(options, huh.NewOption(v.Label, v.Src))
484+
}
485+
486+
// Present quality options to the user
487+
var selectedSrc string
488+
err := huh.NewSelect[string]().
489+
Title("Select Video Quality").
490+
Options(options...).
491+
Value(&selectedSrc).
492+
Run()
493+
if err != nil {
494+
return "", fmt.Errorf("failed to select quality: %w", err)
495+
}
496+
497+
// Store the selected quality for future use in this session
498+
if util.GlobalQuality == "" || util.GlobalQuality == "best" {
499+
// Extract quality label from selected option
454500
for _, v := range videoResponse.Data {
455-
q, _ := strconv.Atoi(v.Label)
456-
if q > bestQ {
457-
best = v
458-
bestQ = q
501+
if v.Src == selectedSrc {
502+
util.GlobalQuality = strings.ToLower(v.Label)
503+
if util.IsDebug {
504+
util.Debugf("Storing selected quality for session: %s", util.GlobalQuality)
505+
}
506+
break
459507
}
460508
}
461-
return best.Src, nil
462509
}
463-
// If only one quality, use it
464-
return videoResponse.Data[0].Src, nil
510+
511+
// Return the selected source URL
512+
return selectedSrc, nil
465513
}
466514

467-
// Try to find a direct video URL in the content
515+
// Fallback: Try to find a direct video URL in the content
468516
videoURLPattern := `https?://[^\s<>"]+?\.(?:mp4|m3u8)`
469517
re := regexp.MustCompile(videoURLPattern)
470518
matches := re.FindString(string(body))
@@ -476,7 +524,7 @@ func extractActualVideoURL(videoSrc string) (string, error) {
476524
}
477525

478526
// Try to find a blogger link
479-
videoSrc, err = findBloggerLink(string(body))
527+
videoSrc, err := findBloggerLink(string(body))
480528
if err == nil && videoSrc != "" {
481529
if util.IsDebug {
482530
util.Debugf("Found blogger link: %s", videoSrc)
@@ -485,37 +533,88 @@ func extractActualVideoURL(videoSrc string) (string, error) {
485533
}
486534
}
487535

488-
// Try to parse as JSON first
489-
var videoResponse VideoResponse
490-
err := json.Unmarshal([]byte(videoSrc), &videoResponse)
491-
if err == nil && len(videoResponse.Data) > 0 {
492-
// If we have multiple qualities, pick the highest automatically
493-
if len(videoResponse.Data) > 1 {
494-
best := videoResponse.Data[0]
495-
bestQ, _ := strconv.Atoi(best.Label)
496-
for _, v := range videoResponse.Data {
497-
q, _ := strconv.Atoi(v.Label)
498-
if q > bestQ {
499-
best = v
500-
bestQ = q
501-
}
536+
// If not JSON or no qualities found, return an error
537+
return "", errors.New("no valid video URL found")
538+
}
539+
540+
// selectQualityFromOptions selects the best matching quality from available options
541+
func selectQualityFromOptions(videoData []VideoData, preferredQuality string) string {
542+
if len(videoData) == 0 {
543+
return ""
544+
}
545+
546+
// Normalize preferred quality string
547+
preferredQuality = strings.ToLower(strings.TrimSpace(preferredQuality))
548+
549+
// Handle "best" quality preference
550+
if preferredQuality == "best" || preferredQuality == "" {
551+
// Find the highest quality (assume higher resolution numbers are better)
552+
bestQuality := videoData[0]
553+
for _, v := range videoData {
554+
if extractResolution(v.Label) > extractResolution(bestQuality.Label) {
555+
bestQuality = v
502556
}
503-
return best.Src, nil
504557
}
505-
// If only one quality, use it
506-
return videoResponse.Data[0].Src, nil
558+
return bestQuality.Src
507559
}
508560

509-
// If not JSON, try to find a direct video URL
510-
videoURLPattern := `https?://[^\s<>"]+?\.(?:mp4|m3u8)`
511-
re := regexp.MustCompile(videoURLPattern)
512-
matches := re.FindString(videoSrc)
561+
// Handle "worst" quality preference
562+
if preferredQuality == "worst" {
563+
// Find the lowest quality
564+
worstQuality := videoData[0]
565+
for _, v := range videoData {
566+
if extractResolution(v.Label) < extractResolution(worstQuality.Label) {
567+
worstQuality = v
568+
}
569+
}
570+
return worstQuality.Src
571+
}
513572

514-
if matches == "" {
515-
return "", errors.New("no valid video URL found")
573+
// Try to find exact match first
574+
for _, v := range videoData {
575+
if strings.Contains(strings.ToLower(v.Label), preferredQuality) {
576+
return v.Src
577+
}
516578
}
517579

518-
return matches, nil
580+
// If no exact match, try to find the closest resolution
581+
targetResolution := extractResolution(preferredQuality)
582+
if targetResolution > 0 {
583+
closestMatch := videoData[0]
584+
minDiff := abs(extractResolution(closestMatch.Label) - targetResolution)
585+
586+
for _, v := range videoData {
587+
diff := abs(extractResolution(v.Label) - targetResolution)
588+
if diff < minDiff {
589+
minDiff = diff
590+
closestMatch = v
591+
}
592+
}
593+
return closestMatch.Src
594+
}
595+
596+
// Fallback to first option if nothing matches
597+
return videoData[0].Src
598+
}
599+
600+
// extractResolution extracts numeric resolution from quality label (e.g., "1080p" -> 1080)
601+
func extractResolution(label string) int {
602+
re := regexp.MustCompile(`(\d+)p?`)
603+
matches := re.FindStringSubmatch(strings.ToLower(label))
604+
if len(matches) > 1 {
605+
if res, err := strconv.Atoi(matches[1]); err == nil {
606+
return res
607+
}
608+
}
609+
return 0
610+
}
611+
612+
// abs returns the absolute value of an integer
613+
func abs(x int) int {
614+
if x < 0 {
615+
return -x
616+
}
617+
return x
519618
}
520619

521620
// VideoData represents the video data structure, with a source URL and a label

0 commit comments

Comments
 (0)