Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for downloading Spotify songs #293

Merged
merged 31 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2e7c7d0
feat: Added support for downloading Spotify songs
sinchana06 Oct 1, 2023
8921399
Merge branch 'master' of https://github.com/sinchana06/Drifty
sinchana06 Oct 1, 2023
2aab895
fix: Changed executable name for Spotify in linux and merged copy exe…
SaptarshiSarkar12 Oct 1, 2023
7c03704
Merge branch 'master' into master
SaptarshiSarkar12 Oct 2, 2023
a3bb586
Merge branch 'master' into master
SaptarshiSarkar12 Oct 2, 2023
417a65a
Merge branch 'master' into master
SaptarshiSarkar12 Oct 4, 2023
af19d0e
Merge branch 'master' into master
SaptarshiSarkar12 Oct 4, 2023
b4ef39f
Merge branch 'master' into master
SaptarshiSarkar12 Oct 4, 2023
e25fdc3
Merge branch 'master' into master
SaptarshiSarkar12 Oct 5, 2023
0de1b1b
Merge branch 'master' into master
SaptarshiSarkar12 Oct 5, 2023
2f4faf2
Merge branch 'master' into master
SaptarshiSarkar12 Oct 5, 2023
f23e3d7
Merge branch 'master' into master
SaptarshiSarkar12 Oct 5, 2023
ccbc28e
Merge branch 'master' into master
SaptarshiSarkar12 Oct 7, 2023
4f0b0d9
Merge branch 'master' into master
SaptarshiSarkar12 Oct 8, 2023
1dad8b1
Merge branch 'master' into master
SaptarshiSarkar12 Oct 9, 2023
5185e4e
Merge branch 'master' into master
SaptarshiSarkar12 Oct 9, 2023
7fe2680
Merge branch 'master' into master
SaptarshiSarkar12 Oct 9, 2023
cff4e84
Merge branch 'master' into master
SaptarshiSarkar12 Oct 9, 2023
9926451
Merge branch 'master' into master
SaptarshiSarkar12 Oct 12, 2023
7ad4ca8
Merge branch 'master' into master
SaptarshiSarkar12 Oct 14, 2023
83c5d13
Merge branch 'master' into master
SaptarshiSarkar12 Oct 17, 2023
1889e92
Merge branch 'master' into master
SaptarshiSarkar12 Oct 18, 2023
bbd195b
Merge branch 'master' into master
SaptarshiSarkar12 Oct 18, 2023
8c30195
fix: fixed missing variable in Environment class
SaptarshiSarkar12 Oct 18, 2023
7331d65
Merge branch 'SaptarshiSarkar12:master' into master
sinchana06 Oct 20, 2023
2d78936
Added progress bar
sinchana06 Oct 20, 2023
7487c73
Removed idea files
sinchana06 Oct 20, 2023
e2be0f9
added changes to update progress bar
sinchana06 Oct 20, 2023
16a877b
Merge branch 'master' of https://github.com/sinchana06/Drifty
sinchana06 Oct 20, 2023
86799c1
fix: Fixed merge conflicts and broken support for Spotify filename de…
SaptarshiSarkar12 Oct 21, 2023
56bca49
chore: Added spotdl executables to resource config
SaptarshiSarkar12 Oct 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions src/main/java/Backend/CopySpotdl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package Backend;
import Enums.Program;
import Utils.Environment;
import Utils.MessageBroker;
import org.apache.commons.io.FileUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;

public class CopySpotdl {
private static final MessageBroker M = Environment.getMessageBroker();

public boolean copySpotdl(InputStream inputStream) throws IOException {
Path spotdlPath = Program.getSpotdlFullPath();

if (!Files.exists(spotdlPath)) {
if (!spotdlPath.toFile().getParentFile().exists()) {
FileUtils.createParentDirectories(spotdlPath.toFile());
}
try (OutputStream outputStream = Files.newOutputStream(spotdlPath)) {
if (inputStream != null) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
if (!Files.isExecutable(spotdlPath)) {
// Adjust this command for making the file executable on your OS
ProcessBuilder makeExecutable = new ProcessBuilder("chmod", "+x", spotdlPath.toString());
makeExecutable.inheritIO();
Process spotdlProcess = makeExecutable.start();
spotdlProcess.waitFor();
}
} catch (FileAlreadyExistsException e) {
M.msgLogWarning("spotdl not copied to " + Program.get(Program.DRIFTY_PATH) + " because it already exists!");
} catch (InterruptedException e) {
M.msgLogWarning("Failed to make spotdl executable: " + e.getMessage());
} catch (IOException e) {
M.msgInitError("Failed to copy spotdl executable: " + e.getMessage());
}
}
return Files.exists(spotdlPath);
}
}
49 changes: 45 additions & 4 deletions src/main/java/Backend/FileDownloader.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;
import java.util.Objects;

import static Enums.Program.SPOTDL;
import static Enums.Program.YT_DLP;
import static Utils.DriftyConstants.*;
import static Utils.Utility.*;
Expand Down Expand Up @@ -185,21 +186,27 @@ public void run() {
}
boolean isYouTubeLink = isYoutube(link);
boolean isInstagramLink = isInstagram(link);
boolean isSpotifyLink = isSpotify(link);
try {
// If the link is of a YouTube or Instagram video, then the following block of code will execute.
if (isYouTubeLink || isInstagramLink) {
if (isYouTubeLink || isInstagramLink || isSpotifyLink) {
try {
if (isYouTubeLink) {
downloadFromYouTube();
} else {
} else if(isSpotifyLink){
downloadFromSpotify();
}
else{
downloadFromInstagram();
}
} catch (InterruptedException e) {
M.msgDownloadError(USER_INTERRUPTION);
} catch (Exception e) {
if (isYouTubeLink) {
M.msgDownloadError(YOUTUBE_DOWNLOAD_FAILED);
} else {
}else if(isSpotifyLink){
M.msgDownloadError(SPOTIFY_DOWNLOAD_FAILED);
}else {
M.msgDownloadError(INSTAGRAM_DOWNLOAD_FAILED);
}
String msg = e.getMessage();
Expand Down Expand Up @@ -257,4 +264,38 @@ private void downloadFromInstagram() throws InterruptedException, IOException {
M.msgDownloadError(String.format(FAILED_TO_DOWNLOAD_F, fileDownloadMessage));
}
}
}

public void downloadFromSpotify() throws InterruptedException, IOException{
String outputFileName = Objects.requireNonNullElse(fileName, DEFAULT_FILENAME);
String fileDownloadMessage;
if (outputFileName.equals(DEFAULT_FILENAME)) {
fileDownloadMessage = "the Spotify File";
} else {
fileDownloadMessage = outputFileName;
}
M.msgDownloadInfo("Trying to download \"" + fileDownloadMessage + "\" ...");

String[] fullCommand = new String[]{Program.getSpotdl(SPOTDL), "download", link};
ProcessBuilder processBuilder = new ProcessBuilder(fullCommand);
processBuilder.inheritIO();

M.msgDownloadInfo(String.format(DOWNLOADING_F, fileDownloadMessage));
int exitValueOfSpotdl = -1;
try {
Process spotdlProcess = processBuilder.start();
spotdlProcess.waitFor();
exitValueOfSpotdl = spotdlProcess.exitValue();
} catch (IOException e) {
M.msgDownloadError("An I/O error occurred while initializing Spotify downloader! " + e.getMessage());
} catch (InterruptedException e) {
M.msgDownloadError("The Spotify download process was interrupted by the user! " + e.getMessage());
}

if (exitValueOfSpotdl == 0) {
M.msgDownloadInfo(String.format(SUCCESSFULLY_DOWNLOADED_F, fileDownloadMessage));
} else {
M.msgDownloadError(String.format(FAILED_TO_DOWNLOAD_F, fileDownloadMessage));
}
}

}
6 changes: 5 additions & 1 deletion src/main/java/Enums/LinkType.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Utils.Utility;

public enum LinkType {
YOUTUBE, INSTAGRAM, OTHER;
YOUTUBE, INSTAGRAM, SPOTIFY, OTHER;

public static LinkType getLinkType(String link) {
if (Utility.isYoutube(link)) {
Expand All @@ -12,6 +12,9 @@ public static LinkType getLinkType(String link) {
else if (Utility.isInstagram(link)) {
return INSTAGRAM;
}
else if (Utility.isSpotify(link)) {
return SPOTIFY;
}
else {
return OTHER;
}
Expand All @@ -21,6 +24,7 @@ public String get() {
return switch(this) {
case YOUTUBE -> "YouTube";
case INSTAGRAM -> "Instagram";
case SPOTIFY -> "Spotify";
case OTHER -> "Other";
};
}
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/Enums/Program.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
import java.nio.file.Path;
import java.nio.file.Paths;
public enum Program {
EXECUTABLE_NAME, YT_DLP, DRIFTY_PATH, JOB_HISTORY_FILE, JOB_FILE;
EXECUTABLE_NAME, YT_DLP, DRIFTY_PATH, JOB_HISTORY_FILE, JOB_FILE, SPOTDL;

private static String ytDLP;
private static String driftyPath;
private static String spotdl;

public static void setExecutableName(String name) {
Program.ytDLP = name;
}
public static void setSpotdlExecutableName(String name) {
Program.spotdl = name;
}

public static void setDriftyPath(String path) { Program.driftyPath = path;}

Expand All @@ -21,13 +25,29 @@ public static String get(Program program) {
case YT_DLP -> Paths.get(driftyPath, ytDLP).toAbsolutePath().toString();
case JOB_HISTORY_FILE -> Paths.get(driftyPath, "JobHistory.json").toAbsolutePath().toString();
case JOB_FILE -> Paths.get(driftyPath, "Jobs.json").toAbsolutePath().toString();
case SPOTDL -> Paths.get(driftyPath, spotdl).toAbsolutePath().toString();
};
}
public static String getSpotdl(Program program) {
return switch (program) {
case EXECUTABLE_NAME -> spotdl;
case DRIFTY_PATH -> driftyPath;
case YT_DLP -> Paths.get(driftyPath, ytDLP).toAbsolutePath().toString();
case JOB_HISTORY_FILE -> Paths.get(driftyPath, "JobHistory.json").toAbsolutePath().toString();
case JOB_FILE -> Paths.get(driftyPath, "Jobs.json").toAbsolutePath().toString();
case SPOTDL -> Paths.get(driftyPath, spotdl).toAbsolutePath().toString();
};
}

public static Path getYtDLPFullPath() {
return Paths.get(driftyPath, ytDLP);
}

public static Path getSpotdlFullPath(){ return Paths.get(driftyPath,spotdl); }
public static Path getJsonDataPath() {
return Paths.get(driftyPath, "JsonData");
}
public static Path getSpotdlDataPath() {
return Paths.get(driftyPath, "JsonData/metafile.spotdl");
}
}
40 changes: 40 additions & 0 deletions src/main/java/GUI/Forms/DownloadFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static Enums.Program.SPOTDL;
import static Utils.DriftyConstants.*;

public class DownloadFile extends Task<Integer> {
private static final MessageBroker M = Environment.getMessageBroker();
private final String YT_DLP = Program.get(Program.YT_DLP);
private final String SPOTDL = Program.get(Program.SPOTDL);
private final StringProperty progressProperty = new SimpleStringProperty();
private final String link;
private final String filename;
Expand Down Expand Up @@ -71,6 +73,7 @@ protected Integer call() {
sendInfoMessage(String.format(TRYING_TO_DOWNLOAD_F, filename));
switch (type) {
case YOUTUBE, INSTAGRAM -> downloadYoutubeOrInstagram();
case SPOTIFY -> downloadSpotify();
case OTHER -> splitDecision();
}
updateProgress(0.0, 1.0);
Expand Down Expand Up @@ -115,6 +118,43 @@ private void downloadYoutubeOrInstagram() {
}
sendFinalMessage("");
}
private void downloadSpotify() {
String[] fullCommand = new String[]{SPOTDL, "--output", "{title}", link};
ProcessBuilder processBuilder = new ProcessBuilder(fullCommand);
sendInfoMessage(String.format(DOWNLOADING_F, filename));
Process process = null;
try {
process = processBuilder.start();
} catch (IOException e) {
M.msgDownloadError("Failed to start download process for \"" + filename + "\"");
} catch (Exception e) {
String msg = e.getMessage();
String[] messageArray = msg.split(",");
if (messageArray.length >= 1 && messageArray[0].toLowerCase().trim().replaceAll(" ", "").contains("cannotrunprogram")) {
M.msgDownloadError(DRIFTY_COMPONENT_NOT_EXECUTABLE);
} else if (messageArray.length >= 1 && messageArray[1].toLowerCase().trim().replaceAll(" ", "").equals("permissiondenied")) {
M.msgDownloadError(PERMISSION_DENIED);
} else if (messageArray[0].toLowerCase().trim().replaceAll(" ", "").equals("videounavailable")) {
M.msgDownloadError(VIDEO_UNAVAILABLE);
} else {
M.msgDownloadError("An Unknown Error occurred! " + e.getMessage());
}
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(process).getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
progressProperty.setValue(line);
}
} catch (IOException e) {
M.msgDownloadError("Failed to read download process status for \"" + filename + "\"");
}
try {
exitCode = Objects.requireNonNull(process).waitFor();
} catch (InterruptedException e) {
M.msgDownloadError("Failed to wait for download process to finish for \"" + filename + "\"");
}
sendFinalMessage("");
}

private void splitDecision() {
long fileSize;
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/GUI/Forms/GetFilename.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.regex.Pattern;

import static Enums.Program.YT_DLP;
import static Utils.Utility.isSpotify;

public class GetFilename extends Task<ConcurrentLinkedDeque<Job>> {
private final String link;
Expand Down Expand Up @@ -69,6 +70,9 @@ protected ConcurrentLinkedDeque<Job> call() {
jobList.clear();
for (String json : jsonList) {
String filename = Utility.getFilenameFromJson(json);
if(isSpotify(link)){
filename = Utility.extractSpotdlFilename(json);
}
String fileLink = Utility.getURLFromJson(json);
String baseName = FilenameUtils.getBaseName(filename);
filename = baseName + ".mp4";
Expand All @@ -94,9 +98,12 @@ public void run() {
for (File file : files) {
try {
String ext = FilenameUtils.getExtension(file.getAbsolutePath());
if (ext.equalsIgnoreCase("json")) {
if (ext.equalsIgnoreCase("json")||ext.equalsIgnoreCase("spotdl")) {
String jsonString = FileUtils.readFileToString(file, Charset.defaultCharset());
String filename = Utility.getFilenameFromJson(jsonString);
if(isSpotify(link)){
filename = Utility.extractSpotdlFilename(jsonString);
}
String baseName = FilenameUtils.getBaseName(filename);
filename = baseName + ".mp4";
updateMessage("Found file: " + filename);
Expand All @@ -117,7 +124,8 @@ public void run() {
if (file.exists()) {
FileUtils.forceDelete(file);
}
} catch (IOException ignored) {}
} catch (IOException ignored) {
}
}
}
};
Expand Down Expand Up @@ -202,4 +210,4 @@ private void sleep(long time) {
throw new RuntimeException(e);
}
}
}
}
1 change: 1 addition & 0 deletions src/main/java/Utils/DriftyConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private DriftyConstants() {}
public static final String FAILED_READING_STREAM = "Failed to get I/O operations channel to read from the data stream !";
public static final String YOUTUBE_DOWNLOAD_FAILED = "Failed to download YouTube video!";
public static final String INSTAGRAM_DOWNLOAD_FAILED = "Failed to download Instagram video!";
public static final String SPOTIFY_DOWNLOAD_FAILED = "Failed to download Spotify audio!";
public static final String VIDEO_UNAVAILABLE = "The requested video is unavailable, it has been deleted from the platform.";
public static final String PERMISSION_DENIED = "You do not have access to download the video, permission is denied.";
public static final String DRIFTY_COMPONENT_NOT_EXECUTABLE = "A Drifty component (yt-dlp) is not marked as executable.";
Expand Down
11 changes: 9 additions & 2 deletions src/main/java/Utils/Environment.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package Utils;

import Backend.CopyYtDLP;
import Backend.CopySpotdl;
import Enums.OS;
import Enums.Program;
import Preferences.AppSettings;
Expand All @@ -11,6 +12,7 @@
import java.nio.file.Files;
import java.nio.file.Paths;

import static Enums.Program.SPOTDL;
import static Enums.Program.YT_DLP;

public class Environment {
Expand All @@ -25,16 +27,23 @@ public class Environment {
public static void initializeEnvironment() {
M.msgLogInfo("OS : " + OS.getOSName());
String ytDLP = OS.isWindows() ? "yt-dlp.exe" : OS.isMac() ? "yt-dlp_macos" : "yt-dlp";
String spotdl = OS.isWindows() ? "spotdl_win.exe" : OS.isMac() ? "spotdl_macos" :"spotdl";
String appUseFolderPath = OS.isWindows() ? Paths.get(System.getenv("LOCALAPPDATA"), "Drifty").toAbsolutePath().toString() : Paths.get(System.getProperty("user.home"), ".config", "Drifty").toAbsolutePath().toString();
Program.setExecutableName(ytDLP);
Program.setSpotdlExecutableName(spotdl);
Program.setDriftyPath(appUseFolderPath);
InputStream ytDLPStream = ClassLoader.getSystemResourceAsStream(ytDLP);
InputStream spotdlStream = ClassLoader.getSystemResourceAsStream(spotdl);
CopyYtDLP copyYtDlp = new CopyYtDLP();
CopySpotdl copySpotdl = new CopySpotdl();
boolean ytDLPExists = false;
boolean spotdlExists = false;
try {
ytDLPExists = copyYtDlp.copyYtDLP(ytDLPStream);
spotdlExists = copySpotdl.copySpotdl(spotdlStream);
} catch (IOException e) {
M.msgInitError("Failed to copy yt-dlp! " + e.getMessage());
M.msgInitError("Failed to copy spotdl! " + e.getMessage());
}
if (ytDLPExists && isYtDLPUpdated()) {
updateYt_dlp();
Expand Down Expand Up @@ -71,13 +80,11 @@ public static void updateYt_dlp() {
M.msgInitError("Component (yt-dlp) update process was interrupted! " + e.getMessage());
}
}

public static boolean isYtDLPUpdated() {
final long oneDay = 1000 * 60 * 60 * 24; // Value of one day (24 Hours) in milliseconds
long timeSinceLastUpdate = System.currentTimeMillis() - AppSettings.get.lastDLPUpdateTime();
return timeSinceLastUpdate >= oneDay;
}

public static MessageBroker getMessageBroker() {
return M;
}
Expand Down
Loading