mirror of
https://github.com/fiso64/slsk-batchdl.git
synced 2024-12-22 22:42:41 +00:00
fix spotify and csv retrieval
place files in subdirs for multi-album downloads if name-format is empty
This commit is contained in:
parent
178dfe26b3
commit
d5c42ad8b8
12 changed files with 106 additions and 51 deletions
23
README.md
23
README.md
|
@ -311,6 +311,14 @@ will be ignored.
|
||||||
|
|
||||||
## Searching
|
## Searching
|
||||||
|
|
||||||
|
### Search Query
|
||||||
|
The search query is determined as follows:
|
||||||
|
|
||||||
|
- For album downloads: If the album field is non-empty, search for 'Artist Album'
|
||||||
|
Otherwise, search for 'Artist Title'
|
||||||
|
- For all other download types: If the title field is non-empty, search for 'Artist Title'
|
||||||
|
Otherwise, search for 'Artist Album'
|
||||||
|
|
||||||
### Soulseek's rate limits
|
### Soulseek's rate limits
|
||||||
The server will ban you for 30 minutes if too many searches are performed within a short
|
The server will ban you for 30 minutes if too many searches are performed within a short
|
||||||
timespan. The program has a search limiter which can be adjusted with --searches-per-time
|
timespan. The program has a search limiter which can be adjusted with --searches-per-time
|
||||||
|
@ -492,13 +500,20 @@ sldl test.csv
|
||||||
```
|
```
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Download spotify likes while skipping songs that already exist in the output folder:
|
Download spotify likes:
|
||||||
```
|
```
|
||||||
sldl spotify-likes --skip-existing
|
sldl spotify-likes
|
||||||
```
|
```
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Download from a youtube playlist with fallback to yt-dlp in case it is not found on soulseek, and retrieve deleted video titles from wayback machine:
|
Download albums for every song in a spotify playlist:
|
||||||
|
```
|
||||||
|
sldl https://spotify/playlist/url --album --skip-existing
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Retrieve deleted video names, then download from a youtube playlist with fallback to yt-dlp:
|
||||||
```
|
```
|
||||||
sldl "https://www.youtube.com/playlist?list=PLI_eFW8NAFzYAXZ5DrU6E6mQ_XfhaLBUX" --get-deleted --yt-dlp
|
sldl "https://www.youtube.com/playlist?list=PLI_eFW8NAFzYAXZ5DrU6E6mQ_XfhaLBUX" --get-deleted --yt-dlp
|
||||||
```
|
```
|
||||||
|
@ -522,7 +537,7 @@ sldl "artist=MC MENTAL" --aggregate --skip-existing --music-dir "path/to/music"
|
||||||
```
|
```
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Download all albums:
|
Download all albums by an artist that are on soulseek:
|
||||||
```
|
```
|
||||||
sldl "artist=MC MENTAL" --aggregate --album
|
sldl "artist=MC MENTAL" --aggregate --album
|
||||||
```
|
```
|
||||||
|
|
|
@ -225,10 +225,16 @@ static class Config
|
||||||
|
|
||||||
if (DoNotDownload)
|
if (DoNotDownload)
|
||||||
m3uOption = M3uOption.None;
|
m3uOption = M3uOption.None;
|
||||||
else if (!hasConfiguredM3uMode && inputType == InputType.String)
|
else if (!hasConfiguredM3uMode)
|
||||||
m3uOption = M3uOption.None;
|
{
|
||||||
else if (!hasConfiguredM3uMode && Program.trackLists != null && !aggregate &&!Program.trackLists.Flattened(true, false, true).Skip(1).Any())
|
if (inputType == InputType.String)
|
||||||
m3uOption = M3uOption.None;
|
m3uOption = M3uOption.None;
|
||||||
|
else if (!aggregate && !(skipExisting && (skipMode == SkipMode.M3u || skipMode == SkipMode.M3uCond))
|
||||||
|
&& Program.trackLists != null && !Program.trackLists.Flattened(true, false, true).Skip(1).Any())
|
||||||
|
{
|
||||||
|
m3uOption = M3uOption.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parentFolder = Utils.ExpandUser(parentFolder);
|
parentFolder = Utils.ExpandUser(parentFolder);
|
||||||
m3uFilePath = Utils.ExpandUser(m3uFilePath);
|
m3uFilePath = Utils.ExpandUser(m3uFilePath);
|
||||||
|
|
|
@ -51,7 +51,10 @@ namespace Data
|
||||||
|
|
||||||
public string ToKey()
|
public string ToKey()
|
||||||
{
|
{
|
||||||
return $"{Artist};{Album};{Title};{Length};{(int)Type}";
|
if (Type == TrackType.Album)
|
||||||
|
return $"{Artist};{Album};{(int)Type}";
|
||||||
|
else
|
||||||
|
return $"{Artist};{Album};{Title};{Length};{(int)Type}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
@ -103,7 +106,7 @@ namespace Data
|
||||||
public bool needSkipExistingAfterSearch = false;
|
public bool needSkipExistingAfterSearch = false;
|
||||||
public bool gotoNextAfterSearch = false;
|
public bool gotoNextAfterSearch = false;
|
||||||
public bool placeInSubdir = false;
|
public bool placeInSubdir = false;
|
||||||
public string? subdirOverride;
|
public bool useRemoteDirname = false;
|
||||||
|
|
||||||
public TrackListEntry()
|
public TrackListEntry()
|
||||||
{
|
{
|
||||||
|
@ -138,12 +141,13 @@ namespace Data
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackListEntry(List<List<Track>> list, Track source, bool needSearch, bool placeInSubdir,
|
public TrackListEntry(List<List<Track>> list, Track source, bool needSearch, bool placeInSubdir,
|
||||||
bool canBeSkipped, bool needSkipExistingAfterSearch, bool gotoNextAfterSearch)
|
bool useRemoteDirname, bool canBeSkipped, bool needSkipExistingAfterSearch, bool gotoNextAfterSearch)
|
||||||
{
|
{
|
||||||
this.list = list;
|
this.list = list;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.needSourceSearch = needSearch;
|
this.needSourceSearch = needSearch;
|
||||||
this.placeInSubdir = placeInSubdir;
|
this.placeInSubdir = placeInSubdir;
|
||||||
|
this.useRemoteDirname = useRemoteDirname;
|
||||||
this.sourceCanBeSkipped = canBeSkipped;
|
this.sourceCanBeSkipped = canBeSkipped;
|
||||||
this.needSkipExistingAfterSearch = needSkipExistingAfterSearch;
|
this.needSkipExistingAfterSearch = needSkipExistingAfterSearch;
|
||||||
this.gotoNextAfterSearch = gotoNextAfterSearch;
|
this.gotoNextAfterSearch = gotoNextAfterSearch;
|
||||||
|
@ -183,10 +187,9 @@ namespace Data
|
||||||
res.AddTrackToLast(enumerator.Current);
|
res.AddTrackToLast(enumerator.Current);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasNext && enumerator.Current.Type != TrackType.Normal)
|
if (hasNext)
|
||||||
res.AddEntry(new TrackListEntry(track));
|
res.AddEntry(new TrackListEntry(enumerator.Current));
|
||||||
else if (!hasNext)
|
else break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,11 +283,26 @@ namespace Data
|
||||||
lists = newLists;
|
lists = newLists;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetListEntryOptions()
|
||||||
|
{
|
||||||
|
// place downloads in subdirs if there is more than one special (album/aggregate) download
|
||||||
|
bool placeInSubdirs = Flattened(true, false, true).Skip(1).Any();
|
||||||
|
|
||||||
|
if (placeInSubdirs)
|
||||||
|
{
|
||||||
|
foreach(var tle in lists)
|
||||||
|
{
|
||||||
|
if (tle.source.Type != TrackType.Normal)
|
||||||
|
tle.placeInSubdir = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<Track> Flattened(bool addSources, bool addSpecialSourceTracks, bool sourcesOnly = false)
|
public IEnumerable<Track> Flattened(bool addSources, bool addSpecialSourceTracks, bool sourcesOnly = false)
|
||||||
{
|
{
|
||||||
foreach (var tle in lists)
|
foreach (var tle in lists)
|
||||||
{
|
{
|
||||||
if ((addSources || sourcesOnly) && tle.source != null)
|
if ((addSources || sourcesOnly) && tle.source != null && tle.source.Type != TrackType.Normal)
|
||||||
yield return tle.source;
|
yield return tle.source;
|
||||||
if (!sourcesOnly && tle.list.Count > 0 && (tle.source.Type == TrackType.Normal || addSpecialSourceTracks))
|
if (!sourcesOnly && tle.list.Count > 0 && (tle.source.Type == TrackType.Normal || addSpecialSourceTracks))
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace Extractors
|
||||||
|
|
||||||
if (isArtist)
|
if (isArtist)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine("Retrieving bandcamp artist discography..");
|
||||||
string artistUrl = Config.input.TrimEnd('/');
|
string artistUrl = Config.input.TrimEnd('/');
|
||||||
|
|
||||||
if (!artistUrl.EndsWith("/music"))
|
if (!artistUrl.EndsWith("/music"))
|
||||||
|
@ -60,13 +61,13 @@ namespace Extractors
|
||||||
{
|
{
|
||||||
source = t,
|
source = t,
|
||||||
placeInSubdir = true,
|
placeInSubdir = true,
|
||||||
subdirOverride = t.ToString(true)
|
|
||||||
};
|
};
|
||||||
trackLists.AddEntry(tle);
|
trackLists.AddEntry(tle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Console.WriteLine("Retrieving bandcamp item..");
|
||||||
var web = new HtmlWeb();
|
var web = new HtmlWeb();
|
||||||
var doc = await web.LoadFromWebAsync(Config.input);
|
var doc = await web.LoadFromWebAsync(Config.input);
|
||||||
|
|
||||||
|
|
|
@ -17,25 +17,22 @@ namespace Extractors
|
||||||
|
|
||||||
public async Task<TrackLists> GetTracks(int maxTracks, int offset, bool reverse)
|
public async Task<TrackLists> GetTracks(int maxTracks, int offset, bool reverse)
|
||||||
{
|
{
|
||||||
int max = reverse ? int.MaxValue : maxTracks;
|
|
||||||
int off = reverse ? 0 : offset;
|
|
||||||
|
|
||||||
if (!File.Exists(Config.input))
|
if (!File.Exists(Config.input))
|
||||||
throw new FileNotFoundException("CSV file not found");
|
throw new FileNotFoundException("CSV file not found");
|
||||||
|
|
||||||
var tracks = await ParseCsvIntoTrackInfo(Config.input, Config.artistCol, Config.trackCol, Config.lengthCol, Config.albumCol, Config.descCol, Config.ytIdCol, Config.trackCountCol, Config.timeUnit, Config.ytParse);
|
var tracks = await ParseCsvIntoTrackInfo(Config.input, Config.artistCol, Config.trackCol, Config.lengthCol,
|
||||||
|
Config.albumCol, Config.descCol, Config.ytIdCol, Config.trackCountCol, Config.timeUnit, Config.ytParse);
|
||||||
|
|
||||||
if (reverse)
|
if (reverse)
|
||||||
tracks.Reverse();
|
tracks.Reverse();
|
||||||
|
|
||||||
var trackLists = TrackLists.FromFlattened(tracks.Skip(off).Take(max));
|
var trackLists = TrackLists.FromFlattened(tracks.Skip(offset).Take(maxTracks));
|
||||||
|
|
||||||
foreach (var tle in trackLists.lists)
|
foreach (var tle in trackLists.lists)
|
||||||
{
|
{
|
||||||
if (tle.source.Type != TrackType.Normal)
|
if (tle.source.Type != TrackType.Normal)
|
||||||
{
|
{
|
||||||
tle.placeInSubdir = true;
|
tle.placeInSubdir = true;
|
||||||
tle.subdirOverride = tle.source.ToString(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,8 @@ namespace Extractors
|
||||||
|
|
||||||
Config.defaultFolderName = playlistName.ReplaceInvalidChars(Config.invalidReplaceStr);
|
Config.defaultFolderName = playlistName.ReplaceInvalidChars(Config.invalidReplaceStr);
|
||||||
|
|
||||||
|
trackLists.AddEntry(tle);
|
||||||
|
|
||||||
if (reverse)
|
if (reverse)
|
||||||
{
|
{
|
||||||
trackLists.Reverse();
|
trackLists.Reverse();
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace Extractors
|
||||||
var trackLists = new TrackLists();
|
var trackLists = new TrackLists();
|
||||||
var music = ParseTrackArg(Config.input, Config.album);
|
var music = ParseTrackArg(Config.input, Config.album);
|
||||||
|
|
||||||
if (music.Title.Length == 0 && music.Album.Length > 0)
|
if (Config.album || (music.Title.Length == 0 && music.Album.Length > 0))
|
||||||
{
|
{
|
||||||
music.Type = TrackType.Album;
|
music.Type = TrackType.Album;
|
||||||
trackLists.AddEntry(new TrackListEntry(music));
|
trackLists.AddEntry(new TrackListEntry(music));
|
||||||
|
@ -35,7 +35,7 @@ namespace Extractors
|
||||||
return trackLists;
|
return trackLists;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Track ParseTrackArg(string input, bool isAlbum)
|
static public Track ParseTrackArg(string input, bool isAlbum)
|
||||||
{
|
{
|
||||||
input = input.Trim();
|
input = input.Trim();
|
||||||
var track = new Track();
|
var track = new Track();
|
||||||
|
|
|
@ -283,6 +283,14 @@ public static class Help
|
||||||
const string searchHelp = @"
|
const string searchHelp = @"
|
||||||
Searching
|
Searching
|
||||||
|
|
||||||
|
Search Query
|
||||||
|
The search query is determined as follows:
|
||||||
|
|
||||||
|
- For album downloads: If the album field is non-empty, search for 'Artist Album'
|
||||||
|
Otherwise, search for 'Artist Title'
|
||||||
|
- For all other download types: If the title field is non-empty, search for 'Artist Title'
|
||||||
|
Otherwise, search for 'Artist Album'
|
||||||
|
|
||||||
Soulseek's rate limits
|
Soulseek's rate limits
|
||||||
The server will ban you for 30 minutes if too many searches are performed within a short
|
The server will ban you for 30 minutes if too many searches are performed within a short
|
||||||
timespan. The program has a search limiter which can be adjusted with --searches-per-time
|
timespan. The program has a search limiter which can be adjusted with --searches-per-time
|
||||||
|
|
|
@ -289,6 +289,7 @@ public class M3uEditor
|
||||||
|
|
||||||
public Track? PreviousRunResult(Track track)
|
public Track? PreviousRunResult(Track track)
|
||||||
{
|
{
|
||||||
|
var key = track.ToKey();
|
||||||
previousRunData.TryGetValue(track.ToKey(), out var t);
|
previousRunData.TryGetValue(track.ToKey(), out var t);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,10 +68,12 @@ static partial class Program
|
||||||
|
|
||||||
WriteLine("Got tracks", debugOnly: true);
|
WriteLine("Got tracks", debugOnly: true);
|
||||||
|
|
||||||
Config.PostProcessArgs();
|
|
||||||
|
|
||||||
trackLists.UpgradeListTypes(Config.aggregate, Config.album);
|
trackLists.UpgradeListTypes(Config.aggregate, Config.album);
|
||||||
|
|
||||||
|
trackLists.SetListEntryOptions();
|
||||||
|
|
||||||
|
Config.PostProcessArgs();
|
||||||
|
|
||||||
m3uEditor = new M3uEditor(Config.m3uFilePath, trackLists, Config.m3uOption, Config.offset);
|
m3uEditor = new M3uEditor(Config.m3uFilePath, trackLists, Config.m3uOption, Config.offset);
|
||||||
|
|
||||||
InitExistingChecker();
|
InitExistingChecker();
|
||||||
|
@ -262,7 +264,7 @@ static partial class Program
|
||||||
foreach (var item in res)
|
foreach (var item in res)
|
||||||
{
|
{
|
||||||
var newSource = new Track(tle.source) { Type = TrackType.Album };
|
var newSource = new Track(tle.source) { Type = TrackType.Album };
|
||||||
trackLists.AddEntry(new TrackListEntry(item, newSource, false, true, true, false, false));
|
trackLists.AddEntry(new TrackListEntry(item, newSource, false, true, true, true, false, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,9 +682,9 @@ static partial class Program
|
||||||
|
|
||||||
soulseekFolderPathPrefix = GetCommonPathPrefix(tracks);
|
soulseekFolderPathPrefix = GetCommonPathPrefix(tracks);
|
||||||
|
|
||||||
if (tle.placeInSubdir && Config.nameFormat.Length == 0 && (!downloadingImages || idx == 0))
|
if (tle.placeInSubdir && Config.nameFormat.Length == 0 && (idx == 0 || !downloadingImages))
|
||||||
{
|
{
|
||||||
string name = tle.subdirOverride ?? Utils.GetBaseNameSlsk(soulseekFolderPathPrefix);
|
string name = tle.useRemoteDirname ? Utils.GetBaseNameSlsk(soulseekFolderPathPrefix) : tle.source.ToString(true);
|
||||||
Config.outputFolder = Path.Join(savedOutputFolder, name);
|
Config.outputFolder = Path.Join(savedOutputFolder, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -919,11 +919,22 @@ static partial class Program
|
||||||
|
|
||||||
static string GetSearchString(Track track)
|
static string GetSearchString(Track track)
|
||||||
{
|
{
|
||||||
if (track.Title.Length > 0)
|
if (track.Type == TrackType.Album)
|
||||||
return (track.Artist + " " + track.Title).Trim();
|
{
|
||||||
else if (track.Album.Length > 0)
|
if (track.Album.Length > 0)
|
||||||
return (track.Artist + " " + track.Album).Trim();
|
return (track.Artist + " " + track.Album).Trim();
|
||||||
return track.Artist.Trim();
|
if (track.Title.Length > 0)
|
||||||
|
return (track.Artist + " " + track.Title).Trim();
|
||||||
|
return track.Artist.Trim();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (track.Title.Length > 0)
|
||||||
|
return (track.Artist + " " + track.Title).Trim();
|
||||||
|
else if (track.Album.Length > 0)
|
||||||
|
return (track.Artist + " " + track.Album).Trim();
|
||||||
|
return track.Artist.Trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
using Data;
|
using Data;
|
||||||
using Enums;
|
using Enums;
|
||||||
using ExistingCheckers;
|
using ExistingCheckers;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using static Test.Helpers;
|
using static Test.Helpers;
|
||||||
|
|
||||||
|
@ -238,17 +232,17 @@ namespace Test
|
||||||
|
|
||||||
var albums = new List<Track>()
|
var albums = new List<Track>()
|
||||||
{
|
{
|
||||||
new Track() { Album="Some Title" },
|
new Track() { Album="Some Title", Type = TrackType.Album },
|
||||||
new Track() { Album="Some, Title" },
|
new Track() { Album="Some, Title", Type = TrackType.Album },
|
||||||
new Track() { Title = "some title", Artist = "Some artist" },
|
new Track() { Title = "some title", Artist = "Some artist", Type = TrackType.Album },
|
||||||
new Track() { Album = "Title", Artist = "Artist", Length = 42 },
|
new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album },
|
||||||
new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42 },
|
new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42, Type = TrackType.Album },
|
||||||
new Track() { Artist = "Some, Artist = a", Album = "Some, Album", Length = 42 },
|
new Track() { Artist = "Some, Artist = a", Album = "Some, Album", Length = 42, Type = TrackType.Album },
|
||||||
|
|
||||||
new Track() { Album = "Foo Bar" },
|
new Track() { Album = "Foo Bar", Type = TrackType.Album },
|
||||||
new Track() { Album = "Bar", Artist = "Foo" },
|
new Track() { Album = "Bar", Artist = "Foo", Type = TrackType.Album },
|
||||||
new Track() { Album = "Title", Artist = "Artist", Length = 42 },
|
new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album },
|
||||||
new Track() { Title = "Title", Artist = "Artist", Length = 42 },
|
new Track() { Title = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album },
|
||||||
};
|
};
|
||||||
|
|
||||||
var extractor = new Extractors.StringExtractor();
|
var extractor = new Extractors.StringExtractor();
|
||||||
|
|
Loading…
Reference in a new issue