Try multiple manual download formats before falling back to auto, and display the bitrate and download size to the user.
This commit is contained in:
parent
83bb9e5805
commit
2bd51ea856
|
@ -36,6 +36,7 @@ public class Downloader implements Consumer<Song>
|
||||||
|
|
||||||
private static final int BITRATE_TARGET = 64_000;
|
private static final int BITRATE_TARGET = 64_000;
|
||||||
private static final Pattern FORMAT_PATTERN = Pattern.compile("^([\\w]+)\\s+([\\w]+)\\s+(\\w+ ?\\w*)\\s+(.*)$");
|
private static final Pattern FORMAT_PATTERN = Pattern.compile("^([\\w]+)\\s+([\\w]+)\\s+(\\w+ ?\\w*)\\s+(.*)$");
|
||||||
|
public static final Pattern DESTINATION_PATTERN = Pattern.compile("Destination: (.*\\.wav)");
|
||||||
|
|
||||||
private final List<Song> downloadQueue = new LinkedList<>();
|
private final List<Song> downloadQueue = new LinkedList<>();
|
||||||
private final LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
|
private final LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
|
||||||
|
@ -62,9 +63,8 @@ public class Downloader implements Consumer<Song>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<Format> formats = getFormats(song);
|
getFormats(song);
|
||||||
Format chosenFormat = chooseFormat(formats);
|
download(song);
|
||||||
download(song, chosenFormat);
|
|
||||||
} catch (Exception ex)
|
} catch (Exception ex)
|
||||||
{
|
{
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
|
@ -78,19 +78,20 @@ public class Downloader implements Consumer<Song>
|
||||||
this.next = next;
|
this.next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Format chooseFormat(List<Format> formats)
|
private void chooseFormats(Song song)
|
||||||
{
|
{
|
||||||
|
List<Format> formats = song.getFormats();
|
||||||
if (formats.isEmpty())
|
if (formats.isEmpty())
|
||||||
return null;
|
return;
|
||||||
System.out.println("Choosing from " + formats.size() + " formats:");
|
// System.out.println("Choosing from " + formats.size() + " formats:");
|
||||||
System.out.println(formats);
|
// System.out.println(formats);
|
||||||
formats.sort((Format a, Format b) ->
|
formats.sort((Format a, Format b) ->
|
||||||
{
|
{
|
||||||
// audio only preferred to video
|
// audio only preferred to video
|
||||||
System.out.println("sort entered; a=" + a.toString() + " b=" + b.toString());
|
// System.out.println("sort entered; a=" + a.toString() + " b=" + b.toString());
|
||||||
int comp = 0;
|
int comp = 0;
|
||||||
comp = Boolean.compare(a.isAudioOnly(), b.isAudioOnly());
|
comp = Boolean.compare(a.isAudioOnly(), b.isAudioOnly());
|
||||||
System.out.println("\tCompared on audio only: " + comp);
|
// System.out.println("\tCompared on audio only: " + comp);
|
||||||
if (comp == 0)
|
if (comp == 0)
|
||||||
{
|
{
|
||||||
// known preferred to unknown
|
// known preferred to unknown
|
||||||
|
@ -105,7 +106,7 @@ public class Downloader implements Consumer<Song>
|
||||||
int aDist = Math.abs(a.getBitrate() - BITRATE_TARGET);
|
int aDist = Math.abs(a.getBitrate() - BITRATE_TARGET);
|
||||||
int bDist = Math.abs(b.getBitrate() - BITRATE_TARGET);
|
int bDist = Math.abs(b.getBitrate() - BITRATE_TARGET);
|
||||||
comp = Integer.compare(bDist, aDist);
|
comp = Integer.compare(bDist, aDist);
|
||||||
System.out.println("\tCompared on bitrate distance: " + comp);
|
// System.out.println("\tCompared on bitrate distance: " + comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (comp == 0)
|
if (comp == 0)
|
||||||
|
@ -120,17 +121,17 @@ public class Downloader implements Consumer<Song>
|
||||||
else // smaller is better
|
else // smaller is better
|
||||||
{
|
{
|
||||||
comp = Long.compare(b.getSize(), a.getSize());
|
comp = Long.compare(b.getSize(), a.getSize());
|
||||||
System.out.println("\tCompared on filesize: " + comp);
|
// System.out.println("\tCompared on filesize: " + comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println("\tOverall: " + comp);
|
// System.out.println("\tOverall: " + comp);
|
||||||
return comp;
|
return -comp;
|
||||||
});
|
});
|
||||||
System.out.println("Sorting done! Formats:" + formats);
|
song.setFormats(formats);
|
||||||
return formats.get(formats.size() - 1);
|
// System.out.println("Sorting done! Formats:" + formats);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Format> getFormats(Song song)
|
private void getFormats(Song song)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
try
|
try
|
||||||
|
@ -155,15 +156,16 @@ public class Downloader implements Consumer<Song>
|
||||||
continue;
|
continue;
|
||||||
formats.add(new Format(split[0], split[1], split[2], split[3]));
|
formats.add(new Format(split[0], split[1], split[2], split[3]));
|
||||||
}
|
}
|
||||||
|
song.setFormats(formats);
|
||||||
// Matcher matcher = FORMAT_PATTERN.matcher(output);
|
// Matcher matcher = FORMAT_PATTERN.matcher(output);
|
||||||
// while (matcher.find())
|
// while (matcher.find())
|
||||||
// formats.add(new Format(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)));
|
// formats.add(new Format(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)));
|
||||||
return formats;
|
// return formats;
|
||||||
|
|
||||||
} catch (Exception ex)
|
} catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
return List.of();
|
// return List.of();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,15 +195,19 @@ public class Downloader implements Consumer<Song>
|
||||||
return downloadDir;
|
return downloadDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download(Song song, Format format)
|
private void download(Song song)
|
||||||
{
|
{
|
||||||
final String formatCodes = format != null ? format.getCode() + "/" : "";
|
chooseFormats(song);
|
||||||
|
String formatCodes = "";
|
||||||
|
final List<Format> formats = song.getFormats();
|
||||||
|
for (int i = 0; i < 3 && i < song.getFormats().size(); i++)
|
||||||
|
formatCodes += formats.get(i).getCode() + "/";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
messageHandler.accept(song, null);
|
messageHandler.accept(song, null);
|
||||||
String cmd = "/usr/bin/youtube-dl -x"
|
String cmd = "/usr/bin/youtube-dl -x"
|
||||||
+ " -f " + formatCodes + "worstaudio/bestaudio/worst/best "
|
+ " -f " + formatCodes + "worstaudio/bestaudio/worst/best"
|
||||||
+ " --audio-format=wav"
|
+ " --audio-format=wav"
|
||||||
+ " --no-playlist"
|
+ " --no-playlist"
|
||||||
+ " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s "
|
+ " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s "
|
||||||
|
@ -212,7 +218,7 @@ public class Downloader implements Consumer<Song>
|
||||||
String output = new String(in.readAllBytes(), Charset.defaultCharset());
|
String output = new String(in.readAllBytes(), Charset.defaultCharset());
|
||||||
String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset());
|
String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset());
|
||||||
System.out.println(output);
|
System.out.println(output);
|
||||||
Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output);
|
Matcher matcher = DESTINATION_PATTERN.matcher(output);
|
||||||
if (matcher.find())
|
if (matcher.find())
|
||||||
song.setLocation(new File(matcher.group(1)));
|
song.setLocation(new File(matcher.group(1)));
|
||||||
else if (exec.exitValue() != 0)
|
else if (exec.exitValue() != 0)
|
||||||
|
|
|
@ -12,6 +12,8 @@ import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
|
import moe.nekojimi.chords.commands.Command;
|
||||||
|
import moe.nekojimi.chords.commands.JoinCommand;
|
||||||
import moe.nekojimi.musicsearcher.Query;
|
import moe.nekojimi.musicsearcher.Query;
|
||||||
import moe.nekojimi.musicsearcher.Result;
|
import moe.nekojimi.musicsearcher.Result;
|
||||||
import moe.nekojimi.musicsearcher.providers.MetaSearcher;
|
import moe.nekojimi.musicsearcher.providers.MetaSearcher;
|
||||||
|
@ -41,6 +43,8 @@ public class Main extends ListenerAdapter
|
||||||
private final Searcher searcher;
|
private final Searcher searcher;
|
||||||
private JDA jda;
|
private JDA jda;
|
||||||
|
|
||||||
|
private final Map<String, Command> commands = new HashMap<>();
|
||||||
|
|
||||||
private VoiceChannel currentVoiceChannel = null;
|
private VoiceChannel currentVoiceChannel = null;
|
||||||
|
|
||||||
private List<Result> lastSearchResults;
|
private List<Result> lastSearchResults;
|
||||||
|
@ -91,12 +95,27 @@ public class Main extends ListenerAdapter
|
||||||
if (song.getLocation() != null)
|
if (song.getLocation() != null)
|
||||||
channel.sendMessage(/*bracketNo + */"Finished downloading " + song + " for " + song.getRequestedBy() + ", added to queue!").queue();
|
channel.sendMessage(/*bracketNo + */"Finished downloading " + song + " for " + song.getRequestedBy() + ", added to queue!").queue();
|
||||||
else
|
else
|
||||||
channel.sendMessage(/*bracketNo + */"Now downloading " + song + " for " + song.getRequestedBy() + " ...").queue();
|
{
|
||||||
|
Format format = song.getBestFormat();
|
||||||
|
String formatDetails = "";
|
||||||
|
if (format != null)
|
||||||
|
{
|
||||||
|
formatDetails = " (" + format.getBitrate() / 1000 + "k, " + String.format("%.2f", format.getSize() / (1024.0 * 1024.0)) + "MiB)";
|
||||||
|
}
|
||||||
|
channel.sendMessage(/*bracketNo + */"Now downloading " + song + formatDetails + " for " + song.getRequestedBy() + " ...").queue();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
channel.sendMessage(/*bracketNo + */"Failed to download " + song + " for " + song.getRequestedBy() + "! Reason: " + ex.getMessage()).queue();
|
channel.sendMessage(/*bracketNo + */"Failed to download " + song + " for " + song.getRequestedBy() + "! Reason: " + ex.getMessage()).queue();
|
||||||
|
|
||||||
});
|
});
|
||||||
searcher = MetaSearcher.loadYAML(new File("searchproviders.yml"));
|
searcher = MetaSearcher.loadYAML(new File("searchproviders.yml"));
|
||||||
|
|
||||||
|
addCommand(new JoinCommand(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCommand(Command command)
|
||||||
|
{
|
||||||
|
commands.put(command.getKeyword(), command);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setJda(JDA jda)
|
public void setJda(JDA jda)
|
||||||
|
@ -122,17 +141,30 @@ public class Main extends ListenerAdapter
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
String[] split = content.split("\\s+", 2);
|
||||||
|
String cmd = split[0].toLowerCase();
|
||||||
|
|
||||||
|
if (!cmd.startsWith("!"))
|
||||||
|
return; // doesn't start with prefix char
|
||||||
|
|
||||||
|
cmd = cmd.substring(1); // strip prefix char
|
||||||
|
|
||||||
String arg = "";
|
String arg = "";
|
||||||
if (content.contains(" "))
|
if (split.length > 1)
|
||||||
arg = content.split(" ", 2)[1];
|
arg = split[1];
|
||||||
|
|
||||||
if (content.startsWith("!join "))
|
if (commands.containsKey(cmd))
|
||||||
onJoinCommand(event, guild, arg);
|
{
|
||||||
|
Command command = commands.get(cmd);
|
||||||
|
command.call(event, List.of(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// else if (content.startsWith("!join "))
|
||||||
|
// onJoinCommand(event, guild, arg);
|
||||||
else if (content.equals("!leave"))
|
else if (content.equals("!leave"))
|
||||||
onLeaveCommand(event);
|
onLeaveCommand(event);
|
||||||
else if (content.equals("!join"))
|
// else if (content.equals("!join"))
|
||||||
onJoinCommand(event);
|
// onJoinCommand(event);
|
||||||
else if (content.startsWith("!play "))
|
else if (content.startsWith("!play "))
|
||||||
onPlayCommand(event, guild, arg);
|
onPlayCommand(event, guild, arg);
|
||||||
else if (content.startsWith("!queue"))
|
else if (content.startsWith("!queue"))
|
||||||
|
@ -157,20 +189,20 @@ public class Main extends ListenerAdapter
|
||||||
* @param event
|
* @param event
|
||||||
* The event for this command
|
* The event for this command
|
||||||
*/
|
*/
|
||||||
private void onJoinCommand(GuildMessageReceivedEvent event)
|
// private void onJoinCommand(GuildMessageReceivedEvent event)
|
||||||
{
|
// {
|
||||||
// Note: None of these can be null due to our configuration with the JDABuilder!
|
// // Note: None of these can be null due to our configuration with the JDABuilder!
|
||||||
Member member = event.getMember(); // Member is the context of the user for the specific guild, containing voice state and roles
|
// Member member = event.getMember(); // Member is the context of the user for the specific guild, containing voice state and roles
|
||||||
GuildVoiceState voiceState = member.getVoiceState(); // Check the current voice state of the user
|
// GuildVoiceState voiceState = member.getVoiceState(); // Check the current voice state of the user
|
||||||
VoiceChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to
|
// VoiceChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to
|
||||||
// if (channel != null)
|
//// if (channel != null)
|
||||||
// {
|
//// {
|
||||||
// connectTo(channel); // Join the channel of the user
|
//// connectTo(channel); // Join the channel of the user
|
||||||
// onConnecting(channel, event.getChannel()); // Tell the user about our success
|
//// onConnecting(channel, event.getChannel()); // Tell the user about our success
|
||||||
// } else
|
//// } else
|
||||||
// onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure
|
//// onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure
|
||||||
onJoinCommand(event, member.getGuild(), channel.getName());
|
// onJoinCommand(event, member.getGuild(), channel.getName());
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle command with arguments.
|
* Handle command with arguments.
|
||||||
|
@ -182,35 +214,34 @@ public class Main extends ListenerAdapter
|
||||||
* @param arg
|
* @param arg
|
||||||
* The input argument
|
* The input argument
|
||||||
*/
|
*/
|
||||||
private void onJoinCommand(GuildMessageReceivedEvent event, Guild guild, String arg)
|
// private void onJoinCommand(GuildMessageReceivedEvent event, Guild guild, String arg)
|
||||||
{
|
// {
|
||||||
boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits
|
// boolean isNumber = arg.matches("\\d+"); // This is a regular expression that ensures the input consists of digits
|
||||||
VoiceChannel channel = null;
|
// VoiceChannel channel = null;
|
||||||
if (isNumber) // The input is an id?
|
// if (isNumber) // The input is an id?
|
||||||
channel = guild.getVoiceChannelById(arg);
|
// channel = guild.getVoiceChannelById(arg);
|
||||||
if (channel == null) // Then the input must be a name?
|
// if (channel == null) // Then the input must be a name?
|
||||||
{
|
// {
|
||||||
List<VoiceChannel> channels = guild.getVoiceChannelsByName(arg, true);
|
// List<VoiceChannel> channels = guild.getVoiceChannelsByName(arg, true);
|
||||||
if (!channels.isEmpty()) // Make sure we found at least one exact match
|
// if (!channels.isEmpty()) // Make sure we found at least one exact match
|
||||||
channel = channels.get(0); // We found a channel! This cannot be null.
|
// channel = channels.get(0); // We found a channel! This cannot be null.
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
TextChannel textChannel = event.getChannel();
|
// TextChannel textChannel = event.getChannel();
|
||||||
if (channel == null) // I have no idea what you want mr user
|
// if (channel == null) // I have no idea what you want mr user
|
||||||
{
|
// {
|
||||||
onUnknownChannel(textChannel, arg); // Let the user know about our failure
|
// onUnknownChannel(textChannel, arg); // Let the user know about our failure
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
connectTo(channel); // We found a channel to connect to!
|
// connectTo(channel); // We found a channel to connect to!
|
||||||
onConnecting(channel, textChannel); // Let the user know, we were successful!
|
// onConnecting(channel, textChannel); // Let the user know, we were successful!
|
||||||
}
|
// }
|
||||||
|
|
||||||
private void onLeaveCommand(GuildMessageReceivedEvent event)
|
private void onLeaveCommand(GuildMessageReceivedEvent event)
|
||||||
{
|
{
|
||||||
if (currentVoiceChannel != null)
|
if (currentVoiceChannel != null)
|
||||||
{
|
{
|
||||||
disconnect();
|
disconnect();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,43 +435,19 @@ public class Main extends ListenerAdapter
|
||||||
+ "!remove <Index> - Remove the song at position <Index> from the queue.\n"
|
+ "!remove <Index> - Remove the song at position <Index> from the queue.\n"
|
||||||
+ "!skip - Skip the current song and play the next one.\n"
|
+ "!skip - Skip the current song and play the next one.\n"
|
||||||
+ "!restart - Try playing the current song again in case it goes wrong.\n";
|
+ "!restart - Try playing the current song again in case it goes wrong.\n";
|
||||||
|
// for (String key: commands.keySet())
|
||||||
|
// {
|
||||||
|
// help += "!" + key + ":"
|
||||||
|
// }
|
||||||
event.getChannel().sendMessage(help).queue();
|
event.getChannel().sendMessage(help).queue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform user about successful connection.
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* The voice channel we connected to
|
|
||||||
* @param textChannel
|
|
||||||
* The text channel to send the message in
|
|
||||||
*/
|
|
||||||
private void onConnecting(VoiceChannel channel, TextChannel textChannel)
|
|
||||||
{
|
|
||||||
textChannel.sendMessage("Connecting to " + channel.getName()).queue(); // never forget to queue()!
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The channel to connect to is not known to us.
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* The message channel (text channel abstraction) to send failure
|
|
||||||
* information to
|
|
||||||
* @param comment
|
|
||||||
* The information of this channel
|
|
||||||
*/
|
|
||||||
private void onUnknownChannel(MessageChannel channel, String comment)
|
|
||||||
{
|
|
||||||
channel.sendMessage("Unable to connect to ``" + comment + "``, no such channel!").queue(); // never forget to queue()!
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to requested channel and start echo handler
|
* Connect to requested channel and start echo handler
|
||||||
*
|
*
|
||||||
* @param channel
|
* @param channel
|
||||||
* The channel to connect to
|
* The channel to connect to
|
||||||
*/
|
*/
|
||||||
private void connectTo(VoiceChannel channel)
|
public void connectTo(VoiceChannel channel)
|
||||||
{
|
{
|
||||||
Guild guild = channel.getGuild();
|
Guild guild = channel.getGuild();
|
||||||
// Get an audio manager for this guild, this will be created upon first use for each guild
|
// Get an audio manager for this guild, this will be created upon first use for each guild
|
||||||
|
@ -460,7 +467,7 @@ public class Main extends ListenerAdapter
|
||||||
currentVoiceChannel = channel;
|
currentVoiceChannel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disconnect()
|
public void disconnect()
|
||||||
{
|
{
|
||||||
if (currentVoiceChannel != null)
|
if (currentVoiceChannel != null)
|
||||||
{
|
{
|
||||||
|
@ -473,4 +480,39 @@ public class Main extends ListenerAdapter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MusicHandler getMusicHandler()
|
||||||
|
{
|
||||||
|
return musicHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Downloader getDownloader()
|
||||||
|
{
|
||||||
|
return downloader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Searcher getSearcher()
|
||||||
|
{
|
||||||
|
return searcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JDA getJda()
|
||||||
|
{
|
||||||
|
return jda;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VoiceChannel getCurrentVoiceChannel()
|
||||||
|
{
|
||||||
|
return currentVoiceChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Result> getLastSearchResults()
|
||||||
|
{
|
||||||
|
return lastSearchResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTrackNumber()
|
||||||
|
{
|
||||||
|
return trackNumber;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ package moe.nekojimi.chords;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -19,6 +21,7 @@ public class Song
|
||||||
private final URL url;
|
private final URL url;
|
||||||
private File location = null;
|
private File location = null;
|
||||||
private int number;
|
private int number;
|
||||||
|
private List<Format> formats = new ArrayList<>();
|
||||||
|
|
||||||
private String requestedBy;
|
private String requestedBy;
|
||||||
private String requestedIn;
|
private String requestedIn;
|
||||||
|
@ -127,4 +130,22 @@ public class Song
|
||||||
return "[" + number + "] " + ret;
|
return "[" + number + "] " + ret;
|
||||||
// return url.toExternalForm();
|
// return url.toExternalForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Format> getFormats()
|
||||||
|
{
|
||||||
|
return formats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFormats(List<Format> formats)
|
||||||
|
{
|
||||||
|
this.formats = formats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Format getBestFormat()
|
||||||
|
{
|
||||||
|
if (formats.isEmpty())
|
||||||
|
return null;
|
||||||
|
return formats.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue