Compare commits

..

No commits in common. '5ec62d4d439f49599438990bc46d7bf5f0739d64' and 'c8684dbe75d9a1ba92024f51e293af14476c2c5e' have entirely different histories.

  1. 44
      src/main/java/moe/nekojimi/chords/Chords.java
  2. 183
      src/main/java/moe/nekojimi/chords/Downloader.java
  3. 28
      src/main/java/moe/nekojimi/chords/TrackPlayer.java

@ -29,7 +29,6 @@ import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel; import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
import net.dv8tion.jda.api.events.message.MessageEmbedEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.managers.AudioManager; import net.dv8tion.jda.api.managers.AudioManager;
@ -239,35 +238,20 @@ public final class Chords extends ListenerAdapter
String cmd = split[0].toLowerCase(); String cmd = split[0].toLowerCase();
if (cmd.startsWith("!")) if (!cmd.startsWith("!"))
{ return; // doesn't start with prefix char
cmd = cmd.substring(1); // strip prefix char
if (commands.containsKey(cmd)) cmd = cmd.substring(1); // strip prefix char
{
Command command = commands.get(cmd); // String arg = "";
command.call(invocation);
} else if (commands.containsKey(cmd))
{
helpCommand.call(invocation);
}
} else if (!event.getMessage().getAttachments().isEmpty())
{ {
for (Message.Attachment attach : event.getMessage().getAttachments()) Command command = commands.get(cmd);
{ command.call(invocation);
if (attach.getContentType().startsWith("audio") || attach.getContentType().startsWith("video")) } else
{ {
try helpCommand.call(invocation);
{
invocation = new Invocation(event, List.of(new URL(attach.getUrl()).toExternalForm()));
invocation.setRequestMessage(message);
playCommand.call(invocation);
} catch (MalformedURLException ex)
{
Logger.getLogger(Chords.class.getName()).log(Level.WARNING, null, ex);
}
}
}
} }
} catch (Exception ex) } catch (Exception ex)
{ {
@ -277,8 +261,6 @@ public final class Chords extends ListenerAdapter
event.getChannel().sendMessage("Error: " + ex.getMessage()).queue(); event.getChannel().sendMessage("Error: " + ex.getMessage()).queue();
log("UERR", "Command error:" + ex.getMessage()); log("UERR", "Command error:" + ex.getMessage());
} }
// TODO: this will handle uploading files, maybe
} }
public void queueDownload(TrackRequest request) public void queueDownload(TrackRequest request)
@ -506,7 +488,7 @@ public final class Chords extends ListenerAdapter
String progressDetails = ""; String progressDetails = "";
if (track.getProgress() >= 0) if (track.getProgress() >= 0)
{ {
if (track.getProgress() >= lastProgressUpdate + 5.0) if (track.getProgress() >= lastProgressUpdate + 10.0)
{ {
shouldUpdate = true; shouldUpdate = true;
lastProgressUpdate = track.getProgress(); lastProgressUpdate = track.getProgress();

@ -5,8 +5,10 @@
*/ */
package moe.nekojimi.chords; package moe.nekojimi.chords;
import com.beust.jcommander.Strings; import java.io.File;
import java.io.*; import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*; import java.util.*;
@ -38,7 +40,6 @@ public class Downloader extends QueueThing<TrackRequest, Track>
private static final int BITRATE_TARGET = (int) AudioSendHandler.INPUT_FORMAT.getSampleRate(); private static final int BITRATE_TARGET = (int) AudioSendHandler.INPUT_FORMAT.getSampleRate();
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)"); public static final Pattern DESTINATION_PATTERN = Pattern.compile("Destination: (.*\\.wav)");
public static final Pattern STREAM_PATTERN = Pattern.compile("Destination: -");
public static final Pattern PROGRESS_PATTERN = Pattern.compile("\\[download\\].*?([\\d\\.]+)%"); public static final Pattern PROGRESS_PATTERN = Pattern.compile("\\[download\\].*?([\\d\\.]+)%");
private static final Pattern INFO_JSON_PATTERN = Pattern.compile("Writing video metadata as JSON to: (.*\\.info\\.json)"); private static final Pattern INFO_JSON_PATTERN = Pattern.compile("Writing video metadata as JSON to: (.*\\.info\\.json)");
private static final Pattern ETA_PATTERN = Pattern.compile("\\[download\\].*?ETA\\s+(\\d{1,2}:\\d{2})"); private static final Pattern ETA_PATTERN = Pattern.compile("\\[download\\].*?ETA\\s+(\\d{1,2}:\\d{2})");
@ -98,9 +99,7 @@ public class Downloader extends QueueThing<TrackRequest, Track>
{ {
getInfo(request); getInfo(request);
// boolean streamOutput = request.getTracks().size() == 1; download(promise);
boolean streamOutput = false;
download(promise, streamOutput);
} catch (Exception ex) } catch (Exception ex)
{ {
ex.printStackTrace(); ex.printStackTrace();
@ -158,8 +157,8 @@ public class Downloader extends QueueThing<TrackRequest, Track>
return; return;
try try
{ {
// String cmd = + " --skip-download -F " + track.getUrl().toString(); String cmd = Chords.getSettings().getYtdlCommand() + " --skip-download -F " + track.getUrl().toString();
Process exec = runCommand(List.of(Chords.getSettings().getYtdlCommand(), "--skip-download", "-F", track.getUrl().toString()), FORMAT_TIMEOUT); Process exec = runCommand(cmd, FORMAT_TIMEOUT);
InputStream input = exec.getInputStream(); InputStream input = exec.getInputStream();
String output = new String(input.readAllBytes(), Charset.defaultCharset()); String output = new String(input.readAllBytes(), Charset.defaultCharset());
@ -232,8 +231,8 @@ public class Downloader extends QueueThing<TrackRequest, Track>
List<Track> ret = new ArrayList<>(); List<Track> ret = new ArrayList<>();
try try
{ {
// String cmd = Chords.getSettings().getYtdlCommand() + " --skip-download --print-json " + request.getUrl().toString(); String cmd = Chords.getSettings().getYtdlCommand() + " --skip-download --print-json " + request.getUrl().toString();
Process exec = runCommand(List.of(Chords.getSettings().getYtdlCommand(), "--skip-download", "--print-json", request.getUrl().toString()), INFO_TIMEOUT); Process exec = runCommand(cmd, INFO_TIMEOUT);
InputStream input = exec.getInputStream(); InputStream input = exec.getInputStream();
// read each line as JSON, turn each into a track object // read each line as JSON, turn each into a track object
@ -331,7 +330,7 @@ public class Downloader extends QueueThing<TrackRequest, Track>
return request.getTracks().get(idx); return request.getTracks().get(idx);
} }
private void download(Promise<TrackRequest, Track> promise, boolean streamOutput) throws InterruptedException, ExecutionException private void download(Promise<TrackRequest, Track> promise) throws InterruptedException, ExecutionException
{ {
TrackRequest request = promise.getInput(); TrackRequest request = promise.getInput();
Set<Format> uniqueFormats = new HashSet<>(); Set<Format> uniqueFormats = new HashSet<>();
@ -352,109 +351,69 @@ public class Downloader extends QueueThing<TrackRequest, Track>
try try
{ {
messageHandler.accept(request, null); messageHandler.accept(request, null);
List<String> cmd = new ArrayList<>(); String cmd = Chords.getSettings().getYtdlCommand()
cmd.add(Chords.getSettings().getYtdlCommand()); + " -x"
cmd.add("-x"); + " -f " + formatCodes + "worstaudio/bestaudio/worst/best"
cmd.add("-f=" + formatCodes + "worstaudio/bestaudio/worst/best"); + " --audio-format=wav"
cmd.add("--audio-format=wav"); + " --no-playlist"
cmd.add("--no-playlist"); // + " --extractor-args youtube:player_client=android"
// cmd.add(" --extractor-args youtube:player_client=android"; + " -N 8"
cmd.add("-N8"); + " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s "
if (streamOutput) + request.getUrl().toString();
{
cmd.add("--downloader=ffmpeg"); // download using FFMpeg Process exec = runCommand(cmd, DOWNLOAD_TIMEOUT);
cmd.add("--downloader-args=ffmpeg:-f wav -c:a pcm_s16le"); // tell FFMpeg to convert to wav InputStream in = exec.getInputStream();
cmd.add("-o"); // output to stdout
cmd.add("-"); Scanner sc = new Scanner(in);
} else while (sc.hasNextLine())
{ {
cmd.add("-o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s"); String line = sc.nextLine();
} System.out.println(line);
cmd.add(request.getUrl().toString());
Process exec = runCommand(cmd, streamOutput ? 0 : DOWNLOAD_TIMEOUT);
if (streamOutput) Matcher itemMatcher = DOWNLOAD_ITEM_PATTERN.matcher(line);
{ if (itemMatcher.find())
Scanner sc = new Scanner(exec.getErrorStream());
while (sc.hasNextLine())
{ {
String line = sc.nextLine(); int idx = Integer.parseInt(itemMatcher.group(1)) - 1;
System.out.println(line); // int total = Integer.parseInt(itemMatcher.group(2));
Matcher streamMatcher = STREAM_PATTERN.matcher(line);
if (streamMatcher.find()) downloadIdx = idx;
{
break;
}
} }
if (!exec.isAlive() && exec.exitValue() != 0) Matcher progMatcher = PROGRESS_PATTERN.matcher(line);
throw new RuntimeException("yt-dlp failed with error code " + exec.exitValue()); if (progMatcher.find())
Track track = getTrackFromRequest(request, 0);
BufferedInputStream inBuf = new BufferedInputStream(exec.getInputStream());
inBuf.mark(128);
final byte[] headerBytes = inBuf.readNBytes(80);
String header = new String(headerBytes);
System.out.println("streaming data header: " + header);
if (!header.startsWith("RIFF"))
throw new RuntimeException("Streaming data has bad header!");
inBuf.reset();
track.setInputStream(inBuf);
promise.complete(track);
track.setProgress(100.0);
messageHandler.accept(request, null);
} else
{
Scanner sc = new Scanner(exec.getInputStream());
while (sc.hasNextLine())
{ {
String line = sc.nextLine(); getTrackFromRequest(request, downloadIdx).setProgress(Double.parseDouble(progMatcher.group(1)));
System.out.println(line); messageHandler.accept(request, null);
}
Matcher itemMatcher = DOWNLOAD_ITEM_PATTERN.matcher(line);
if (itemMatcher.find())
{
int idx = Integer.parseInt(itemMatcher.group(1)) - 1;
downloadIdx = idx;
}
Matcher progMatcher = PROGRESS_PATTERN.matcher(line);
if (progMatcher.find())
{
getTrackFromRequest(request, downloadIdx).setProgress(Double.parseDouble(progMatcher.group(1)));
messageHandler.accept(request, null);
}
Matcher destMatcher = DESTINATION_PATTERN.matcher(line); Matcher destMatcher = DESTINATION_PATTERN.matcher(line);
if (destMatcher.find()) if (destMatcher.find())
{ {
Track track = getTrackFromRequest(request, downloadIdx); Track track = getTrackFromRequest(request, downloadIdx);
track.setLocation(new File(destMatcher.group(1))); track.setLocation(new File(destMatcher.group(1)));
// this is currently our criteria for completion; submit the track and move on // this is currently our criteria for completion; submit the track and move on
promise.complete(track); promise.complete(track);
track.setProgress(100.0); track.setProgress(100.0);
messageHandler.accept(request, null); messageHandler.accept(request, null);
downloadIdx++; downloadIdx++;
}
} }
}
boolean exited = exec.waitFor(DOWNLOAD_TIMEOUT, TimeUnit.SECONDS); boolean exited = exec.waitFor(DOWNLOAD_TIMEOUT, TimeUnit.SECONDS);
if (exited) if (exited)
{ {
String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset()); String error = new String(exec.getErrorStream().readAllBytes(), Charset.defaultCharset());
if (exec.exitValue() != 0) if (exec.exitValue() != 0)
throw new RuntimeException("youtube-dl failed with error " + exec.exitValue() + ", output:\n" + error); throw new RuntimeException("youtube-dl failed with error " + exec.exitValue() + ", output:\n" + error);
} else } else
{ {
throw new RuntimeException("youtube-dl failed to exit."); throw new RuntimeException("youtube-dl failed to exit.");
}
} }
messageHandler.accept(request, null); messageHandler.accept(request, null);
@ -467,21 +426,25 @@ public class Downloader extends QueueThing<TrackRequest, Track>
} }
} }
private Process runCommand(List<String> cmd, int timeoutSecs) throws RuntimeException, IOException, InterruptedException private Process runCommand(String cmd, int timeoutSecs) throws RuntimeException, IOException, InterruptedException
{ {
System.out.println("Running command: " + cmd); System.out.println("Running command: " + cmd);
Process exec = new ProcessBuilder(cmd).redirectOutput(ProcessBuilder.Redirect.PIPE).redirectError(ProcessBuilder.Redirect.PIPE).start(); // Process exec = Runtime.getRuntime().exec().split(" "));
if (timeoutSecs > 0) Process exec = new ProcessBuilder(cmd.split(" ")).redirectOutput(ProcessBuilder.Redirect.PIPE).redirectError(ProcessBuilder.Redirect.PIPE).start();
scheduler.schedule(() ->
{ {
scheduler.schedule(() -> if (exec.isAlive())
{ {
if (exec.isAlive()) exec.destroyForcibly();
{ System.err.println("Process " + cmd + " took too long, killing process.");
exec.destroyForcibly(); }
System.err.println("Process " + cmd + " took too long, killing process."); }, timeoutSecs, TimeUnit.SECONDS);
} // boolean done = exec.waitFor(timeoutSecs, TimeUnit.SECONDS);
}, timeoutSecs, TimeUnit.SECONDS); // if (!done)
} // {
// exec.destroyForcibly();
// throw new RuntimeException("Took too long, giving up.");
// }
return exec; return exec;
} }

@ -5,7 +5,6 @@
*/ */
package moe.nekojimi.chords; package moe.nekojimi.chords;
import java.io.BufferedInputStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -40,32 +39,30 @@ public class TrackPlayer implements Closeable
{ {
AudioInputStream in = null; AudioInputStream in = null;
AudioFormat decodedFormat = null; AudioFormat decodedFormat = null;
int retry = 0; for (int retry = 0; retry < RETRY_COUNT; retry++)
while (in == null)
{ {
try try
{ {
in = AudioSystem.getAudioInputStream(new BufferedInputStream(track.getInputStream())); in = AudioSystem.getAudioInputStream(track.getInputStream());
decodedFormat = AudioSendHandler.INPUT_FORMAT; decodedFormat = AudioSendHandler.INPUT_FORMAT;
break; // it worked! break; // it worked!
} catch (Exception ex) } catch (Exception ex)
{ {
retry++; try
if (retry < RETRY_COUNT)
{ {
System.err.println("Open file " + track.getLocation() + " failed because " + ex.getMessage() + " retry " + retry + "..."); Thread.sleep(((long) Math.pow(2, retry)) * RETRY_DELAY);
try } catch (InterruptedException ex1)
{
if (retry < RETRY_COUNT)
{ {
Thread.sleep(((long) Math.pow(2, retry)) * RETRY_DELAY); System.err.println("Open file " + track.getLocation() + " failed, retry " + retry + "...");
} catch (InterruptedException ex1) continue;
} else
{ {
throw ex;
} }
} else
{
throw ex;
} }
} }
} }
input = AudioSystem.getAudioInputStream(decodedFormat, in); input = AudioSystem.getAudioInputStream(decodedFormat, in);
@ -91,7 +88,7 @@ public class TrackPlayer implements Closeable
// throw new OutOfInputException(); // throw new OutOfInputException();
int toRead = Math.min(length, audioBuffer.getCurrentNumberOfBytes()); int toRead = Math.min(length, audioBuffer.getCurrentNumberOfBytes());
System.out.println("To read: " + toRead + " from " + audioBuffer.getCurrentNumberOfBytes()); // System.out.println("To read: " + toRead + " from " + audioBuffer.getCurrentNumberOfBytes());
if (toRead <= 0) if (toRead <= 0)
throw new OutOfInputException(); throw new OutOfInputException();
@ -169,7 +166,6 @@ public class TrackPlayer implements Closeable
} }
public static class OutOfInputException extends RuntimeException public static class OutOfInputException extends RuntimeException
{ {

Loading…
Cancel
Save