mirror of
https://github.com/fiso64/slsk-batchdl.git
synced 2024-12-22 06:22: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
|
||||
|
||||
### 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
|
||||
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
|
||||
|
@ -492,13 +500,20 @@ sldl test.csv
|
|||
```
|
||||
<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>
|
||||
|
||||
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
|
||||
```
|
||||
|
@ -522,7 +537,7 @@ sldl "artist=MC MENTAL" --aggregate --skip-existing --music-dir "path/to/music"
|
|||
```
|
||||
<br>
|
||||
|
||||
Download all albums:
|
||||
Download all albums by an artist that are on soulseek:
|
||||
```
|
||||
sldl "artist=MC MENTAL" --aggregate --album
|
||||
```
|
||||
|
|
|
@ -225,10 +225,16 @@ static class Config
|
|||
|
||||
if (DoNotDownload)
|
||||
m3uOption = M3uOption.None;
|
||||
else if (!hasConfiguredM3uMode && inputType == InputType.String)
|
||||
m3uOption = M3uOption.None;
|
||||
else if (!hasConfiguredM3uMode && Program.trackLists != null && !aggregate &&!Program.trackLists.Flattened(true, false, true).Skip(1).Any())
|
||||
m3uOption = M3uOption.None;
|
||||
else if (!hasConfiguredM3uMode)
|
||||
{
|
||||
if (inputType == InputType.String)
|
||||
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);
|
||||
m3uFilePath = Utils.ExpandUser(m3uFilePath);
|
||||
|
|
|
@ -51,7 +51,10 @@ namespace Data
|
|||
|
||||
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()
|
||||
|
@ -103,7 +106,7 @@ namespace Data
|
|||
public bool needSkipExistingAfterSearch = false;
|
||||
public bool gotoNextAfterSearch = false;
|
||||
public bool placeInSubdir = false;
|
||||
public string? subdirOverride;
|
||||
public bool useRemoteDirname = false;
|
||||
|
||||
public TrackListEntry()
|
||||
{
|
||||
|
@ -138,12 +141,13 @@ namespace Data
|
|||
}
|
||||
|
||||
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.source = source;
|
||||
this.needSourceSearch = needSearch;
|
||||
this.placeInSubdir = placeInSubdir;
|
||||
this.useRemoteDirname = useRemoteDirname;
|
||||
this.sourceCanBeSkipped = canBeSkipped;
|
||||
this.needSkipExistingAfterSearch = needSkipExistingAfterSearch;
|
||||
this.gotoNextAfterSearch = gotoNextAfterSearch;
|
||||
|
@ -183,10 +187,9 @@ namespace Data
|
|||
res.AddTrackToLast(enumerator.Current);
|
||||
}
|
||||
|
||||
if (hasNext && enumerator.Current.Type != TrackType.Normal)
|
||||
res.AddEntry(new TrackListEntry(track));
|
||||
else if (!hasNext)
|
||||
break;
|
||||
if (hasNext)
|
||||
res.AddEntry(new TrackListEntry(enumerator.Current));
|
||||
else break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,11 +283,26 @@ namespace Data
|
|||
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)
|
||||
{
|
||||
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;
|
||||
if (!sourcesOnly && tle.list.Count > 0 && (tle.source.Type == TrackType.Normal || addSpecialSourceTracks))
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace Extractors
|
|||
|
||||
if (isArtist)
|
||||
{
|
||||
Console.WriteLine("Retrieving bandcamp artist discography..");
|
||||
string artistUrl = Config.input.TrimEnd('/');
|
||||
|
||||
if (!artistUrl.EndsWith("/music"))
|
||||
|
@ -60,13 +61,13 @@ namespace Extractors
|
|||
{
|
||||
source = t,
|
||||
placeInSubdir = true,
|
||||
subdirOverride = t.ToString(true)
|
||||
};
|
||||
trackLists.AddEntry(tle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Retrieving bandcamp item..");
|
||||
var web = new HtmlWeb();
|
||||
var doc = await web.LoadFromWebAsync(Config.input);
|
||||
|
||||
|
|
|
@ -17,25 +17,22 @@ namespace Extractors
|
|||
|
||||
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))
|
||||
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)
|
||||
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)
|
||||
{
|
||||
if (tle.source.Type != TrackType.Normal)
|
||||
{
|
||||
tle.placeInSubdir = true;
|
||||
tle.subdirOverride = tle.source.ToString(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,8 @@ namespace Extractors
|
|||
|
||||
Config.defaultFolderName = playlistName.ReplaceInvalidChars(Config.invalidReplaceStr);
|
||||
|
||||
trackLists.AddEntry(tle);
|
||||
|
||||
if (reverse)
|
||||
{
|
||||
trackLists.Reverse();
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Extractors
|
|||
var trackLists = new TrackLists();
|
||||
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;
|
||||
trackLists.AddEntry(new TrackListEntry(music));
|
||||
|
@ -35,7 +35,7 @@ namespace Extractors
|
|||
return trackLists;
|
||||
}
|
||||
|
||||
public Track ParseTrackArg(string input, bool isAlbum)
|
||||
static public Track ParseTrackArg(string input, bool isAlbum)
|
||||
{
|
||||
input = input.Trim();
|
||||
var track = new Track();
|
||||
|
|
|
@ -283,6 +283,14 @@ public static class Help
|
|||
const string searchHelp = @"
|
||||
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
|
||||
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
|
||||
|
|
|
@ -289,6 +289,7 @@ public class M3uEditor
|
|||
|
||||
public Track? PreviousRunResult(Track track)
|
||||
{
|
||||
var key = track.ToKey();
|
||||
previousRunData.TryGetValue(track.ToKey(), out var t);
|
||||
return t;
|
||||
}
|
||||
|
|
|
@ -68,10 +68,12 @@ static partial class Program
|
|||
|
||||
WriteLine("Got tracks", debugOnly: true);
|
||||
|
||||
Config.PostProcessArgs();
|
||||
|
||||
trackLists.UpgradeListTypes(Config.aggregate, Config.album);
|
||||
|
||||
trackLists.SetListEntryOptions();
|
||||
|
||||
Config.PostProcessArgs();
|
||||
|
||||
m3uEditor = new M3uEditor(Config.m3uFilePath, trackLists, Config.m3uOption, Config.offset);
|
||||
|
||||
InitExistingChecker();
|
||||
|
@ -262,7 +264,7 @@ static partial class Program
|
|||
foreach (var item in res)
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -919,11 +919,22 @@ static partial class Program
|
|||
|
||||
static string GetSearchString(Track track)
|
||||
{
|
||||
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();
|
||||
if (track.Type == TrackType.Album)
|
||||
{
|
||||
if (track.Album.Length > 0)
|
||||
return (track.Artist + " " + track.Album).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 Enums;
|
||||
using ExistingCheckers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using static Test.Helpers;
|
||||
|
||||
|
@ -238,17 +232,17 @@ namespace Test
|
|||
|
||||
var albums = new List<Track>()
|
||||
{
|
||||
new Track() { Album="Some Title" },
|
||||
new Track() { Album="Some, Title" },
|
||||
new Track() { Title = "some title", Artist = "Some artist" },
|
||||
new Track() { Album = "Title", Artist = "Artist", Length = 42 },
|
||||
new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42 },
|
||||
new Track() { Artist = "Some, Artist = a", Album = "Some, Album", Length = 42 },
|
||||
new Track() { Album="Some Title", Type = TrackType.Album },
|
||||
new Track() { Album="Some, Title", Type = TrackType.Album },
|
||||
new Track() { Title = "some title", Artist = "Some artist", Type = TrackType.Album },
|
||||
new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album },
|
||||
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, Type = TrackType.Album },
|
||||
|
||||
new Track() { Album = "Foo Bar" },
|
||||
new Track() { Album = "Bar", Artist = "Foo" },
|
||||
new Track() { Album = "Title", Artist = "Artist", Length = 42 },
|
||||
new Track() { Title = "Title", Artist = "Artist", Length = 42 },
|
||||
new Track() { Album = "Foo Bar", Type = TrackType.Album },
|
||||
new Track() { Album = "Bar", Artist = "Foo", Type = TrackType.Album },
|
||||
new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album },
|
||||
new Track() { Title = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album },
|
||||
};
|
||||
|
||||
var extractor = new Extractors.StringExtractor();
|
||||
|
|
Loading…
Reference in a new issue