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 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 LinkedBlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
@ -62,9 +63,8 @@ public class Downloader implements Consumer<Song>
{
try
{
List<Format> formats = getFormats(song);
Format chosenFormat = chooseFormat(formats);
download(song, chosenFormat);
getFormats(song);
download(song);
} catch (Exception ex)
{
ex.printStackTrace();
@ -78,19 +78,20 @@ public class Downloader implements Consumer<Song>
this.next = next;
}
private Format chooseFormat(List<Format> formats)
private void chooseFormats(Song song)
{
List<Format> formats = song.getFormats();
if (formats.isEmpty())
return null;
System.out.println("Choosing from " + formats.size() + " formats:");
System.out.println(formats);
return;
// System.out.println("Choosing from " + formats.size() + " formats:");
// System.out.println(formats);
formats.sort((Format a, Format b) ->
{
// 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;
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)
{
// known preferred to unknown
@ -105,7 +106,7 @@ public class Downloader implements Consumer<Song>
int aDist = Math.abs(a.getBitrate() - BITRATE_TARGET);
int bDist = Math.abs(b.getBitrate() - BITRATE_TARGET);
comp = Integer.compare(bDist, aDist);
System.out.println("\tCompared on bitrate distance: " + comp);
// System.out.println("\tCompared on bitrate distance: " + comp);
}
}
if (comp == 0)
@ -120,17 +121,17 @@ public class Downloader implements Consumer<Song>
else // smaller is better
{
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);
return comp;
// System.out.println("\tOverall: " + comp);
return -comp;
});
System.out.println("Sorting done! Formats:" + formats);
return formats.get(formats.size() - 1);
song.setFormats(formats);
// System.out.println("Sorting done! Formats:" + formats);
}
private List<Format> getFormats(Song song)
private void getFormats(Song song)
{
//
try
@ -155,15 +156,16 @@ public class Downloader implements Consumer<Song>
continue;
formats.add(new Format(split[0], split[1], split[2], split[3]));
}
song.setFormats(formats);
// Matcher matcher = FORMAT_PATTERN.matcher(output);
// while (matcher.find())
// formats.add(new Format(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)));
return formats;
// return formats;
} catch (Exception 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;
}
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
{
messageHandler.accept(song, null);
String cmd = "/usr/bin/youtube-dl -x"
+ " -f " + formatCodes + "worstaudio/bestaudio/worst/best "
+ " -f " + formatCodes + "worstaudio/bestaudio/worst/best"
+ " --audio-format=wav"
+ " --no-playlist"
+ " -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 error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset());
System.out.println(output);
Matcher matcher = Pattern.compile("Destination: (.*\\.wav)").matcher(output);
Matcher matcher = DESTINATION_PATTERN.matcher(output);
if (matcher.find())
song.setLocation(new File(matcher.group(1)));
else if (exec.exitValue() != 0)

@ -12,6 +12,8 @@ import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
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.Result;
import moe.nekojimi.musicsearcher.providers.MetaSearcher;
@ -41,6 +43,8 @@ public class Main extends ListenerAdapter
private final Searcher searcher;
private JDA jda;
private final Map<String, Command> commands = new HashMap<>();
private VoiceChannel currentVoiceChannel = null;
private List<Result> lastSearchResults;
@ -91,12 +95,27 @@ public class Main extends ListenerAdapter
if (song.getLocation() != null)
channel.sendMessage(/*bracketNo + */"Finished downloading " + song + " for " + song.getRequestedBy() + ", added to queue!").queue();
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
channel.sendMessage(/*bracketNo + */"Failed to download " + song + " for " + song.getRequestedBy() + "! Reason: " + ex.getMessage()).queue();
});
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)
@ -122,17 +141,30 @@ public class Main extends ListenerAdapter
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 = "";
if (content.contains(" "))
arg = content.split(" ", 2)[1];
if (split.length > 1)
arg = split[1];
if (content.startsWith("!join "))
onJoinCommand(event, guild, arg);
if (commands.containsKey(cmd))
{
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"))
onLeaveCommand(event);
else if (content.equals("!join"))
onJoinCommand(event);
// else if (content.equals("!join"))
// onJoinCommand(event);
else if (content.startsWith("!play "))
onPlayCommand(event, guild, arg);
else if (content.startsWith("!queue"))
@ -157,20 +189,20 @@ public class Main extends ListenerAdapter
* @param event
* The event for this command
*/
private void onJoinCommand(GuildMessageReceivedEvent event)
{
// 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
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
// if (channel != null)
// {
// connectTo(channel); // Join the channel of the user
// onConnecting(channel, event.getChannel()); // Tell the user about our success
// } else
// onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure
onJoinCommand(event, member.getGuild(), channel.getName());
}
// private void onJoinCommand(GuildMessageReceivedEvent event)
// {
// // 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
// 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
//// if (channel != null)
//// {
//// connectTo(channel); // Join the channel of the user
//// onConnecting(channel, event.getChannel()); // Tell the user about our success
//// } else
//// onUnknownChannel(event.getChannel(), "your voice channel"); // Tell the user about our failure
// onJoinCommand(event, member.getGuild(), channel.getName());
// }
/**
* Handle command with arguments.
@ -182,35 +214,34 @@ public class Main extends ListenerAdapter
* @param arg
* The input argument
*/
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
VoiceChannel channel = null;
if (isNumber) // The input is an id?
channel = guild.getVoiceChannelById(arg);
if (channel == null) // Then the input must be a name?
{
List<VoiceChannel> channels = guild.getVoiceChannelsByName(arg, true);
if (!channels.isEmpty()) // Make sure we found at least one exact match
channel = channels.get(0); // We found a channel! This cannot be null.
}
TextChannel textChannel = event.getChannel();
if (channel == null) // I have no idea what you want mr user
{
onUnknownChannel(textChannel, arg); // Let the user know about our failure
return;
}
connectTo(channel); // We found a channel to connect to!
onConnecting(channel, textChannel); // Let the user know, we were successful!
}
// 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
// VoiceChannel channel = null;
// if (isNumber) // The input is an id?
// channel = guild.getVoiceChannelById(arg);
// if (channel == null) // Then the input must be a name?
// {
// List<VoiceChannel> channels = guild.getVoiceChannelsByName(arg, true);
// if (!channels.isEmpty()) // Make sure we found at least one exact match
// channel = channels.get(0); // We found a channel! This cannot be null.
// }
//
// TextChannel textChannel = event.getChannel();
// if (channel == null) // I have no idea what you want mr user
// {
// onUnknownChannel(textChannel, arg); // Let the user know about our failure
// return;
// }
// connectTo(channel); // We found a channel to connect to!
// onConnecting(channel, textChannel); // Let the user know, we were successful!
// }
private void onLeaveCommand(GuildMessageReceivedEvent event)
{
if (currentVoiceChannel != null)
{
disconnect();
}
}
@ -404,43 +435,19 @@ public class Main extends ListenerAdapter
+ "!remove <Index> - Remove the song at position <Index> from the queue.\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";
// for (String key: commands.keySet())
// {
// help += "!" + key + ":"
// }
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
*
* @param channel
* The channel to connect to
*/
private void connectTo(VoiceChannel channel)
public void connectTo(VoiceChannel channel)
{
Guild guild = channel.getGuild();
// 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;
}
private void disconnect()
public void disconnect()
{
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.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
*
@ -19,6 +21,7 @@ public class Song
private final URL url;
private File location = null;
private int number;
private List<Format> formats = new ArrayList<>();
private String requestedBy;
private String requestedIn;
@ -127,4 +130,22 @@ public class Song
return "[" + number + "] " + ret;
// 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