1
0
Fork 0
mirror of https://github.com/fiso64/slsk-batchdl.git synced 2024-12-22 22:42:41 +00:00
This commit is contained in:
fiso64 2024-06-20 19:48:50 +02:00
parent 1e7564dd7e
commit 93fb18f221
4 changed files with 100 additions and 38 deletions

View file

@ -122,6 +122,8 @@ Options:
--name-format <format> Name format for downloaded tracks, e.g "{artist} - {title}" --name-format <format> Name format for downloaded tracks, e.g "{artist} - {title}"
--fast-search Begin downloading as soon as a file satisfying the preferred --fast-search Begin downloading as soon as a file satisfying the preferred
conditions is found. Higher chance to download wrong files. conditions is found. Higher chance to download wrong files.
--remove-from-source Remove downloaded tracks from source playlist or CSV file
(spotify and CSV only)
--m3u <option> Create an m3u8 playlist file --m3u <option> Create an m3u8 playlist file
'none': Do not create a playlist file 'none': Do not create a playlist file
'fails' (default): Write only failed downloads to the m3u 'fails' (default): Write only failed downloads to the m3u
@ -129,7 +131,6 @@ Options:
--spotify-id <id> spotify client ID --spotify-id <id> spotify client ID
--spotify-secret <secret> spotify client secret --spotify-secret <secret> spotify client secret
--remove-from-playlist Remove downloaded tracks from playlist (spotify only)
--youtube-key <key> Youtube data API key --youtube-key <key> Youtube data API key
--get-deleted Attempt to retrieve titles of deleted videos from wayback --get-deleted Attempt to retrieve titles of deleted videos from wayback

View file

@ -70,6 +70,7 @@ static class Program
static string spotifySecret = ""; static string spotifySecret = "";
static string ytKey = ""; static string ytKey = "";
static string csvPath = ""; static string csvPath = "";
static int csvColumnCount = -1;
static string username = ""; static string username = "";
static string password = ""; static string password = "";
static string artistCol = ""; static string artistCol = "";
@ -138,6 +139,7 @@ static class Program
static int listenPort = 50000; static int listenPort = 50000;
static object consoleLock = new object(); static object consoleLock = new object();
static object csvLock = new object();
static bool skipUpdate = false; static bool skipUpdate = false;
static bool debugDisableDownload = false; static bool debugDisableDownload = false;
@ -154,7 +156,17 @@ static class Program
private static M3UEditor? m3uEditor; private static M3UEditor? m3uEditor;
private static CancellationTokenSource? mainLoopCts; private static CancellationTokenSource? mainLoopCts;
static string inputType = ""; static InputType inputType = InputType.None;
public enum InputType
{
None,
Spotify,
YouTube,
Bandcamp,
String,
CSV
};
static void PrintHelp() static void PrintHelp()
{ {
@ -193,6 +205,8 @@ static class Program
"\n --name-format <format> Name format for downloaded tracks, e.g \"{artist} - {title}\"" + "\n --name-format <format> Name format for downloaded tracks, e.g \"{artist} - {title}\"" +
"\n --fast-search Begin downloading as soon as a file satisfying the preferred" + "\n --fast-search Begin downloading as soon as a file satisfying the preferred" +
"\n conditions is found. Higher chance to download wrong files." + "\n conditions is found. Higher chance to download wrong files." +
"\n --remove-from-source Remove downloaded tracks from source playlist or CSV file " +
"\n (spotify and CSV only)" +
"\n --m3u <option> Create an m3u8 playlist file" + "\n --m3u <option> Create an m3u8 playlist file" +
"\n 'none': Do not create a playlist file" + "\n 'none': Do not create a playlist file" +
"\n 'fails' (default): Write only failed downloads to the m3u" + "\n 'fails' (default): Write only failed downloads to the m3u" +
@ -200,7 +214,6 @@ static class Program
"\n" + "\n" +
"\n --spotify-id <id> spotify client ID" + "\n --spotify-id <id> spotify client ID" +
"\n --spotify-secret <secret> spotify client secret" + "\n --spotify-secret <secret> spotify client secret" +
"\n --remove-from-playlist Remove downloaded tracks from playlist (spotify only)" +
"\n" + "\n" +
"\n --youtube-key <key> Youtube data API key" + "\n --youtube-key <key> Youtube data API key" +
"\n --get-deleted Attempt to retrieve titles of deleted videos from wayback" + "\n --get-deleted Attempt to retrieve titles of deleted videos from wayback" +
@ -399,7 +412,16 @@ static class Program
break; break;
case "--it": case "--it":
case "--input-type": case "--input-type":
inputType = args[++i]; inputType = args[++i].ToLower().Trim() switch
{
"none" => InputType.None,
"csv" => InputType.CSV,
"youtube" => InputType.YouTube,
"spotify" => InputType.Spotify,
"bandcamp" => InputType.Bandcamp,
"string" => InputType.String,
_ => throw new ArgumentException($"Invalid input type '{args[i]}'"),
};
break; break;
case "-p": case "-p":
case "--path": case "--path":
@ -563,6 +585,8 @@ static class Program
skipNotFound = true; skipNotFound = true;
break; break;
case "--rfp": case "--rfp":
case "--rfs":
case "--remove-from-source":
case "--remove-from-playlist": case "--remove-from-playlist":
removeTracksFromSource = true; removeTracksFromSource = true;
break; break;
@ -925,7 +949,7 @@ static class Program
else else
{ {
if (input == "") if (input == "")
input = args[i]; input = args[i].Trim();
else else
throw new ArgumentException($"Invalid argument \'{args[i]}\'. Input is already set to \'{input}\'"); throw new ArgumentException($"Invalid argument \'{args[i]}\'. Input is already set to \'{input}\'");
} }
@ -933,8 +957,6 @@ static class Program
if (input == "") if (input == "")
throw new ArgumentException($"No input provided"); throw new ArgumentException($"No input provided");
if (!(new string[] { "", "youtube", "spotify", "csv", "string", "bandcamp" }).Contains(inputType))
throw new ArgumentException($"Invalid input type '{inputType}'");
if (ytKey != "") if (ytKey != "")
YouTube.apiKey = ytKey; YouTube.apiKey = ytKey;
@ -944,22 +966,22 @@ static class Program
ignoreOn = Math.Min(ignoreOn, deprioritizeOn); ignoreOn = Math.Min(ignoreOn, deprioritizeOn);
if (inputType == "youtube" || (inputType == "" && input.StartsWith("http") && input.Contains("youtu"))) if (inputType == InputType.YouTube || (inputType == InputType.None && input.StartsWith("http") && input.Contains("youtu")))
{ {
WriteLine("Youtube download", debugOnly: true); WriteLine("Youtube download", debugOnly: true);
await YoutubeInput(); await YoutubeInput();
} }
else if (inputType == "spotify" || (inputType == "" && (input.StartsWith("http") && input.Contains("spotify")) || input == "spotify-likes")) else if (inputType == InputType.Spotify || (inputType == InputType.None && (input.StartsWith("http") && input.Contains("spotify")) || input == "spotify-likes"))
{ {
WriteLine("Spotify download", debugOnly: true); WriteLine("Spotify download", debugOnly: true);
await SpotifyInput(); await SpotifyInput();
} }
else if (inputType == "bandcamp" || (inputType == "" && input.StartsWith("http") && input.Contains("bandcamp"))) else if (inputType == InputType.Bandcamp || (inputType == InputType.None && input.StartsWith("http") && input.Contains("bandcamp")))
{ {
WriteLine("Bandcamp download", debugOnly: true); WriteLine("Bandcamp download", debugOnly: true);
await BandcampInput(); await BandcampInput();
} }
else if (inputType == "csv" || (inputType == "" && Path.GetExtension(input).Equals(".csv", StringComparison.OrdinalIgnoreCase))) else if (inputType == InputType.CSV || (inputType == InputType.None && input.EndsWith(".csv", StringComparison.OrdinalIgnoreCase)))
{ {
WriteLine("CSV download", debugOnly: true); WriteLine("CSV download", debugOnly: true);
await CsvInput(); await CsvInput();
@ -1021,7 +1043,7 @@ static class Program
int max = reverse ? int.MaxValue : maxTracks; int max = reverse ? int.MaxValue : maxTracks;
int off = reverse ? 0 : offset; int off = reverse ? 0 : offset;
ytUrl = input; ytUrl = input;
inputType = "youtube"; inputType = InputType.YouTube;
string name; string name;
List<Track>? deleted = null; List<Track>? deleted = null;
@ -1071,10 +1093,9 @@ static class Program
int off = reverse ? 0 : offset; int off = reverse ? 0 : offset;
spotifyUrl = input; spotifyUrl = input;
inputType = "spotify"; inputType = InputType.Spotify;
string? playlistName; string? playlistName;
bool usedDefaultId = false;
bool needLogin = spotifyUrl == "spotify-likes" || removeTracksFromSource; bool needLogin = spotifyUrl == "spotify-likes" || removeTracksFromSource;
List<Track> tracks; List<Track> tracks;
@ -1129,16 +1150,20 @@ static class Program
} }
catch (SpotifyAPI.Web.APIException) catch (SpotifyAPI.Web.APIException)
{ {
if (!needLogin) if (!needLogin && !spotifyClient.UsedDefaultCredentials)
{
await spotifyClient.Authorize(true, removeTracksFromSource);
(playlistName, playlistUri, tracks) = await spotifyClient.GetPlaylist(spotifyUrl, max, off);
}
else if (!needLogin)
{ {
Console.WriteLine("Spotify playlist not found. It may be set to private. Login? [Y/n]"); Console.WriteLine("Spotify playlist not found. It may be set to private. Login? [Y/n]");
string answer = Console.ReadLine(); if (Console.ReadLine()?.ToLower().Trim() == "y")
if (answer.ToLower() == "y")
{ {
if (usedDefaultId)
readSpotifyCreds(); readSpotifyCreds();
await spotifyClient.Authorize(true); spotifyClient = new Spotify(spotifyId, spotifySecret);
Console.WriteLine("Loading Spotify tracks"); await spotifyClient.Authorize(true, removeTracksFromSource);
Console.WriteLine("Loading Spotify playlist");
(playlistName, playlistUri, tracks) = await spotifyClient.GetPlaylist(spotifyUrl, max, off); (playlistName, playlistUri, tracks) = await spotifyClient.GetPlaylist(spotifyUrl, max, off);
} }
else else
@ -1162,7 +1187,7 @@ static class Program
static async Task BandcampInput() static async Task BandcampInput()
{ {
inputType = "bandcamp"; inputType = InputType.Bandcamp;
bool isAlbum = !input.Contains("/track/"); bool isAlbum = !input.Contains("/track/");
var web = new HtmlWeb(); var web = new HtmlWeb();
@ -1212,7 +1237,7 @@ static class Program
int off = reverse ? 0 : offset; int off = reverse ? 0 : offset;
csvPath = input; csvPath = input;
inputType = "csv"; inputType = InputType.CSV;
if (!File.Exists(csvPath)) if (!File.Exists(csvPath))
throw new FileNotFoundException("CSV file not found"); throw new FileNotFoundException("CSV file not found");
@ -1227,7 +1252,7 @@ static class Program
static async Task StringInput() static async Task StringInput()
{ {
searchStr = input; searchStr = input;
inputType = "string"; inputType = InputType.String;
var music = ParseTrackArg(searchStr, album); var music = ParseTrackArg(searchStr, album);
bool isAlbum = false; bool isAlbum = false;
@ -1557,19 +1582,21 @@ static class Program
} }
if (savedFilePath != "") if (savedFilePath != "")
{
try
{ {
lock (trackLists) { tracks[index] = new Track(track) { TrackState = Track.State.Downloaded, DownloadPath = savedFilePath }; } lock (trackLists) { tracks[index] = new Track(track) { TrackState = Track.State.Downloaded, DownloadPath = savedFilePath }; }
if (removeTracksFromSource && !string.IsNullOrEmpty(spotifyUrl)) if (removeTracksFromSource)
spotifyClient.RemoveTrackFromPlaylist(playlistUri, track.URI); {
try
{
await RemoveTrackFromSource(track);
} }
catch (Exception ex) catch (Exception ex)
{ {
WriteLine($"\n{ex.Message}\n{ex.StackTrace}\n", ConsoleColor.DarkYellow, true); WriteLine($"\n{ex.Message}\n{ex.StackTrace}\n", ConsoleColor.DarkYellow, true);
} }
} }
}
m3uEditor.Update(); m3uEditor.Update();
@ -1911,6 +1938,28 @@ static class Program
} }
static async Task RemoveTrackFromSource(Track track)
{
if (inputType == InputType.Spotify && track.URI != "")
{
await spotifyClient.RemoveTrackFromPlaylist(playlistUri, track.URI);
}
else if (inputType == InputType.CSV && track.CsvRow != -1)
{
lock (csvLock)
{
string[] lines = File.ReadAllLines(csvPath, System.Text.Encoding.UTF8);
if (lines.Length > track.CsvRow)
{
lines[track.CsvRow] = new string(',', Math.Max(0, csvColumnCount - 1));
File.WriteAllLines(csvPath, lines, System.Text.Encoding.UTF8);
}
}
}
}
static async Task Login(bool random = false, int tries = 3) static async Task Login(bool random = false, int tries = 3)
{ {
string user = username, pass = password; string user = username, pass = password;
@ -3435,9 +3484,13 @@ static class Program
using var sr = new StreamReader(path, System.Text.Encoding.UTF8); using var sr = new StreamReader(path, System.Text.Encoding.UTF8);
var parser = new SmallestCSV.SmallestCSVParser(sr); var parser = new SmallestCSV.SmallestCSVParser(sr);
int index = 0;
var header = parser.ReadNextRow(); var header = parser.ReadNextRow();
while (header == null || header.Count == 0 || !header.Any(t => t.Trim() != "")) while (header == null || header.Count == 0 || !header.Any(t => t.Trim() != ""))
{
index++;
header = parser.ReadNextRow(); header = parser.ReadNextRow();
}
string[] cols = { artistCol, albumCol, trackCol, lengthCol, descCol, ytIdCol, trackCountCol }; string[] cols = { artistCol, albumCol, trackCol, lengthCol, descCol, ytIdCol, trackCountCol };
string[][] aliases = { string[][] aliases = {
@ -3482,6 +3535,7 @@ static class Program
while (true) while (true)
{ {
index++;
var values = parser.ReadNextRow(); var values = parser.ReadNextRow();
if (values == null) if (values == null)
break; break;
@ -3490,9 +3544,12 @@ static class Program
while (values.Count < foundCount) while (values.Count < foundCount)
values.Add(""); values.Add("");
var desc = ""; if (csvColumnCount == -1)
csvColumnCount = values.Count;
var desc = "";
var track = new Track() { CsvRow = index };
var track = new Track();
if (artistIndex >= 0) track.Artist = values[artistIndex]; if (artistIndex >= 0) track.Artist = values[artistIndex];
if (trackIndex >= 0) track.Title = values[trackIndex]; if (trackIndex >= 0) track.Title = values[trackIndex];
if (albumIndex >= 0) track.Album = values[albumIndex]; if (albumIndex >= 0) track.Album = values[albumIndex];
@ -4572,6 +4629,7 @@ public struct Track
public string FailureReason = ""; public string FailureReason = "";
public string DownloadPath = ""; public string DownloadPath = "";
public string Other = ""; public string Other = "";
public int CsvRow = -1;
public State TrackState = State.Initial; public State TrackState = State.Initial;
public SlDictionary? Downloads = null; public SlDictionary? Downloads = null;
@ -4604,6 +4662,7 @@ public struct Track
Other = other.Other; Other = other.Other;
MinAlbumTrackCount = other.MinAlbumTrackCount; MinAlbumTrackCount = other.MinAlbumTrackCount;
MaxAlbumTrackCount = other.MaxAlbumTrackCount; MaxAlbumTrackCount = other.MaxAlbumTrackCount;
CsvRow = other.CsvRow;
} }
public override readonly string ToString() public override readonly string ToString()

View file

@ -7,7 +7,7 @@ public class Spotify
private EmbedIOAuthServer _server; private EmbedIOAuthServer _server;
private readonly string _clientId; private readonly string _clientId;
private readonly string _clientSecret; private readonly string _clientSecret;
private SpotifyClient _client; private SpotifyClient? _client;
private bool loggedIn = false; private bool loggedIn = false;
// default spotify credentials (base64-encoded to keep the bots away) // default spotify credentials (base64-encoded to keep the bots away)
@ -15,7 +15,7 @@ public class Spotify
public const string encodedSpotifySecret = "Y2JlM2QxYTE5MzJkNDQ2MmFiOGUy3shTuf4Y2JhY2M3ZDdjYWU="; public const string encodedSpotifySecret = "Y2JlM2QxYTE5MzJkNDQ2MmFiOGUy3shTuf4Y2JhY2M3ZDdjYWU=";
public bool UsedDefaultCredentials { get; private set; } public bool UsedDefaultCredentials { get; private set; }
public Spotify(string clientId, string clientSecret) public Spotify(string clientId="", string clientSecret="")
{ {
_clientId = clientId; _clientId = clientId;
_clientSecret = clientSecret; _clientSecret = clientSecret;
@ -30,6 +30,8 @@ public class Spotify
public async Task Authorize(bool login = false, bool needModify = false) public async Task Authorize(bool login = false, bool needModify = false)
{ {
_client = null;
if (!login) if (!login)
{ {
var config = SpotifyClientConfig.CreateDefault(); var config = SpotifyClientConfig.CreateDefault();

View file

@ -24,8 +24,8 @@
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" /> <PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
<PackageReference Include="SmallestCSVParser" Version="1.1.1" /> <PackageReference Include="SmallestCSVParser" Version="1.1.1" />
<PackageReference Include="Soulseek" Version="6.4.1" /> <PackageReference Include="Soulseek" Version="6.4.1" />
<PackageReference Include="SpotifyAPI.Web" Version="7.0.2" /> <PackageReference Include="SpotifyAPI.Web" Version="7.1.1" />
<PackageReference Include="SpotifyAPI.Web.Auth" Version="7.0.2" /> <PackageReference Include="SpotifyAPI.Web.Auth" Version="7.1.1" />
<PackageReference Include="TagLibSharp" Version="2.3.0" /> <PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="YoutubeExplode" Version="6.3.16" /> <PackageReference Include="YoutubeExplode" Version="6.3.16" />
</ItemGroup> </ItemGroup>