mirror of
https://github.com/mitb-archive/filebot
synced 2025-03-09 22:09:47 -04:00
Refactor common media characteristics (e.g. to use ffprobe instead of libmediainfo internally for various use cases)
This commit is contained in:
parent
3885030695
commit
1390a23318
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user