Refactor common media characteristics (e.g. to use ffprobe instead of libmediainfo internally for various use cases)

This commit is contained in:
Reinhard Pointner 2018-06-04 19:44:35 +07:00
parent 3885030695
commit 1390a23318
2 changed files with 109 additions and 8 deletions

View File

@ -1082,7 +1082,7 @@ public class MediaBindingBean {
@Define("ffprobe")
public Object getFFProbeDump() throws Exception {
return new FFProbe().streams(getInferredMediaFile());
return new FFProbe().open(getInferredMediaFile());
}
public File getInferredMediaFile() {

View File

@ -2,23 +2,29 @@ package net.filebot.media;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import com.cedarsoftware.util.io.JsonReader;
import com.cedarsoftware.util.io.JsonWriter;
public class FFProbe {
public class FFProbe implements MediaCharacteristics {
public String getFFProbeCommand() {
protected String getFFProbeCommand() {
return System.getProperty("net.filebot.media.ffprobe", "ffprobe");
}
public List<Map<String, Object>> streams(File file) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(getFFProbeCommand(), "-show_streams", "-print_format", "json", "-v", "error", file.getCanonicalPath());
protected Map<String, Object> parse(File file) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(getFFProbeCommand(), "-show_streams", "-show_format", "-print_format", "json", "-v", "error", file.getCanonicalPath());
processBuilder.directory(file.getParentFile());
processBuilder.redirectError(Redirect.INHERIT);
@ -26,15 +32,110 @@ public class FFProbe {
Process process = processBuilder.start();
// parse process standard output
Map<String, Object> json = (Map<String, Object>) JsonReader.jsonToJava(process.getInputStream(), singletonMap(JsonReader.USE_MAPS, true));
List<Map<String, Object>> streams = (List) asList((Object[]) json.get("streams"));
Map<String, Object> json = (Map) JsonReader.jsonToJava(process.getInputStream(), singletonMap(JsonReader.USE_MAPS, true));
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException(String.format("%s failed with exit code %d", processBuilder.command(), exitCode));
}
return streams;
// group video / audio / subtitle streams together
return json;
}
private Map<String, Object> json;
public synchronized FFProbe open(File file) throws IOException, InterruptedException {
json = parse(file);
return this;
}
@Override
public synchronized void close() {
json = null;
}
@Override
public String getVideoCodec() {
return getString("video", "codec_name");
}
@Override
public String getAudioCodec() {
return getString("audio", "codec_name");
}
@Override
public String getAudioLanguage() {
return getString("audio", "tags", "language");
}
@Override
public String getSubtitleCodec() {
return getString("subtitle", "codec_name");
}
@Override
public Duration getDuration() {
double seconds = Double.parseDouble(getFormat().get("duration").toString());
long millis = (long) (seconds * 1000);
return Duration.ofMillis(millis);
}
@Override
public Integer getWidth() {
return getInteger("video", "width");
}
@Override
public Integer getHeight() {
return getInteger("video", "height");
}
@Override
public Float getFrameRate() {
return find("video", "avg_frame_rate").map(fps -> {
switch (fps) {
case "500/21":
return 23.976f; // normalize FPS value (using MediaInfo standards)
default:
return Float.parseFloat(fps);
}
}).get();
}
public Map<String, Object> getFormat() {
return (Map) json.get("format");
}
public List<Map<String, Object>> getStreams() {
return (List) asList((Object[]) json.get("streams"));
}
protected String getString(String streamKind, String key) {
return stream(streamKind, key).map(Objects::toString).collect(joining(" "));
}
protected String getString(String streamKind, String objectKey, String valueKey) {
return stream(streamKind, objectKey).map(t -> ((Map) t).get(valueKey)).map(Objects::toString).collect(joining(" "));
}
protected Stream<Object> stream(String streamKind, String property) {
return getStreams().stream().filter(s -> streamKind.equals(s.get("codec_type"))).map(s -> s.get(property)).filter(Objects::nonNull);
}
protected Integer getInteger(String streamKind, String property) {
return find(streamKind, property).map(Integer::parseInt).get();
}
protected Optional<String> find(String streamKind, String property) {
return stream(streamKind, property).map(Objects::toString).findFirst();
}
@Override
public String toString() {
return JsonWriter.objectToJson(json);
}
}