@@ -16,7 +16,7 @@ import (
1616
1717func fetchInfoCmd (url string , browser string ) tea.Cmd {
1818 return func () tea.Msg {
19- args := []string {"-J" , "--flat-playlist" , "--no-warnings" , "--quiet" , "--ignore-errors" }
19+ args := []string {"-J" , "--flat-playlist" , "--no-warnings" , "--quiet" , "--ignore-errors" , "--ignore-config" , "--no-check-formats" , "--js-runtimes" , "node" }
2020 if browser != "" && browser != "none" {
2121 args = append (args , "--cookies-from-browser" , browser )
2222 }
@@ -27,37 +27,44 @@ func fetchInfoCmd(url string, browser string) tea.Cmd {
2727 cmd = exec .Command ("python3" , append ([]string {"-m" , "yt_dlp" }, args ... )... )
2828 }
2929
30- var stderr strings.Builder
30+ var stdout , stderr strings.Builder
31+ cmd .Stdout = & stdout
3132 cmd .Stderr = & stderr
32- out , err := cmd .Output ()
33- if err != nil {
34- errMsgStr := stderr .String ()
35- if errMsgStr == "" {
36- errMsgStr = err .Error ()
33+ runErr := cmd .Run ()
34+
35+ outStr := strings .TrimSpace (stdout .String ())
36+
37+ // Prefer valid JSON output even if yt-dlp exited non-zero (e.g. format warnings)
38+ if outStr != "" {
39+ var info map [string ]interface {}
40+ if err := json .Unmarshal ([]byte (outStr ), & info ); err == nil {
41+ title , _ := info ["title" ].(string )
42+ durFloat , _ := info ["duration" ].(float64 )
43+ return infoFetchedMsg {
44+ title : title ,
45+ duration : durFloat ,
46+ }
3747 }
38- return errMsg {err : fmt .Errorf ("could not fetch info: %s" , errMsgStr )}
3948 }
4049
41- var info map [string ]interface {}
42- if err := json .Unmarshal (out , & info ); err != nil {
43- return errMsg {err : fmt .Errorf ("failed to parse info: %v" , err )}
50+ // No usable JSON — surface the actual error
51+ if runErr != nil {
52+ errMsgStr := strings .TrimSpace (stderr .String ())
53+ if errMsgStr == "" {
54+ errMsgStr = runErr .Error ()
55+ }
56+ return errMsg {err : fmt .Errorf ("could not fetch info: %s" , errMsgStr )}
4457 }
4558
46- title , _ := info ["title" ].(string )
47- durFloat , _ := info ["duration" ].(float64 )
48-
49- return infoFetchedMsg {
50- title : title ,
51- duration : durFloat ,
52- }
59+ return errMsg {err : fmt .Errorf ("could not fetch info: no output from yt-dlp" )}
5360 }
5461}
5562
5663func searchCmd (query string ) tea.Cmd {
5764 return func () tea.Msg {
58- cmd := exec .Command ("yt-dlp" , "ytsearch5:" + query , "-j" , "--no-warnings" , "--quiet" , "--flat-playlist" )
65+ cmd := exec .Command ("yt-dlp" , "ytsearch5:" + query , "-j" , "--no-warnings" , "--quiet" , "--flat-playlist" , "--ignore-config" )
5966 if _ , err := exec .LookPath ("yt-dlp" ); err != nil {
60- cmd = exec .Command ("python3" , "-m" , "yt_dlp" , "ytsearch5:" + query , "-j" , "--no-warnings" , "--quiet" , "--flat-playlist" )
67+ cmd = exec .Command ("python3" , "-m" , "yt_dlp" , "ytsearch5:" + query , "-j" , "--no-warnings" , "--quiet" , "--flat-playlist" , "--ignore-config" )
6168 }
6269
6370 out , err := cmd .Output ()
@@ -96,36 +103,41 @@ func searchCmd(query string) tea.Cmd {
96103 }
97104}
98105
99- func startDownloadTask (c chan tea.Msg , url , saveDir , saveFilename , format , browser string ) {
106+ func startDownloadTask (c chan tea.Msg , url , saveDir , saveFilename , format , browser , archivePath string ) {
100107 outtmpl := filepath .Join (saveDir , saveFilename + "." + format )
101108 isPlaylist := strings .Contains (url , "list=" ) || strings .Contains (url , "playlist" )
102-
109+
103110 args := []string {
104- "-f" , "bestaudio/best" ,
111+ "-f" , "bestaudio[ext=m4a]/bestaudio[ext=webm]/bestaudio /best" ,
105112 "--extract-audio" ,
106113 "--audio-format" , format ,
114+ "--audio-quality" , "0" ,
115+ "--no-check-formats" ,
116+ "--js-runtimes" , "node" ,
117+ "--embed-metadata" ,
107118 "--no-warnings" ,
108119 "--newline" ,
109120 "--progress" ,
110121 "--ignore-errors" ,
111122 "--no-cache-dir" ,
112123 "--lazy-playlist" ,
113124 "--no-overwrites" ,
125+ "--ignore-config" ,
114126 }
115127
116128 if isPlaylist {
117129 playlistDir := filepath .Join (saveDir , saveFilename )
118130 os .MkdirAll (playlistDir , 0755 )
119-
120- // LOCAL ARCHIVE: Keep track of downloads INSIDE the playlist folder
121- // This prevents duplicates within the folder but allows downloading
122- // the same song to a DIFFERENT folder.
123- archivePath := filepath .Join (playlistDir , ".soundsnatch_archive.txt" )
124- args = append (args , "--download-archive" , archivePath )
125-
126- outtmpl = filepath .Join (playlistDir , "%(title)s [%(id)s].%(ext)s" )
131+ outtmpl = filepath .Join (playlistDir , "%(title)s.%(ext)s" )
132+ // Use a per-playlist archive so the same song can exist in different playlists
133+ // and playlists don't interfere with each other
134+ localArchive := filepath .Join (playlistDir , ".archive.txt" )
135+ args = append (args , "--download-archive" , localArchive )
127136 } else {
128137 args = append (args , "--no-playlist" )
138+ if archivePath != "" {
139+ args = append (args , "--download-archive" , archivePath )
140+ }
129141 }
130142
131143 args = append (args , "-o" , outtmpl )
@@ -184,7 +196,7 @@ func startDownloadTask(c chan tea.Msg, url, saveDir, saveFilename, format, brows
184196 }
185197 }
186198
187- c <- downloadDoneMsg {message : fmt .Sprintf ("🎉 Sync Complete!\n Files are in : %s" , saveDir )}
199+ c <- downloadDoneMsg {message : fmt .Sprintf ("🎉 Sync Complete!\n Your library at : %s is now up to date. " , saveDir )}
188200}
189201
190202func waitForMsg (c chan tea.Msg ) tea.Cmd {
0 commit comments