Optimise downloads by selecting download format manually.
This commit is contained in:
parent
cc17eb0bd9
commit
83bb9e5805
|
@ -10,11 +10,9 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.attribute.FileAttribute;
|
import java.util.ArrayList;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -24,6 +22,7 @@ import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.json.Json;
|
import javax.json.Json;
|
||||||
import javax.json.JsonObject;
|
import javax.json.JsonObject;
|
||||||
import javax.json.JsonReader;
|
import javax.json.JsonReader;
|
||||||
|
@ -34,6 +33,10 @@ import javax.json.JsonReader;
|
||||||
*/
|
*/
|
||||||
public class Downloader implements Consumer<Song>
|
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+(.*)$");
|
||||||
|
|
||||||
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<>();
|
||||||
private final ThreadPoolExecutor exec = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, workQueue);
|
private final ThreadPoolExecutor exec = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, workQueue);
|
||||||
|
@ -57,7 +60,15 @@ public class Downloader implements Consumer<Song>
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
download(song);
|
try
|
||||||
|
{
|
||||||
|
List<Format> formats = getFormats(song);
|
||||||
|
Format chosenFormat = chooseFormat(formats);
|
||||||
|
download(song, chosenFormat);
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -67,6 +78,95 @@ public class Downloader implements Consumer<Song>
|
||||||
this.next = next;
|
this.next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Format chooseFormat(List<Format> formats)
|
||||||
|
{
|
||||||
|
if (formats.isEmpty())
|
||||||
|
return null;
|
||||||
|
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());
|
||||||
|
int comp = 0;
|
||||||
|
comp = Boolean.compare(a.isAudioOnly(), b.isAudioOnly());
|
||||||
|
System.out.println("\tCompared on audio only: " + comp);
|
||||||
|
if (comp == 0)
|
||||||
|
{
|
||||||
|
// known preferred to unknown
|
||||||
|
if (a.getBitrate() == b.getBitrate())
|
||||||
|
comp = 0;
|
||||||
|
else if (a.getBitrate() <= 0)
|
||||||
|
comp = 1;
|
||||||
|
else if (b.getBitrate() <= 0)
|
||||||
|
comp = -1;
|
||||||
|
else // closer to the target bitrate is best
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (comp == 0)
|
||||||
|
{
|
||||||
|
// known preferred to unknown
|
||||||
|
if (a.getSize() == b.getSize())
|
||||||
|
comp = 0;
|
||||||
|
if (a.getSize() <= 0)
|
||||||
|
comp = 1;
|
||||||
|
else if (b.getSize() <= 0)
|
||||||
|
comp = -1;
|
||||||
|
else // smaller is better
|
||||||
|
{
|
||||||
|
comp = Long.compare(b.getSize(), a.getSize());
|
||||||
|
System.out.println("\tCompared on filesize: " + comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("\tOverall: " + comp);
|
||||||
|
return comp;
|
||||||
|
});
|
||||||
|
System.out.println("Sorting done! Formats:" + formats);
|
||||||
|
return formats.get(formats.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Format> getFormats(Song song)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String cmd = "/usr/bin/youtube-dl --skip-download -F " + song.getUrl().toString();
|
||||||
|
Process exec = runCommand(cmd, 5);
|
||||||
|
InputStream input = exec.getInputStream();
|
||||||
|
String output = new String(input.readAllBytes(), Charset.defaultCharset());
|
||||||
|
|
||||||
|
List<Format> formats = new ArrayList<>();
|
||||||
|
|
||||||
|
List<String> list = output.lines().collect(Collectors.toList());
|
||||||
|
int i = 0;
|
||||||
|
while (!list.get(i).contains("Available formats"))
|
||||||
|
i++;
|
||||||
|
i++;
|
||||||
|
for (; i < list.size(); i++)
|
||||||
|
{
|
||||||
|
String line = list.get(i);
|
||||||
|
String[] split = line.split("\\s\\s+", 4);
|
||||||
|
if (split.length < 4)
|
||||||
|
continue;
|
||||||
|
formats.add(new Format(split[0], split[1], split[2], split[3]));
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void getInfo(Song song)
|
private void getInfo(Song song)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -93,13 +193,20 @@ public class Downloader implements Consumer<Song>
|
||||||
return downloadDir;
|
return downloadDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download(Song song)
|
private void download(Song song, Format format)
|
||||||
{
|
{
|
||||||
|
final String formatCodes = format != null ? format.getCode() + "/" : "";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
messageHandler.accept(song, null);
|
messageHandler.accept(song, null);
|
||||||
String cmd = "/usr/bin/youtube-dl -x -f=worstaudio/worst --audio-format=wav --no-playlist -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s " + song.getUrl().toString();
|
String cmd = "/usr/bin/youtube-dl -x"
|
||||||
|
+ " -f " + formatCodes + "worstaudio/bestaudio/worst/best "
|
||||||
|
+ " --audio-format=wav"
|
||||||
|
+ " --no-playlist"
|
||||||
|
+ " -o " + getDownloadDir().getAbsolutePath() + "/%(title)s.%(ext)s "
|
||||||
|
+ song.getUrl().toString();
|
||||||
|
|
||||||
Process exec = runCommand(cmd, 300);
|
Process exec = runCommand(cmd, 300);
|
||||||
InputStream in = exec.getInputStream();
|
InputStream in = exec.getInputStream();
|
||||||
String output = new String(in.readAllBytes(), Charset.defaultCharset());
|
String output = new String(in.readAllBytes(), Charset.defaultCharset());
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package moe.nekojimi.chords;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author jimj316
|
||||||
|
*/
|
||||||
|
class Format
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final Pattern SIZE_PATTERN = Pattern.compile("\\b([0-9]+\\.?[0-9]*)([kkMmGg])i?[bB]\\b");
|
||||||
|
private static final Pattern BITRATE_PATTERN = Pattern.compile("\\b([0-9]+)k(b(ps?))?\\b");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String extension;
|
||||||
|
private final String resolution;
|
||||||
|
private final String note;
|
||||||
|
|
||||||
|
public Format(String code, String extension, String resolution, String note)
|
||||||
|
{
|
||||||
|
this.code = code;
|
||||||
|
this.extension = extension;
|
||||||
|
this.resolution = resolution;
|
||||||
|
this.note = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAudioOnly()
|
||||||
|
{
|
||||||
|
return resolution.trim().toLowerCase().contains("audio only");
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSize()
|
||||||
|
{
|
||||||
|
// try to find eg. "1.32MiB" inside note
|
||||||
|
Matcher matcher = SIZE_PATTERN.matcher(note);
|
||||||
|
if (matcher.find())
|
||||||
|
{
|
||||||
|
double value = Double.parseDouble(matcher.group(1));
|
||||||
|
String mag = matcher.group(2).toUpperCase();
|
||||||
|
long mult = 1;
|
||||||
|
switch (mag)
|
||||||
|
{
|
||||||
|
case "K":
|
||||||
|
mult = 1024;
|
||||||
|
break;
|
||||||
|
case "M":
|
||||||
|
mult = 1024 * 1024;
|
||||||
|
break;
|
||||||
|
case "G":
|
||||||
|
mult = 1024 * 1024 * 1024;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
value *= mult;
|
||||||
|
return (long) value;
|
||||||
|
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBitrate()
|
||||||
|
{
|
||||||
|
// try to find eg. "51k" inside note
|
||||||
|
Matcher matcher = BITRATE_PATTERN.matcher(note);
|
||||||
|
if (matcher.find())
|
||||||
|
{
|
||||||
|
return Integer.parseInt(matcher.group(1)) * 1000;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode()
|
||||||
|
{
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtension()
|
||||||
|
{
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResolution()
|
||||||
|
{
|
||||||
|
return resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNote()
|
||||||
|
{
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "Format{" + "code=" + code + ", extension=" + extension + ", resolution=" + resolution + ", note=" + note + '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue