Skip to content

Commit

Permalink
Feature/ffmpeg layouts (#314)
Browse files Browse the repository at this point in the history
* Implement ffmpeg layouts command
  • Loading branch information
Euklios authored Mar 11, 2024
1 parent be9d457 commit 1d11c37
Show file tree
Hide file tree
Showing 8 changed files with 359 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/main/java/net/bramp/ffmpeg/FFmpeg.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -80,6 +81,9 @@ public class FFmpeg extends FFcommon {
/** Supported filters */
private List<Filter> filters = null;

/** Supported channel layouts */
private List<ChannelLayout> channelLayouts = null;

public FFmpeg() throws IOException {
this(DEFAULT_PATH, new RunProcessFunction());
}
Expand Down Expand Up @@ -242,6 +246,24 @@ public synchronized List<PixelFormat> pixelFormats() throws IOException {
return pixelFormats;
}

public synchronized List<ChannelLayout> channelLayouts() throws IOException {
checkIfFFmpeg();

if (this.channelLayouts == null) {
Process p = runFunc.run(ImmutableList.of(path, "-layouts"));

try {
BufferedReader r = wrapInReader(p);
this.channelLayouts = Collections.unmodifiableList(InfoParser.parseLayouts(r));
} finally {
p.destroy();
}

}

return this.channelLayouts;
}

protected ProgressParser createProgressParser(ProgressListener listener) throws IOException {
// TODO In future create the best kind for this OS, unix socket, named pipe, or TCP.
try {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/net/bramp/ffmpeg/info/ChannelLayout.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.bramp.ffmpeg.info;

public interface ChannelLayout {
String getName();
}
39 changes: 39 additions & 0 deletions src/main/java/net/bramp/ffmpeg/info/IndividualChannel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package net.bramp.ffmpeg.info;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class IndividualChannel implements ChannelLayout {
private final String name;
private final String description;

public IndividualChannel(String name, String description) {
this.name = name;
this.description = description;

}

@Override
public String getName() {
return name;
}

public String getDescription() {
return description;
}

@Override
public String toString() {
return name + " " + description;
}

@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}

@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
48 changes: 48 additions & 0 deletions src/main/java/net/bramp/ffmpeg/info/InfoParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.bramp.ffmpeg.info;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.*;

public final class InfoParser {
private InfoParser() {
throw new AssertionError("No instances for you!");
}

public static List<ChannelLayout> parseLayouts(BufferedReader r) throws IOException {
Map<String, IndividualChannel> individualChannelLookup = new HashMap<>();
List<ChannelLayout> channelLayouts = new ArrayList<>();

String line;
boolean parsingIndividualChannels = false;
boolean parsingChannelLayouts = false;

while ((line = r.readLine()) != null) {
if (line.startsWith("NAME") || line.isEmpty()) {
// Skip header and empty lines
continue;
} else if (line.equals("Individual channels:")) {
parsingIndividualChannels = true;
parsingChannelLayouts = false;
} else if (line.equals("Standard channel layouts:")) {
parsingIndividualChannels = false;
parsingChannelLayouts = true;
} else if (parsingIndividualChannels) {
String[] s = line.split(" ", 2);
IndividualChannel individualChannel = new IndividualChannel(s[0], s[1].trim());
channelLayouts.add(individualChannel);
individualChannelLookup.put(individualChannel.getName(), individualChannel);
} else if (parsingChannelLayouts) {
String[] s = line.split(" ", 2);
List<IndividualChannel> decomposition = new ArrayList<>();
for (String channelName : s[1].trim().split("\\+")) {
decomposition.add(individualChannelLookup.get(channelName));
}

channelLayouts.add(new StandardChannelLayout(s[0], Collections.unmodifiableList(decomposition)));
}
}

return channelLayouts;
}
}
40 changes: 40 additions & 0 deletions src/main/java/net/bramp/ffmpeg/info/StandardChannelLayout.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package net.bramp.ffmpeg.info;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

import java.util.List;

public class StandardChannelLayout implements ChannelLayout {
private final String name;
private final List<IndividualChannel> decomposition;

public StandardChannelLayout(String name, List<IndividualChannel> decomposition) {
this.name = name;
this.decomposition = decomposition;
}

@Override
public String getName() {
return name;
}

public List<IndividualChannel> getDecomposition() {
return decomposition;
}

@Override
public String toString() {
return name;
}

@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}

@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
11 changes: 11 additions & 0 deletions src/test/java/net/bramp/ffmpeg/FFmpegTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import net.bramp.ffmpeg.fixtures.Codecs;
import net.bramp.ffmpeg.fixtures.Filters;
import net.bramp.ffmpeg.fixtures.Formats;
import net.bramp.ffmpeg.fixtures.ChannelLayouts;
import net.bramp.ffmpeg.fixtures.PixelFormats;
import net.bramp.ffmpeg.info.Filter;
import net.bramp.ffmpeg.lang.NewProcessAnswer;
Expand Down Expand Up @@ -41,6 +42,8 @@ public void before() throws IOException {
.thenAnswer(new NewProcessAnswer("ffmpeg-version", "ffmpeg-no-such-file"));
when(runFunc.run(argThatHasItem("-filters")))
.thenAnswer(new NewProcessAnswer("ffmpeg-filters"));
when(runFunc.run(argThatHasItem("-layouts")))
.thenAnswer(new NewProcessAnswer("ffmpeg-layouts"));

ffmpeg = new FFmpeg(runFunc);
}
Expand Down Expand Up @@ -115,4 +118,12 @@ public void testFilters() throws IOException {

verify(runFunc, times(1)).run(argThatHasItem("-filters"));
}

@Test
public void testLayouts() throws IOException {
assertEquals(ChannelLayouts.CHANNEL_LAYOUTS, ffmpeg.channelLayouts());
assertEquals(ChannelLayouts.CHANNEL_LAYOUTS, ffmpeg.channelLayouts());

verify(runFunc, times(1)).run(argThatHasItem("-layouts"));
}
}
128 changes: 128 additions & 0 deletions src/test/java/net/bramp/ffmpeg/fixtures/ChannelLayouts.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package net.bramp.ffmpeg.fixtures;

import com.google.common.collect.ImmutableList;
import net.bramp.ffmpeg.info.ChannelLayout;
import net.bramp.ffmpeg.info.IndividualChannel;
import net.bramp.ffmpeg.info.StandardChannelLayout;

import java.util.*;

/**
* Class that contains all layouts as defined in the unit tests This should not be used as a concise
* list of available layouts, as every install of ffmpeg is different. Call ffmpeg.layouts() to
* discover.
*
* @author euklios
*/
public final class ChannelLayouts {

private ChannelLayouts() {
throw new AssertionError("No instances for you!");
}

private static final IndividualChannel FL = new IndividualChannel("FL", "front left");
private static final IndividualChannel FR = new IndividualChannel("FR", "front right");
private static final IndividualChannel FC = new IndividualChannel("FC", "front center");
private static final IndividualChannel LFE = new IndividualChannel("LFE", "low frequency");
private static final IndividualChannel BL = new IndividualChannel("BL", "back left");
private static final IndividualChannel BR = new IndividualChannel("BR", "back right");
private static final IndividualChannel FLC = new IndividualChannel("FLC", "front left-of-center");
private static final IndividualChannel FRC = new IndividualChannel("FRC", "front right-of-center");
private static final IndividualChannel BC = new IndividualChannel("BC", "back center");
private static final IndividualChannel SL = new IndividualChannel("SL", "side left");
private static final IndividualChannel SR = new IndividualChannel("SR", "side right");
private static final IndividualChannel TC = new IndividualChannel("TC", "top center");
private static final IndividualChannel TFL = new IndividualChannel("TFL", "top front left");
private static final IndividualChannel TFC = new IndividualChannel("TFC", "top front center");
private static final IndividualChannel TFR = new IndividualChannel("TFR", "top front right");
private static final IndividualChannel TBL = new IndividualChannel("TBL", "top back left");
private static final IndividualChannel TBC = new IndividualChannel("TBC", "top back center");
private static final IndividualChannel TBR = new IndividualChannel("TBR", "top back right");
private static final IndividualChannel DL = new IndividualChannel("DL", "downmix left");
private static final IndividualChannel DR = new IndividualChannel("DR", "downmix right");
private static final IndividualChannel WL = new IndividualChannel("WL", "wide left");
private static final IndividualChannel WR = new IndividualChannel("WR", "wide right");
private static final IndividualChannel SDL = new IndividualChannel("SDL", "surround direct left");
private static final IndividualChannel SDR = new IndividualChannel("SDR", "surround direct right");
private static final IndividualChannel LFE2 = new IndividualChannel("LFE2", "low frequency 2");
private static final IndividualChannel TSL = new IndividualChannel("TSL", "top side left");
private static final IndividualChannel TSR = new IndividualChannel("TSR", "top side right");
private static final IndividualChannel BFC = new IndividualChannel("BFC", "bottom front center");
private static final IndividualChannel BFL = new IndividualChannel("BFL", "bottom front left");
private static final IndividualChannel BFR = new IndividualChannel("BFR", "bottom front right");

public static final ImmutableList<ChannelLayout> CHANNEL_LAYOUTS =
new ImmutableList.Builder<ChannelLayout>()
.add(
FL,
FR,
FC,
LFE,
BL,
BR,
FLC,
FRC,
BC,
SL,
SR,
TC,
TFL,
TFC,
TFR,
TBL,
TBC,
TBR,
DL,
DR,
WL,
WR,
SDL,
SDR,
LFE2,
TSL,
TSR,
BFC,
BFL,
BFR,
new StandardChannelLayout("mono", decomposition(FC)),
new StandardChannelLayout("stereo", decomposition(FL, FR)),
new StandardChannelLayout("2.1", decomposition(FL, FR, LFE)),
new StandardChannelLayout("3.0", decomposition(FL, FR, FC)),
new StandardChannelLayout("3.0(back)", decomposition(FL, FR, BC)),
new StandardChannelLayout("4.0", decomposition(FL, FR, FC, BC)),
new StandardChannelLayout("quad", decomposition(FL, FR, BL, BR)),
new StandardChannelLayout("quad(side)", decomposition(FL, FR, SL, SR)),
new StandardChannelLayout("3.1", decomposition(FL, FR, FC, LFE)),
new StandardChannelLayout("5.0", decomposition(FL, FR, FC, BL, BR)),
new StandardChannelLayout("5.0(side)", decomposition(FL, FR, FC, SL, SR)),
new StandardChannelLayout("4.1", decomposition(FL, FR, FC, LFE, BC)),
new StandardChannelLayout("5.1", decomposition(FL, FR, FC, LFE, BL, BR)),
new StandardChannelLayout("5.1(side)", decomposition(FL, FR, FC, LFE, SL, SR)),
new StandardChannelLayout("6.0", decomposition(FL, FR, FC, BC, SL, SR)),
new StandardChannelLayout("6.0(front)", decomposition(FL, FR, FLC, FRC, SL, SR)),
new StandardChannelLayout("hexagonal", decomposition(FL, FR, FC, BL, BR, BC)),
new StandardChannelLayout("6.1", decomposition(FL, FR, FC, LFE, BC, SL, SR)),
new StandardChannelLayout("6.1(back)", decomposition(FL, FR, FC, LFE, BL, BR, BC)),
new StandardChannelLayout("6.1(front)", decomposition(FL, FR, LFE, FLC, FRC, SL, SR)),
new StandardChannelLayout("7.0", decomposition(FL, FR, FC, BL, BR, SL, SR)),
new StandardChannelLayout("7.0(front)", decomposition(FL, FR, FC, FLC, FRC, SL, SR)),
new StandardChannelLayout("7.1", decomposition(FL, FR, FC, LFE, BL, BR, SL, SR)),
new StandardChannelLayout("7.1(wide)", decomposition(FL, FR, FC, LFE, BL, BR, FLC, FRC)),
new StandardChannelLayout("7.1(wide-side)", decomposition(FL, FR, FC, LFE, FLC, FRC, SL, SR)),
new StandardChannelLayout("7.1(top)", decomposition(FL, FR, FC, LFE, BL, BR, TFL, TFR)),
new StandardChannelLayout("octagonal", decomposition(FL, FR, FC, BL, BR, BC, SL, SR)),
new StandardChannelLayout("cube", decomposition(FL, FR, BL, BR, TFL, TFR, TBL, TBR)),
new StandardChannelLayout("hexadecagonal", decomposition(FL, FR, FC, BL, BR, BC, SL, SR, TFL, TFC, TFR, TBL, TBC, TBR, WL, WR)),
new StandardChannelLayout("downmix", decomposition(DL, DR)),
new StandardChannelLayout("22.2", decomposition(FL, FR, FC, LFE, BL, BR, FLC, FRC, BC, SL, SR, TC, TFL, TFC, TFR, TBL, TBC, TBR, LFE2, TSL, TSR, BFC, BFL, BFR))
)
.build();

private static List<IndividualChannel> decomposition(IndividualChannel... channels) {
List<IndividualChannel> decomposition = new ArrayList<>();

Collections.addAll(decomposition, channels);

return Collections.unmodifiableList(decomposition);
}
}
Loading

0 comments on commit 1d11c37

Please sign in to comment.