Try multiple manual download formats before falling back to auto, and display the bitrate and download size to the user.

crossfading
Nekojimi 3 years ago
parent 83bb9e5805
commit 2bd51ea856
  1. 50
      src/main/java/moe/nekojimi/chords/Downloader.java
  2. 190
      src/main/java/moe/nekojimi/chords/Main.java
  3. 21
      src/main/java/moe/nekojimi/chords/Song.java

@ -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…
Cancel
Save