/*
* 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 net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.dlna.DLNAMediaAudio;
import net.pms.dlna.DLNAMediaInfo;
import net.pms.dlna.DLNAMediaSubtitle;
import net.pms.dlna.DLNAResource;
import net.pms.external.ExternalFactory;
import net.pms.external.ExternalListener;
import net.pms.external.FinalizeTranscoderArgsListener;
import net.pms.formats.Format;
import net.pms.io.OutputParams;
import net.pms.io.ProcessWrapper;
import net.pms.util.FileUtil;
import net.pms.util.Iso639;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
public abstract class Player {
private static final Logger logger = LoggerFactory.getLogger(Player.class);
public abstract PlayerPurpose getPurpose();
public abstract JComponent config();
public abstract String id();
public abstract String name();
public abstract int type();
// FIXME this is an implementation detail (and not a very good one).
// it's entirely up to engines how they construct their command lines.
// need to get rid of this
@Deprecated
public abstract String[] args();
public abstract String mimeType();
public boolean isNative() {
return false;
}
public abstract String executable();
private static List<FinalizeTranscoderArgsListener> finalizeTranscoderArgsListeners =
new ArrayList<FinalizeTranscoderArgsListener>();
public static void initializeFinalizeTranscoderArgsListeners() {
for (ExternalListener listener : ExternalFactory.getExternalListeners()) {
if (listener instanceof FinalizeTranscoderArgsListener) {
finalizeTranscoderArgsListeners.add((FinalizeTranscoderArgsListener) listener);
}
}
}
@Deprecated
public boolean avisynth() {
return false;
}
public boolean excludeFormat(Format extension) {
return false;
}
public boolean isPlayerCompatible(RendererConfiguration renderer) {
return true;
}
public boolean isInternalSubtitlesSupported() {
return true;
}
public boolean isExternalSubtitlesSupported() {
return true;
}
public boolean isTimeSeekable() {
return false;
}
/**
* Each engine capable of video hardware acceleration must
* override this method and set
* <p>
* <code>return true</code>.
* @return false
*/
public boolean isVideoHardwareAccelerationReady() {
return false;
}
/**
* @deprecated Use {@link #launchTranscode(net.pms.dlna.DLNAResource, net.pms.dlna.DLNAMediaInfo, net.pms.io.OutputParams)} instead.
*/
@Deprecated
public ProcessWrapper launchTranscode(
String filename,
DLNAResource dlna,
DLNAMediaInfo media,
OutputParams params
) throws IOException {
return launchTranscode(dlna, media, params);
}
public abstract ProcessWrapper launchTranscode(
DLNAResource dlna,
DLNAMediaInfo media,
OutputParams params
) throws IOException;
@Override
public String toString() {
return name();
}
// no need to pass Player as a parameter: it's the invocant
@Deprecated
protected String[] finalizeTranscoderArgs(
Player player,
String filename,
DLNAResource dlna,
DLNAMediaInfo media,
OutputParams params,
String[] cmdArgs
) {
return finalizeTranscoderArgs(
filename,
dlna,
media,
params,
cmdArgs
);
}
protected String[] finalizeTranscoderArgs(
String filename,
DLNAResource dlna,
DLNAMediaInfo media,
OutputParams params,
String[] cmdArgs
) {
if (finalizeTranscoderArgsListeners.isEmpty()) {
return cmdArgs;
} else {
// make it mutable
List<String> cmdList = new ArrayList<String>(Arrays.asList(cmdArgs));
for (FinalizeTranscoderArgsListener listener : finalizeTranscoderArgsListeners) {
try {
cmdList = listener.finalizeTranscoderArgs(
this,
filename,
dlna,
media,
params,
cmdList
);
} catch (Throwable t) {
logger.error(String.format("Failed to call finalizeTranscoderArgs on listener of type=%s", listener.getClass()), t);
}
}
String[] cmdArray = new String[ cmdList.size() ];
cmdList.toArray(cmdArray);
return cmdArray;
}
}
/**
* This method populates the supplied {@link OutputParams} object with the correct audio track (aid)
* and subtitles (sid), based on the given filename, its MediaInfo metadata and PMS configuration settings.
*
* @param fileName
* The file name used to determine the availability of subtitles.
* @param media
* The MediaInfo metadata for the file.
* @param params
* The parameters to populate.
* @param configuration
* The PMS configuration settings.
*/
// FIXME this code is almost unreadable in its current form and should be broken down into separate methods
// that handle just one facet of its functionality. it also needs to be decoupled from MEncoder
public void setAudioAndSubs(String fileName, DLNAMediaInfo media, OutputParams params, PmsConfiguration configuration) {
if (params.aid == null && media != null) {
// check for preferred audio
StringTokenizer st = new StringTokenizer(configuration.getAudioLanguages(), ",");
while (st != null && st.hasMoreTokens()) {
String lang = st.nextToken();
lang = lang.trim();
logger.trace("Looking for an audio track with lang: " + lang);
for (DLNAMediaAudio audio : media.getAudioTracksList()) {
if (audio.matchCode(lang)) {
params.aid = audio;
logger.trace("Matched audio track: " + audio);
st = null;
break;
}
}
}
}
if (params.aid == null && media.getAudioTracksList().size() > 0) {
// take a default audio track, dts first if possible
for (DLNAMediaAudio audio : media.getAudioTracksList()) {
if (audio.isDTS()) {
params.aid = audio;
logger.trace("Found priority audio track with DTS: " + audio);
break;
}
}
if (params.aid == null) {
params.aid = media.getAudioTracksList().get(0);
logger.trace("Chose a default audio track: " + params.aid);
}
}
String currentLang = null;
DLNAMediaSubtitle matchedSub = null;
if (params.aid != null) {
currentLang = params.aid.getLang();
}
if (params.sid != null && params.sid.getId() == -1) {
logger.trace("Don't want subtitles!");
params.sid = null;
return;
}
StringTokenizer st1 = new StringTokenizer(configuration.getAudioSubLanguages(), ";");
while (st1.hasMoreTokens()) {
String pair = st1.nextToken();
if (pair.contains(",")) {
String audio = pair.substring(0, pair.indexOf(","));
String sub = pair.substring(pair.indexOf(",") + 1);
audio = audio.trim();
sub = sub.trim();
logger.trace("Searching for a match for: " + currentLang + " with " + audio + " and " + sub);
if (Iso639.isCodesMatching(audio, currentLang) || (currentLang != null && audio.equals("*"))) {
if (sub.equals("off")) {
matchedSub = new DLNAMediaSubtitle();
matchedSub.setLang("off");
} else {
for (DLNAMediaSubtitle present_sub : media.getSubtitleTracksList()) {
if (present_sub.matchCode(sub) || sub.equals("*")) {
matchedSub = present_sub;
logger.trace("Found a match: " + matchedSub);
break;
}
}
}
if (matchedSub != null) {
break;
}
}
}
}
if (matchedSub != null && params.sid == null) {
if (configuration.isDisableSubtitles() || (matchedSub.getLang() != null && matchedSub.getLang().equals("off"))) {
logger.trace(" Disabled the subtitles: " + matchedSub);
} else {
params.sid = matchedSub;
}
}
if (!configuration.isDisableSubtitles() && params.sid == null && media != null) {
// Check for subtitles again
File video = new File(fileName);
FileUtil.isSubtitlesExists(video, media, false);
if (configuration.isAutoloadExternalSubtitles()) {
boolean forcedSubsFound = false;
// Priority to external subtitles
for (DLNAMediaSubtitle sub : media.getSubtitleTracksList()) {
if (matchedSub !=null && matchedSub.getLang() !=null && matchedSub.getLang().equals("off")) {
StringTokenizer st = new StringTokenizer(configuration.getForcedSubtitleTags(), ",");
while (st != null && sub.getFlavor() != null && st.hasMoreTokens()) {
String forcedTags = st.nextToken();
forcedTags = forcedTags.trim();
if (sub.getFlavor().toLowerCase().indexOf(forcedTags) > -1
&& Iso639.isCodesMatching(sub.getLang(), configuration.getForcedSubtitleLanguage())) {
logger.trace("Forcing preferred subtitles : " + sub.getLang() + "/" + sub.getFlavor());
logger.trace("Forced subtitles track : " + sub);
if (sub.getExternalFile() != null) {
logger.trace("Found external forced file : " + sub.getExternalFile().getAbsolutePath());
}
params.sid = sub;
forcedSubsFound = true;
break;
}
}
if (forcedSubsFound == true) {
break;
}
} else {
logger.trace("Found subtitles track: " + sub);
if (sub.getExternalFile() != null) {
logger.trace("Found external file: " + sub.getExternalFile().getAbsolutePath());
params.sid = sub;
break;
}
}
}
}
if (
matchedSub != null &&
matchedSub.getLang() != null &&
matchedSub.getLang().equals("off")
) {
return;
}
if (params.sid == null) {
StringTokenizer st = new StringTokenizer(configuration.getSubtitlesLanguages(), ",");
while (st != null && st.hasMoreTokens()) {
String lang = st.nextToken();
lang = lang.trim();
logger.trace("Looking for a subtitle track with lang: " + lang);
for (DLNAMediaSubtitle sub : media.getSubtitleTracksList()) {
if (sub.matchCode(lang)) {
params.sid = sub;
logger.trace("Matched sub track: " + params.sid);
st = null;
break;
}
}
}
}
}
}
/**
* Returns whether or not the player can handle a given resource.
* If the resource is <code>null</code> compatibility cannot be
* determined and <code>false</code> will be returned.
*
* @param resource
* The {@link DLNAResource} to be matched.
* @return True when the resource can be handled, false otherwise.
* @since 1.60.0
*/
public abstract boolean isCompatible(DLNAResource resource);
}