/*
* PS3 Media Server, for streaming any medias to your PS3.
* Copyright (C) 2008 A.Brochard
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.pms.encoders;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.StringTokenizer;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import net.pms.Messages;
import net.pms.PMS;
import net.pms.configuration.PmsConfiguration;
import net.pms.dlna.DLNAMediaSubtitle;
import net.pms.dlna.DLNAResource;
import net.pms.formats.Format;
import net.pms.formats.v2.SubtitleType;
import net.pms.newgui.GuiUtil;
import net.pms.util.PlayerUtil;
import net.pms.util.ProcessUtil;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*
* This class handles the Windows-specific AviSynth/FFmpeg player combination.
*/
public class AviSynthFFmpeg extends FFMpegVideo {
private static final Logger LOGGER = LoggerFactory.getLogger(AviSynthFFmpeg.class);
public static final String ID = "avsffmpeg";
@Override
public String id() {
return ID;
}
@Override
public String name() {
return "AviSynth/FFmpeg";
}
@Override
public boolean avisynth() {
return true;
}
@Override
public String initialString() {
String threads = "";
if (configuration.isFfmpegAviSynthMultithreading()) {
threads = " -threads " + configuration.getNumberOfCpuCores();
}
return configuration.getMPEG2MainSettingsFFmpeg() + " -ab " + configuration.getAudioBitrate() + "k" + threads;
}
@Override
public JComponent config() {
return config("NetworkTab.5");
}
@Override
public boolean isGPUAccelerationReady() {
return true;
}
public static File getAVSScript(String filename, DLNAMediaSubtitle subTrack) throws IOException {
return getAVSScript(filename, subTrack, -1, -1, null, null, _configuration);
}
/*
* Generate the AviSynth script based on the user's settings
*/
public static File getAVSScript(String filename, DLNAMediaSubtitle subTrack, int fromFrame, int toFrame, String frameRateRatio, String frameRateNumber, PmsConfiguration configuration) throws IOException {
String onlyFileName = filename.substring(1 + filename.lastIndexOf('\\'));
File file = new File(configuration.getTempFolder(), "pms-avs-" + onlyFileName + ".avs");
try (PrintWriter pw = new PrintWriter(new FileOutputStream(file))) {
String numerator;
String denominator;
if (frameRateRatio != null && frameRateNumber != null) {
if (frameRateRatio.equals(frameRateNumber)) {
// No ratio was available
numerator = frameRateRatio;
denominator = "1";
} else {
String[] frameRateNumDen = frameRateRatio.split("/");
numerator = frameRateNumDen[0];
denominator = "1001";
}
} else {
// No framerate was given so we should try the most common one
numerator = "24000";
denominator = "1001";
frameRateNumber = "23.976";
}
String assumeFPS = ".AssumeFPS(" + numerator + "," + denominator + ")";
String directShowFPS = "";
if (!"0".equals(frameRateNumber)) {
directShowFPS = ", fps=" + frameRateNumber;
}
String convertfps = "";
if (configuration.getFfmpegAvisynthConvertFps()) {
convertfps = ", convertfps=true";
}
File f = new File(filename);
if (f.exists()) {
filename = ProcessUtil.getShortFileNameIfWideChars(filename);
}
String movieLine = "DirectShowSource(\"" + filename + "\"" + directShowFPS + convertfps + ")" + assumeFPS;
String mtLine1 = "";
String mtLine2 = "";
String interframeLines = null;
String interframePath = configuration.getInterFramePath();
int Cores = 1;
if (configuration.isFfmpegAviSynthMultithreading()) {
Cores = configuration.getNumberOfCpuCores();
// Goes at the start of the file to initiate multithreading
mtLine1 = "SetMemoryMax(512)\nSetMTMode(3," + Cores + ")\n";
// Goes after the input line to make multithreading more efficient
mtLine2 = "SetMTMode(2)";
}
// True Motion
if (configuration.getFfmpegAvisynthInterFrame()) {
String GPU = "";
movieLine += ".ConvertToYV12()";
// Enable GPU to assist with CPU
if (configuration.getFfmpegAvisynthInterFrameGPU() && interframegpu.isEnabled()){
GPU = ", GPU=true";
}
interframeLines = "\n" +
"PluginPath = \"" + interframePath + "\"\n" +
"LoadPlugin(PluginPath+\"svpflow1.dll\")\n" +
"LoadPlugin(PluginPath+\"svpflow2.dll\")\n" +
"Import(PluginPath+\"InterFrame2.avsi\")\n" +
"InterFrame(Cores=" + Cores + GPU + ", Preset=\"Faster\")\n";
}
String subLine = null;
if (subTrack != null && configuration.isAutoloadExternalSubtitles() && !configuration.isDisableSubtitles()) {
if (subTrack.getExternalFile() != null) {
LOGGER.info("AviSynth script: Using subtitle track: " + subTrack);
String function = "TextSub";
if (subTrack.getType() == SubtitleType.VOBSUB) {
function = "VobSub";
}
subLine = function + "(\"" + ProcessUtil.getShortFileNameIfWideChars(subTrack.getExternalFile().getAbsolutePath()) + "\")";
}
}
ArrayList<String> lines = new ArrayList<>();
lines.add(mtLine1);
boolean fullyManaged = false;
String script = "<movie>\n<sub>\n";
StringTokenizer st = new StringTokenizer(script, PMS.AVS_SEPARATOR);
while (st.hasMoreTokens()) {
String line = st.nextToken();
if (line.contains("<movie") || line.contains("<sub")) {
fullyManaged = true;
}
lines.add(line);
}
if (configuration.getFfmpegAvisynthInterFrame()) {
lines.add(mtLine2);
lines.add(interframeLines);
}
if (fullyManaged) {
for (String s : lines) {
if (s.contains("<moviefilename>")) {
s = s.replace("<moviefilename>", filename);
}
s = s.replace("<movie>", movieLine);
s = s.replace("<sub>", subLine != null ? subLine : "#");
pw.println(s);
}
} else {
pw.println(movieLine);
if (subLine != null) {
pw.println(subLine);
}
pw.println("clip");
}
}
file.deleteOnExit();
return file;
}
private JCheckBox multithreading;
private JCheckBox interframe;
private static JCheckBox interframegpu;
private JCheckBox convertfps;
@Override
protected JComponent config(String languageLabel) {
FormLayout layout = new FormLayout(
"left:pref, 0:grow",
"p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu");
PanelBuilder builder = new PanelBuilder(layout);
builder.border(Borders.EMPTY);
builder.opaque(false);
CellConstraints cc = new CellConstraints();
JComponent cmp = builder.addSeparator(Messages.getString(languageLabel), cc.xyw(2, 1, 1));
cmp = (JComponent) cmp.getComponent(0);
cmp.setFont(cmp.getFont().deriveFont(Font.BOLD));
multithreading = new JCheckBox(Messages.getString("MEncoderVideo.35"), configuration.isFfmpegAviSynthMultithreading());
multithreading.setContentAreaFilled(false);
multithreading.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
configuration.setFfmpegAviSynthMultithreading(e.getStateChange() == ItemEvent.SELECTED);
}
});
builder.add(GuiUtil.getPreferredSizeComponent(multithreading), cc.xy(2, 3));
interframe = new JCheckBox(Messages.getString("AviSynthMEncoder.13"), configuration.getFfmpegAvisynthInterFrame());
interframe.setContentAreaFilled(false);
interframe.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
configuration.setFfmpegAvisynthInterFrame(interframe.isSelected());
if (configuration.getFfmpegAvisynthInterFrame()) {
JOptionPane.showMessageDialog(
SwingUtilities.getWindowAncestor((Component) PMS.get().getFrame()),
Messages.getString("AviSynthMEncoder.16"),
Messages.getString("Dialog.Information"),
JOptionPane.INFORMATION_MESSAGE
);
}
}
});
builder.add(GuiUtil.getPreferredSizeComponent(interframe), cc.xy(2, 5));
interframegpu = new JCheckBox(Messages.getString("AviSynthMEncoder.15"), configuration.getFfmpegAvisynthInterFrameGPU());
interframegpu.setContentAreaFilled(false);
interframegpu.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
configuration.setFfmpegAvisynthInterFrameGPU((e.getStateChange() == ItemEvent.SELECTED));
}
});
builder.add(GuiUtil.getPreferredSizeComponent(interframegpu), cc.xy(2, 7));
convertfps = new JCheckBox(Messages.getString("AviSynthMEncoder.3"), configuration.getFfmpegAvisynthConvertFps());
convertfps.setContentAreaFilled(false);
convertfps.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
configuration.setFfmpegAvisynthConvertFps((e.getStateChange() == ItemEvent.SELECTED));
}
});
builder.add(GuiUtil.getPreferredSizeComponent(convertfps), cc.xy(2, 9));
configuration.addConfigurationListener(new ConfigurationListener() {
@Override
public void configurationChanged(ConfigurationEvent event) {
if (event.getPropertyName() == null) {
return;
}
if ((!event.isBeforeUpdate()) && event.getPropertyName().equals(PmsConfiguration.KEY_GPU_ACCELERATION)) {
interframegpu.setEnabled(configuration.isGPUAcceleration());
}
}
});
return builder.getPanel();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCompatible(DLNAResource resource) {
Format format = resource.getFormat();
if (format != null) {
if (format.getIdentifier() == Format.Identifier.WEB) {
return false;
}
}
DLNAMediaSubtitle subtitle = resource.getMediaSubtitle();
// Check whether the subtitle actually has a language defined,
// uninitialized DLNAMediaSubtitle objects have a null language.
if (subtitle != null && subtitle.getLang() != null) {
// The resource needs a subtitle, but this engine implementation does not support subtitles yet
return false;
}
try {
String audioTrackName = resource.getMediaAudio().toString();
String defaultAudioTrackName = resource.getMedia().getAudioTracksList().get(0).toString();
if (!audioTrackName.equals(defaultAudioTrackName)) {
// This engine implementation only supports playback of the default audio track at this time
return false;
}
} catch (NullPointerException e) {
LOGGER.trace("AviSynth/FFmpeg cannot determine compatibility based on audio track for " + resource.getSystemName());
} catch (IndexOutOfBoundsException e) {
LOGGER.trace("AviSynth/FFmpeg cannot determine compatibility based on default audio track for " + resource.getSystemName());
}
if (
PlayerUtil.isVideo(resource, Format.Identifier.MKV) ||
PlayerUtil.isVideo(resource, Format.Identifier.MPG)
) {
return true;
}
return false;
}
}