use of net.pms.configuration.PmsConfiguration in project UniversalMediaServer by UniversalMediaServer.
the class DLNAResource method discoverWithRenderer.
protected final void discoverWithRenderer(RendererConfiguration renderer, int count, boolean forced, String searchStr) {
PmsConfiguration configurationSpecificToRenderer = PMS.getConfiguration(renderer);
// Discover children if it hasn't been done already
if (!isDiscovered()) {
if (configurationSpecificToRenderer.getFolderLimit() && depthLimit()) {
if (renderer.isPS3() || renderer.isXbox360()) {
LOGGER.info("Depth limit potentionally hit for " + getDisplayName());
}
if (defaultRenderer != null) {
defaultRenderer.addFolderLimit(this);
}
}
discoverChildren(searchStr);
boolean ready;
if (renderer.isUseMediaInfo() && renderer.isDLNATreeHack()) {
ready = analyzeChildren(count);
} else {
ready = analyzeChildren(-1);
}
if (!renderer.isUseMediaInfo() || ready) {
setDiscovered(true);
}
notifyRefresh();
} else {
// if forced, then call the old 'refreshChildren' method
LOGGER.trace("discover {} refresh forced: {}", getResourceId(), forced);
/*if (forced && shouldRefresh(searchStr)) {
doRefreshChildren(searchStr);
notifyRefresh();
} */
if (forced) {
// (refreshChildren is not overridden in MapFile)
if (refreshChildren(searchStr)) {
notifyRefresh();
}
} else {
// if not, then the regular isRefreshNeeded/doRefreshChildren pair.
if (shouldRefresh(searchStr)) {
doRefreshChildren(searchStr);
notifyRefresh();
}
}
}
}
use of net.pms.configuration.PmsConfiguration in project UniversalMediaServer by UniversalMediaServer.
the class DLNAResource method resolvePlayer.
/**
* Determine whether we are a candidate for streaming or transcoding to the
* given renderer, and return the relevant player or null as appropriate.
*
* @param renderer The target renderer
* @return A player if transcoding or null if streaming
*/
public Player resolvePlayer(RendererConfiguration renderer) {
// Use device-specific pms conf, if any
PmsConfiguration configurationSpecificToRenderer = PMS.getConfiguration(renderer);
boolean parserV2 = media != null && renderer != null && renderer.isUseMediaInfo();
Player resolvedPlayer = null;
if (media == null) {
media = new DLNAMediaInfo();
}
if (format == null) {
// Shouldn't happen, this is just a desperate measure
Format f = FormatFactory.getAssociatedFormat(getSystemName());
setFormat(f != null ? f : FormatFactory.getAssociatedFormat(".mpg"));
}
// Check if we're a transcode folder item
if (isNoName() && (getParent() instanceof FileTranscodeVirtualFolder)) {
// Yes, leave everything as-is
resolvedPlayer = getPlayer();
LOGGER.trace("Selecting player {} based on transcode item settings", resolvedPlayer);
return resolvedPlayer;
}
boolean hasSubsToTranscode = false;
boolean hasEmbeddedSubs = false;
for (DLNAMediaSubtitle s : media.getSubtitleTracksList()) {
hasEmbeddedSubs = (hasEmbeddedSubs || s.isEmbedded());
}
/**
* At this stage, we know the media is compatible with the renderer based on its
* "Supported" lines, and can therefore be streamed to the renderer without a
* player. However, other details about the media can change this, such as
* whether it has subtitles that match this user's language settings, so here we
* perform those checks.
*/
if (format.isVideo() && !configurationSpecificToRenderer.isDisableSubtitles()) {
if (hasEmbeddedSubs || hasExternalSubtitles()) {
OutputParams params = new OutputParams(configurationSpecificToRenderer);
// set proper subtitles in accordance with user setting
Player.setAudioAndSubs(getSystemName(), media, params);
if (params.sid != null) {
if (params.sid.isExternal()) {
if (renderer != null && renderer.isExternalSubtitlesFormatSupported(params.sid, media)) {
media_subtitle = params.sid;
media_subtitle.setSubsStreamable(true);
LOGGER.trace("This video has external subtitles that could be streamed");
} else {
hasSubsToTranscode = true;
LOGGER.trace("This video has external subtitles that should be transcoded");
}
} else if (params.sid.isEmbedded()) {
if (renderer != null && renderer.isEmbeddedSubtitlesFormatSupported(params.sid)) {
LOGGER.trace("This video has embedded subtitles that could be streamed");
} else {
hasSubsToTranscode = true;
LOGGER.trace("This video has embedded subtitles that should be transcoded");
}
}
}
} else {
LOGGER.trace("This video does not have subtitles");
}
}
if (configurationSpecificToRenderer.isDisableTranscoding()) {
LOGGER.trace("Final verdict: \"{}\" will be streamed since transcoding is disabled", getName());
return null;
}
String configurationSkipExtensions = configurationSpecificToRenderer.getDisableTranscodeForExtensions();
String rendererSkipExtensions = renderer == null ? null : renderer.getStreamedExtensions();
// Should transcoding be skipped for this format?
skipTranscode = format.skip(configurationSkipExtensions, rendererSkipExtensions);
if (skipTranscode) {
LOGGER.trace("Final verdict: \"{}\" will be streamed since it is forced by configuration", getName());
return null;
}
// Try to match a player based on media information and format.
resolvedPlayer = PlayerFactory.getPlayer(this);
if (resolvedPlayer != null) {
String configurationForceExtensions = configurationSpecificToRenderer.getForceTranscodeForExtensions();
String rendererForceExtensions = null;
if (renderer != null) {
rendererForceExtensions = renderer.getTranscodedExtensions();
}
// Should transcoding be forced for this format?
boolean forceTranscode = format.skip(configurationForceExtensions, rendererForceExtensions);
boolean isIncompatible = false;
String prependTraceReason = "File \"{}\" will not be streamed because ";
if (forceTranscode) {
LOGGER.trace(prependTraceReason + "transcoding is forced by configuration", getName());
} else if (this instanceof DVDISOTitle) {
forceTranscode = true;
LOGGER.trace("DVD video track \"{}\" will be transcoded because streaming isn't supported", getName());
} else if (!format.isCompatible(media, renderer)) {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "it is not supported by the renderer", getName());
} else if (configurationSpecificToRenderer.isEncodedAudioPassthrough()) {
if (getMediaAudio() != null && (FormatConfiguration.AC3.equals(getMediaAudio().getAudioCodec()) || FormatConfiguration.DTS.equals(getMediaAudio().getAudioCodec()))) {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "the audio will use the encoded audio passthrough feature", getName());
} else {
for (DLNAMediaAudio audioTrack : media.getAudioTracksList()) {
if (audioTrack != null && (FormatConfiguration.AC3.equals(audioTrack.getAudioCodec()) || FormatConfiguration.DTS.equals(audioTrack.getAudioCodec()))) {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "the audio will use the encoded audio passthrough feature", getName());
break;
}
}
}
}
if (!isIncompatible && format.isVideo() && parserV2 && renderer != null) {
int maxBandwidth = renderer.getMaxBandwidth();
if (renderer.isKeepAspectRatio() && !"16:9".equals(media.getAspectRatioContainer())) {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "the renderer needs us to add borders to change the aspect ratio from {} to 16/9.", getName(), media.getAspectRatioContainer());
} else if (!renderer.isResolutionCompatibleWithRenderer(media.getWidth(), media.getHeight())) {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "the resolution is incompatible with the renderer.", getName());
} else if (media.getBitrate() > maxBandwidth) {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "the bitrate ({} b/s) is too high ({} b/s).", getName(), media.getBitrate(), maxBandwidth);
} else if (!renderer.isVideoBitDepthSupported(media.getVideoBitDepth())) {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "the video bit depth ({}) is not supported.", getName(), media.getVideoBitDepth());
} else if (renderer.isH264Level41Limited() && media.isH264()) {
if (media.getAvcLevel() != null) {
double h264Level = 4.1;
try {
h264Level = Double.parseDouble(media.getAvcLevel());
} catch (NumberFormatException e) {
LOGGER.trace("Could not convert {} to double: {}", media.getAvcLevel(), e.getMessage());
}
if (h264Level > 4.1) {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "the H.264 level ({}) is not supported.", getName(), h264Level);
}
} else {
isIncompatible = true;
LOGGER.trace(prependTraceReason + "the H.264 level is unknown.", getName());
}
} else if (media.is3d() && StringUtils.isNotBlank(renderer.getOutput3DFormat()) && (!media.get3DLayout().toString().toLowerCase(Locale.ROOT).equals(renderer.getOutput3DFormat()))) {
forceTranscode = true;
LOGGER.trace("Video \"{}\" is 3D and is forced to transcode to the format \"{}\"", getName(), renderer.getOutput3DFormat());
}
}
// Prefer transcoding over streaming if:
// 1) the media is unsupported by the renderer, or
// 2) there are subs to transcode
boolean preferTranscode = isIncompatible || hasSubsToTranscode;
// 2) transcoding is preferred and not prevented by configuration
if (forceTranscode || (preferTranscode && !isSkipTranscode())) {
if (parserV2) {
LOGGER.trace("Final verdict: \"{}\" will be transcoded with player \"{}\" with mime type \"{}\"", getName(), resolvedPlayer.toString(), renderer != null ? renderer.getMimeType(mimeType(resolvedPlayer), media) : media.getMimeType());
} else {
LOGGER.trace("Final verdict: \"{}\" will be transcoded with player \"{}\"", getName(), resolvedPlayer.toString());
}
} else {
resolvedPlayer = null;
LOGGER.trace("Final verdict: \"{}\" will be streamed", getName());
}
} else {
LOGGER.trace("Final verdict: \"{}\" will be streamed because no compatible player was found", getName());
}
return resolvedPlayer;
}
use of net.pms.configuration.PmsConfiguration in project UniversalMediaServer by UniversalMediaServer.
the class DLNAResource method getInputStream.
/**
* Returns an InputStream of this DLNAResource that starts at a given time, if possible. Very useful if video chapters are being used.
*
* @param range
* @param mediarenderer
* @return The inputstream
* @throws IOException
*/
public synchronized InputStream getInputStream(Range range, RendererConfiguration mediarenderer) throws IOException {
// Use device-specific pms conf, if any
PmsConfiguration configurationSpecificToRenderer = PMS.getConfiguration(mediarenderer);
LOGGER.trace("Asked stream chunk : " + range + " of " + getName() + " and player " + player);
// shagrath: small fix, regression on chapters
boolean timeseek_auto = false;
// Ditlew - WDTV Live
// Ditlew - We convert byteoffset to timeoffset here. This needs the stream to be CBR!
int cbr_video_bitrate = mediarenderer.getCBRVideoBitrate();
long low = range.isByteRange() && range.isStartOffsetAvailable() ? range.asByteRange().getStart() : 0;
long high = range.isByteRange() && range.isEndLimitAvailable() ? range.asByteRange().getEnd() : -1;
Range.Time timeRange = range.createTimeRange();
if (player != null && low > 0 && cbr_video_bitrate > 0) {
// 1.04 = container overhead
int used_bit_rated = (int) ((cbr_video_bitrate + 256) * 1024 / (double) 8 * 1.04);
if (low > used_bit_rated) {
timeRange.setStart(low / (double) (used_bit_rated));
low = 0;
// asking for an invalid offset which kills MEncoder
if (timeRange.getStartOrZero() > media.getDurationInSeconds()) {
return null;
}
// Should we rewind a little (in case our overhead isn't accurate enough)
int rewind_secs = mediarenderer.getByteToTimeseekRewindSeconds();
timeRange.rewindStart(rewind_secs);
// shagrath:
timeseek_auto = true;
}
}
if (low > 0 && media.getBitrate() > 0) {
lastStartPosition = (low * 8) / media.getBitrate();
LOGGER.trace("Estimating seek position from byte range:");
LOGGER.trace(" media.getBitrate: " + media.getBitrate());
LOGGER.trace(" low: " + low);
LOGGER.trace(" lastStartPosition: " + lastStartPosition);
} else {
lastStartPosition = timeRange.getStartOrZero();
LOGGER.trace("Setting lastStartPosition from time-seeking: " + lastStartPosition);
}
// Determine source of the stream
if (player == null && !isResume()) {
// No transcoding
if (this instanceof IPushOutput) {
PipedOutputStream out = new PipedOutputStream();
InputStream fis = new PipedInputStream(out);
((IPushOutput) this).push(out);
if (low > 0) {
fis.skip(low);
}
// http://www.ps3mediaserver.org/forum/viewtopic.php?f=11&t=12035
lastStartSystemTime = System.currentTimeMillis();
return wrap(fis, high, low);
}
InputStream fis = getInputStream();
if (fis != null) {
if (low > 0) {
fis.skip(low);
}
// http://www.ps3mediaserver.org/forum/viewtopic.php?f=11&t=12035
fis = wrap(fis, high, low);
if (timeRange.getStartOrZero() > 0 && this instanceof RealFile) {
fis.skip(MpegUtil.getPositionForTimeInMpeg(((RealFile) this).getFile(), (int) timeRange.getStartOrZero()));
}
}
lastStartSystemTime = System.currentTimeMillis();
return fis;
} else {
// Pipe transcoding result
OutputParams params = new OutputParams(configurationSpecificToRenderer);
params.aid = getMediaAudio();
params.sid = media_subtitle;
params.header = getHeaders();
params.mediaRenderer = mediarenderer;
timeRange.limit(getSplitRange());
params.timeseek = timeRange.getStartOrZero();
params.timeend = timeRange.getEndOrZero();
params.shift_scr = timeseek_auto;
if (this instanceof IPushOutput) {
params.stdin = (IPushOutput) this;
}
if (resume != null) {
if (range.isTimeRange()) {
resume.update((Range.Time) range, this);
}
params.timeseek = resume.getTimeOffset() / 1000;
if (player == null) {
player = new FFMpegVideo();
}
}
if (System.currentTimeMillis() - lastStartSystemTime < 500) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
LOGGER.error(null, e);
}
}
// (Re)start transcoding process if necessary
if (externalProcess == null || externalProcess.isDestroyed()) {
// First playback attempt => start new transcoding process
LOGGER.debug("Starting transcode/remux of " + getName() + " with media info: " + media);
lastStartSystemTime = System.currentTimeMillis();
externalProcess = player.launchTranscode(this, media, params);
if (params.waitbeforestart > 0) {
LOGGER.trace("Sleeping for {} milliseconds", params.waitbeforestart);
try {
Thread.sleep(params.waitbeforestart);
} catch (InterruptedException e) {
LOGGER.error(null, e);
}
LOGGER.trace("Finished sleeping for " + params.waitbeforestart + " milliseconds");
}
} else if (params.timeseek > 0 && media != null && media.isMediaparsed() && media.getDurationInSeconds() > 0) {
// Time seek request => stop running transcode process and start a new one
LOGGER.debug("Requesting time seek: " + params.timeseek + " seconds");
params.minBufferSize = 1;
Runnable r = new Runnable() {
@Override
public void run() {
externalProcess.stopProcess();
}
};
new Thread(r, "External Process Stopper").start();
lastStartSystemTime = System.currentTimeMillis();
ProcessWrapper newExternalProcess = player.launchTranscode(this, media, params);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.error(null, e);
}
if (newExternalProcess == null) {
LOGGER.trace("External process instance is null... sounds not good");
}
externalProcess = newExternalProcess;
}
if (externalProcess == null) {
return null;
}
InputStream is = null;
int timer = 0;
while (is == null && timer < 10) {
is = externalProcess.getInputStream(low);
timer++;
if (is == null) {
LOGGER.debug("External input stream instance is null... sounds not good, waiting 500ms");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
// instead of exiting
if (is == null && !externalProcess.isDestroyed()) {
Runnable r = new Runnable() {
@Override
public void run() {
LOGGER.error("External input stream instance is null... stopping process");
externalProcess.stopProcess();
}
};
new Thread(r, "Hanging External Process Stopper").start();
}
return is;
}
}
use of net.pms.configuration.PmsConfiguration in project UniversalMediaServer by UniversalMediaServer.
the class MEncoderVideo method launchTranscode.
@Override
public ProcessWrapper launchTranscode(DLNAResource dlna, DLNAMediaInfo media, OutputParams params) throws IOException {
// Use device-specific pms conf
PmsConfiguration prev = configuration;
configuration = (DeviceConfiguration) params.mediaRenderer;
params.manageFastStart();
boolean avisynth = avisynth();
final String filename = dlna.getFileName();
setAudioAndSubs(filename, media, params);
String externalSubtitlesFileName = null;
if (params.sid != null && params.sid.isExternal()) {
if (params.sid.isExternalFileUtf16()) {
// convert UTF-16 -> UTF-8
File convertedSubtitles = new File(configuration.getTempFolder(), "utf8_" + params.sid.getExternalFile().getName());
FileUtil.convertFileFromUtf16ToUtf8(params.sid.getExternalFile(), convertedSubtitles);
externalSubtitlesFileName = ProcessUtil.getShortFileNameIfWideChars(convertedSubtitles.getAbsolutePath());
} else {
externalSubtitlesFileName = ProcessUtil.getShortFileNameIfWideChars(params.sid.getExternalFile().getAbsolutePath());
}
}
InputFile newInput = new InputFile();
newInput.setFilename(filename);
newInput.setPush(params.stdin);
boolean isDVD = dlna instanceof DVDISOTitle;
ovccopy = false;
pcm = false;
ac3Remux = false;
dtsRemux = false;
wmv = false;
int intOCW = 0;
int intOCH = 0;
try {
intOCW = Integer.parseInt(configuration.getMencoderOverscanCompensationWidth());
} catch (NumberFormatException e) {
LOGGER.error("Cannot parse configured MEncoder overscan compensation width: \"{}\"", configuration.getMencoderOverscanCompensationWidth());
}
try {
intOCH = Integer.parseInt(configuration.getMencoderOverscanCompensationHeight());
} catch (NumberFormatException e) {
LOGGER.error("Cannot parse configured MEncoder overscan compensation height: \"{}\"", configuration.getMencoderOverscanCompensationHeight());
}
/*
* Check if the video track and the container report different aspect ratios
*/
boolean aspectRatiosMatch = true;
if (media.getAspectRatioContainer() != null && media.getAspectRatioVideoTrack() != null && !media.getAspectRatioContainer().equals(media.getAspectRatioVideoTrack())) {
aspectRatiosMatch = false;
}
// Decide whether to defer to tsMuxeR or continue to use MEncoder
boolean deferToTsmuxer = true;
String prependTraceReason = "Not muxing the video stream with tsMuxeR via MEncoder because ";
if (!configuration.isMencoderMuxWhenCompatible()) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the user setting is disabled");
}
if (deferToTsmuxer == true && configuration.isShowTranscodeFolder() && dlna.isNoName() && (dlna.getParent() instanceof FileTranscodeVirtualFolder)) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the file is being played via a MEncoder entry in the transcode folder.");
}
if (deferToTsmuxer == true && !params.mediaRenderer.isMuxH264MpegTS()) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the renderer does not support H.264 inside MPEG-TS.");
}
if (deferToTsmuxer == true && params.sid != null) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "we need to burn subtitles.");
}
if (deferToTsmuxer == true && isDVD) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "this is a DVD track.");
}
if (deferToTsmuxer == true && avisynth()) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "we are using AviSynth.");
}
if (deferToTsmuxer == true && params.mediaRenderer.isH264Level41Limited() && !media.isVideoWithinH264LevelLimits(newInput, params.mediaRenderer)) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the video stream is not within H.264 level limits for this renderer.");
}
if (deferToTsmuxer == true && !media.isMuxable(params.mediaRenderer)) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the video stream is not muxable to this renderer");
}
if (deferToTsmuxer == true && intOCW > 0 && intOCH > 0) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "we need to transcode to apply overscan compensation.");
}
if (deferToTsmuxer == true && !aspectRatiosMatch) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "we need to transcode to apply the correct aspect ratio.");
}
if (deferToTsmuxer == true && !params.mediaRenderer.isPS3() && media.isWebDl(filename, params)) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the version of tsMuxeR supported by this renderer does not support WEB-DL files.");
}
if (deferToTsmuxer == true && "bt.601".equals(media.getMatrixCoefficients())) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the colorspace probably isn't supported by the renderer.");
}
if (deferToTsmuxer == true && (params.mediaRenderer.isKeepAspectRatio() || params.mediaRenderer.isKeepAspectRatioTranscoding()) && !"16:9".equals(media.getAspectRatioContainer())) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the renderer needs us to add borders so it displays the correct aspect ratio of " + media.getAspectRatioContainer() + ".");
}
if (deferToTsmuxer == true && !params.mediaRenderer.isResolutionCompatibleWithRenderer(media.getWidth(), media.getHeight())) {
deferToTsmuxer = false;
LOGGER.trace(prependTraceReason + "the resolution is incompatible with the renderer.");
}
if (deferToTsmuxer) {
String[] expertOptions = getSpecificCodecOptions(configuration.getMencoderCodecSpecificConfig(), media, params, filename, externalSubtitlesFileName, configuration.isMencoderIntelligentSync(), false);
boolean nomux = false;
for (String s : expertOptions) {
if (s.equals("-nomux")) {
nomux = true;
}
}
if (!nomux) {
TsMuxeRVideo tv = new TsMuxeRVideo();
params.forceFps = media.getValidFps(false);
if (media.getCodecV() != null) {
if (media.isH264()) {
params.forceType = "V_MPEG4/ISO/AVC";
} else if (media.getCodecV().startsWith("mpeg2")) {
params.forceType = "V_MPEG-2";
} else if (media.getCodecV().equals("vc1")) {
params.forceType = "V_MS/VFW/WVC1";
}
}
return tv.launchTranscode(dlna, media, params);
}
} else if (params.sid == null && isDVD && configuration.isMencoderRemuxMPEG2() && params.mediaRenderer.isMpeg2Supported()) {
String[] expertOptions = getSpecificCodecOptions(configuration.getMencoderCodecSpecificConfig(), media, params, filename, externalSubtitlesFileName, configuration.isMencoderIntelligentSync(), false);
boolean nomux = false;
for (String s : expertOptions) {
if (s.equals("-nomux")) {
nomux = true;
}
}
if (!nomux) {
ovccopy = true;
}
}
isTranscodeToMPEGTS = params.mediaRenderer.isTranscodeToMPEGTS();
isTranscodeToH264 = params.mediaRenderer.isTranscodeToH264() || params.mediaRenderer.isTranscodeToH265();
isTranscodeToAAC = params.mediaRenderer.isTranscodeToAAC();
final boolean isXboxOneWebVideo = params.mediaRenderer.isXboxOne() && purpose() == VIDEO_WEBSTREAM_PLAYER;
String vcodec = "mpeg2video";
if (isTranscodeToH264) {
vcodec = "libx264";
} else if ((params.mediaRenderer.isTranscodeToWMV() && !params.mediaRenderer.isXbox360()) || isXboxOneWebVideo) {
wmv = true;
vcodec = "wmv2";
}
// Default: Empty string
String rendererMencoderOptions = params.mediaRenderer.getCustomMencoderOptions();
// (see sanitizeArgs()) rather than ignoring the options entirely
if (rendererMencoderOptions.contains("expand=") && isDVD) {
rendererMencoderOptions = "";
}
// Default: Empty string
String globalMencoderOptions = configuration.getMencoderCustomOptions();
String combinedCustomOptions = defaultString(globalMencoderOptions) + " " + defaultString(rendererMencoderOptions);
/**
* Disable AC3 remux for stereo tracks with 384 kbits bitrate and PS3 renderer (PS3 FW bug?)
*
* Commented out until we can find a way to detect when a video has an audio track that switches from 2 to 6 channels
* because MEncoder can't handle those files, which are very common these days.
* boolean ps3_and_stereo_and_384_kbits = params.aid != null &&
* (params.mediaRenderer.isPS3() && params.aid.getAudioProperties().getNumberOfChannels() == 2) &&
* (params.aid.getBitRate() > 370000 && params.aid.getBitRate() < 400000);
*/
final boolean isTsMuxeRVideoEngineEnabled = configuration.getEnginesAsList(PMS.get().getRegistry()).contains(TsMuxeRVideo.ID);
final boolean mencoderAC3RemuxAudioDelayBug = (params.aid != null) && (params.aid.getAudioProperties().getAudioDelay() != 0) && (params.timeseek == 0);
encodedAudioPassthrough = isTsMuxeRVideoEngineEnabled && configuration.isEncodedAudioPassthrough() && params.mediaRenderer.isWrapEncodedAudioIntoPCM() && (!isDVD || configuration.isMencoderRemuxMPEG2()) && params.aid != null && params.aid.isNonPCMEncodedAudio() && !avisynth() && params.mediaRenderer.isMuxLPCMToMpeg();
if (configuration.isAudioRemuxAC3() && params.aid != null && params.aid.isAC3() && !avisynth() && params.mediaRenderer.isTranscodeToAC3() && !configuration.isMEncoderNormalizeVolume() && !combinedCustomOptions.contains("acodec=") && !encodedAudioPassthrough && !isXboxOneWebVideo && params.aid.getAudioProperties().getNumberOfChannels() <= configuration.getAudioChannelCount()) {
ac3Remux = true;
} else {
// Now check for DTS remux and LPCM streaming
dtsRemux = isTsMuxeRVideoEngineEnabled && configuration.isAudioEmbedDtsInPcm() && (!isDVD || configuration.isMencoderRemuxMPEG2()) && params.aid != null && params.aid.isDTS() && !avisynth() && params.mediaRenderer.isDTSPlayable() && !combinedCustomOptions.contains("acodec=");
pcm = isTsMuxeRVideoEngineEnabled && configuration.isAudioUsePCM() && (!isDVD || configuration.isMencoderRemuxMPEG2()) && // Disable LPCM transcoding for MP4 container with non-H.264 video as workaround for MEncoder's A/V sync bug
!(media.getContainer().equals("mp4") && !media.isH264()) && params.aid != null && (// disable 7.1 DTS-HD => LPCM because of channels mapping bug
(params.aid.isDTS() && params.aid.getAudioProperties().getNumberOfChannels() <= 6) || params.aid.isLossless() || params.aid.isTrueHD() || (!configuration.isMencoderUsePcmForHQAudioOnly() && (params.aid.isAC3() || params.aid.isMP3() || params.aid.isAAC() || params.aid.isVorbis() || // params.aid.isWMA() ||
params.aid.isMpegAudio()))) && params.mediaRenderer.isLPCMPlayable() && !combinedCustomOptions.contains("acodec=");
}
if (dtsRemux || pcm || encodedAudioPassthrough) {
params.losslessaudio = true;
params.forceFps = media.getValidFps(false);
}
// MPEG-2 remux still buggy with MEncoder
// TODO when we can still use it?
ovccopy = false;
if (pcm && avisynth()) {
params.avidemux = true;
}
String add = "";
if (!combinedCustomOptions.contains("-lavdopts")) {
add = " -lavdopts debug=0";
}
int channels;
if (ac3Remux) {
// AC-3 remux
channels = params.aid.getAudioProperties().getNumberOfChannels();
} else if (dtsRemux || encodedAudioPassthrough || (!params.mediaRenderer.isXbox360() && wmv)) {
channels = 2;
} else if (pcm) {
channels = params.aid.getAudioProperties().getNumberOfChannels();
} else {
/**
* Note: MEncoder will output 2 audio channels if the input video had 2 channels
* regardless of us telling it to output 6 (unlike FFmpeg which will output 6).
*/
// 5.1 max for AC-3 encoding
channels = configuration.getAudioChannelCount();
}
String channelsString = "-channels " + channels;
if (combinedCustomOptions.contains("-channels")) {
channelsString = "";
}
StringTokenizer st = new StringTokenizer(channelsString + (isNotBlank(globalMencoderOptions) ? " " + globalMencoderOptions : "") + (isNotBlank(rendererMencoderOptions) ? " " + rendererMencoderOptions : "") + add, " ");
// XXX why does this field (which is used to populate the array returned by args(),
// called below) store the renderer-specific (i.e. not global) MEncoder options?
overriddenMainArgs = new String[st.countTokens()];
{
int nThreads = (isDVD || filename.toLowerCase().endsWith("dvr-ms")) ? 1 : configuration.getMencoderMaxThreads();
// Multithreading for decoding offers little performance gain anyway so it's not a big deal.
if (nThreads > 4) {
nThreads = 4;
}
boolean handleToken = false;
int i = 0;
while (st.hasMoreTokens()) {
String token = st.nextToken().trim();
if (handleToken) {
token += ":threads=" + nThreads;
if (configuration.getSkipLoopFilterEnabled() && !avisynth()) {
token += ":skiploopfilter=all";
}
handleToken = false;
}
if (token.toLowerCase().contains("lavdopts")) {
handleToken = true;
}
overriddenMainArgs[i++] = token;
}
}
String vcodecString = ":vcodec=" + vcodec;
if (combinedCustomOptions.contains("vcodec=")) {
vcodecString = "";
}
if ((configuration.getx264ConstantRateFactor() != null && isTranscodeToH264) || (configuration.getMPEG2MainSettings() != null && !isTranscodeToH264)) {
// Ditlew - WDTV Live (+ other byte asking clients), CBR. This probably ought to be placed in addMaximumBitrateConstraints(..)
int cbr_bitrate = params.mediaRenderer.getCBRVideoBitrate();
String cbr_settings = (cbr_bitrate > 0) ? ":vrc_buf_size=5000:vrc_minrate=" + cbr_bitrate + ":vrc_maxrate=" + cbr_bitrate + ":vbitrate=" + ((cbr_bitrate > 16000) ? cbr_bitrate * 1000 : cbr_bitrate) : "";
// Set audio codec and bitrate if audio is being transcoded
String acodec = "";
String abitrate = "";
if (!ac3Remux && !dtsRemux && !isTranscodeToAAC) {
// Set the audio codec used by Lavc
if (!combinedCustomOptions.contains("acodec=")) {
acodec = ":acodec=";
if (wmv && !params.mediaRenderer.isXbox360()) {
acodec += "wmav2";
} else {
acodec = cbr_settings + acodec;
if (params.mediaRenderer.isTranscodeToAAC()) {
acodec += "libfaac";
} else if (configuration.isMencoderAc3Fixed()) {
acodec += "ac3_fixed";
} else {
acodec += "ac3";
}
}
}
// Set the audio bitrate used by Lavc
if (!combinedCustomOptions.contains("abitrate=")) {
abitrate = ":abitrate=";
if (wmv && !params.mediaRenderer.isXbox360()) {
abitrate += "448";
} else {
abitrate += CodecUtil.getAC3Bitrate(configuration, params.aid);
}
}
}
// Find out the maximum bandwidth we are supposed to use
int[] defaultMaxBitrates = getVideoBitrateConfig(configuration.getMaximumBitrate());
int[] rendererMaxBitrates = new int[2];
if (isNotEmpty(params.mediaRenderer.getMaxVideoBitrate())) {
rendererMaxBitrates = getVideoBitrateConfig(params.mediaRenderer.getMaxVideoBitrate());
}
if ((rendererMaxBitrates[0] > 0) && (rendererMaxBitrates[0] < defaultMaxBitrates[0])) {
LOGGER.trace("Using video bitrate limit from {} configuration ({} Mb/s) because " + "it is lower than the general configuration bitrate limit ({} Mb/s)", params.mediaRenderer.getRendererName(), rendererMaxBitrates[0], defaultMaxBitrates[0]);
defaultMaxBitrates = rendererMaxBitrates;
}
int maximumBitrate = defaultMaxBitrates[0];
// Set which audio codec to use
String audioType = "ac3";
if (dtsRemux) {
audioType = "dts";
} else if (pcm || encodedAudioPassthrough) {
audioType = "pcm";
} else if (params.mediaRenderer.isTranscodeToAAC()) {
audioType = "aac";
}
String encodeSettings = "";
/**
* Fixes aspect ratios on Sony TVs
*/
String aspectRatioLavcopts = "autoaspect=1";
if (!isDVD && ((params.mediaRenderer.isKeepAspectRatio() || params.mediaRenderer.isKeepAspectRatioTranscoding()) && !"16:9".equals(media.getAspectRatioContainer())) && !configuration.isMencoderScaler()) {
aspectRatioLavcopts = "aspect=16/9";
}
if (isXboxOneWebVideo || (configuration.getMPEG2MainSettings() != null && !isTranscodeToH264)) {
// Set MPEG-2 video quality
String mpeg2Options = configuration.getMPEG2MainSettings();
String mpeg2OptionsRenderer = params.mediaRenderer.getCustomMEncoderMPEG2Options();
// Renderer settings take priority over user settings
if (isNotBlank(mpeg2OptionsRenderer)) {
mpeg2Options = mpeg2OptionsRenderer;
} else {
// Remove comment from the value
if (mpeg2Options.contains("/*")) {
mpeg2Options = mpeg2Options.substring(mpeg2Options.indexOf("/*"));
}
// Determine a good quality setting based on video attributes
if (mpeg2Options.contains("Automatic")) {
mpeg2Options = "keyint=5:vqscale=1:vqmin=2:vqmax=3";
// It has been reported that non-PS3 renderers prefer keyint 5 but prefer it for PS3 because it lowers the average bitrate
if (params.mediaRenderer.isPS3()) {
mpeg2Options = "keyint=25:vqscale=1:vqmin=2:vqmax=3";
}
if (mpeg2Options.contains("Wireless") || maximumBitrate < 70) {
// Lower quality for 720p+ content
if (media.getWidth() > 1280) {
mpeg2Options = "keyint=25:vqmax=7:vqmin=2";
} else if (media.getWidth() > 720) {
mpeg2Options = "keyint=25:vqmax=5:vqmin=2";
}
}
}
}
encodeSettings = "-lavcopts " + aspectRatioLavcopts + vcodecString + acodec + abitrate + ":threads=" + (wmv && !params.mediaRenderer.isXbox360() ? 1 : configuration.getMencoderMaxThreads()) + ("".equals(mpeg2Options) ? "" : ":" + mpeg2Options);
encodeSettings = addMaximumBitrateConstraints(encodeSettings, media, mpeg2Options, params.mediaRenderer, audioType);
} else if (configuration.getx264ConstantRateFactor() != null && isTranscodeToH264) {
// Set H.264 video quality
String x264CRF = configuration.getx264ConstantRateFactor();
// Remove comment from the value
if (x264CRF.contains("/*")) {
x264CRF = x264CRF.substring(x264CRF.indexOf("/*"));
}
// Determine a good quality setting based on video attributes
if (x264CRF.contains("Automatic")) {
if (x264CRF.contains("Wireless") || maximumBitrate < 70) {
x264CRF = "19";
// Lower quality for 720p+ content
if (media.getWidth() > 1280) {
x264CRF = "23";
} else if (media.getWidth() > 720) {
x264CRF = "22";
}
} else {
x264CRF = "16";
// Lower quality for 720p+ content
if (media.getWidth() > 720) {
x264CRF = "19";
}
}
}
encodeSettings = "-lavcopts " + aspectRatioLavcopts + vcodecString + acodec + abitrate + ":threads=" + configuration.getMencoderMaxThreads() + ":o=preset=superfast,crf=" + x264CRF + ",g=250,i_qfactor=0.71,qcomp=0.6,level=3.1,weightp=0,8x8dct=0,aq-strength=0,me_range=16";
encodeSettings = addMaximumBitrateConstraints(encodeSettings, media, "", params.mediaRenderer, audioType);
}
st = new StringTokenizer(encodeSettings, " ");
{
// Old length
int i = overriddenMainArgs.length;
overriddenMainArgs = Arrays.copyOf(overriddenMainArgs, overriddenMainArgs.length + st.countTokens());
while (st.hasMoreTokens()) {
overriddenMainArgs[i++] = st.nextToken();
}
}
}
boolean foundNoassParam = false;
String[] expertOptions = getSpecificCodecOptions(configuration.getMencoderCodecSpecificConfig(), media, params, filename, externalSubtitlesFileName, configuration.isMencoderIntelligentSync(), false);
if (expertOptions != null) {
for (String s : expertOptions) {
if (s.equals("-noass")) {
foundNoassParam = true;
}
}
}
StringBuilder sb = new StringBuilder();
// Set subtitles options
if (!isDisableSubtitles(params)) {
int subtitleMargin = 0;
int userMargin = 0;
// Use ASS flag (and therefore ASS font styles) for all subtitled files except vobsub, PGS (Blu-ray Disc) and DVD
boolean apply_ass_styling = params.sid.getType() != SubtitleType.VOBSUB && params.sid.getType() != SubtitleType.PGS && // GUI: enable subtitles formating
configuration.isMencoderAss() && // GUI: codec specific options
!foundNoassParam && !isDVD;
if (apply_ass_styling) {
sb.append("-ass ");
// GUI: Override ASS subtitles style if requested (always for SRT and TX3G subtitles)
boolean override_ass_style = !configuration.isUseEmbeddedSubtitlesStyle() || params.sid.getType() == SubtitleType.SUBRIP || params.sid.getType() == SubtitleType.TX3G;
if (override_ass_style) {
String assSubColor = configuration.getSubsColor().getMEncoderHexValue();
sb.append("-ass-color ").append(assSubColor).append(" -ass-border-color 00000000 -ass-font-scale ").append(configuration.getAssScale());
// Set subtitles font
if (isNotBlank(configuration.getFont())) {
/* Set font with -font option, workaround for the bug:
* https://github.com/Happy-Neko/ps3mediaserver/commit/52e62203ea12c40628de1869882994ce1065446a#commitcomment-990156
*/
sb.append(" -font ").append(quoteArg(configuration.getFont())).append(' ');
String font = CodecUtil.isFontRegisteredInOS(configuration.getFont());
if (font != null) {
sb.append(" -ass-force-style FontName=").append(quoteArg(font)).append(',');
}
} else {
String font = CodecUtil.getDefaultFontPath();
if (isNotBlank(font)) {
sb.append(" -font ").append(quoteArg(font)).append(' ');
String fontName = CodecUtil.isFontRegisteredInOS(font);
if (fontName != null) {
sb.append(" -ass-force-style FontName=").append(quoteArg(fontName)).append(',');
}
} else {
sb.append(" -font Arial ");
sb.append(" -ass-force-style FontName=Arial,");
}
}
/*
* Add to the subtitle margin if overscan compensation is being used
* This keeps the subtitle text inside the frame instead of in the border
*/
if (intOCH > 0) {
subtitleMargin = (media.getHeight() / 100) * intOCH;
subtitleMargin /= 2;
}
sb.append("Outline=").append(configuration.getAssOutline()).append(",Shadow=").append(configuration.getAssShadow());
try {
userMargin = Integer.parseInt(configuration.getAssMargin());
} catch (NumberFormatException n) {
LOGGER.debug("Could not parse SSA margin from \"" + configuration.getAssMargin() + "\"");
}
subtitleMargin += userMargin;
sb.append(",MarginV=").append(subtitleMargin).append(' ');
} else if (intOCH > 0) {
/*
* Add to the subtitle margin
* This keeps the subtitle text inside the frame instead of in the border
*/
subtitleMargin = (media.getHeight() / 100) * intOCH;
subtitleMargin /= 2;
sb.append("-ass-force-style MarginV=").append(subtitleMargin).append(' ');
}
// use of the "-ass" option also requires the "-font" option.
if (Platform.isMac() && !sb.toString().contains(" -font ")) {
String font = CodecUtil.getDefaultFontPath();
if (isNotBlank(font)) {
sb.append("-font ").append(quoteArg(font)).append(' ');
}
}
// Workaround for MPlayer #2041, remove when that bug is fixed
if (!params.sid.isEmbedded()) {
sb.append("-noflip-hebrew ");
}
// Use PLAINTEXT formatting
} else {
// Set subtitles font
if (configuration.getFont() != null && configuration.getFont().length() > 0) {
sb.append(" -font ").append(quoteArg(configuration.getFont())).append(' ');
} else {
String font = CodecUtil.getDefaultFontPath();
if (isNotBlank(font)) {
sb.append(" -font ").append(quoteArg(font)).append(' ');
}
}
sb.append(" -subfont-text-scale ").append(configuration.getMencoderNoAssScale());
sb.append(" -subfont-outline ").append(configuration.getMencoderNoAssOutline());
sb.append(" -subfont-blur ").append(configuration.getMencoderNoAssBlur());
// This keeps the subtitle text inside the frame instead of in the border
if (intOCH > 0) {
subtitleMargin = intOCH;
}
try {
userMargin = Integer.parseInt(configuration.getMencoderNoAssSubPos());
} catch (NumberFormatException n) {
LOGGER.debug("Could not parse subpos from \"" + configuration.getMencoderNoAssSubPos() + "\"");
}
subtitleMargin += userMargin;
sb.append(" -subpos ").append(100 - subtitleMargin).append(' ');
}
// Appending the flag will break execution, so skip it on Mac OS X.
if (!Platform.isMac()) {
// Use fontconfig if enabled
sb.append('-').append(configuration.isMencoderFontConfig() ? "" : "no").append("fontconfig ");
}
// Apply DVD/VOBsub subtitle quality
if (params.sid.getType() == SubtitleType.VOBSUB && configuration.getMencoderVobsubSubtitleQuality() != null) {
String subtitleQuality = configuration.getMencoderVobsubSubtitleQuality();
sb.append("-spuaa ").append(subtitleQuality).append(' ');
}
// External subtitles file
if (params.sid.isExternal()) {
if (!params.sid.isExternalFileUtf()) {
String subcp = null;
// Append -subcp option for non UTF external subtitles
if (isNotBlank(configuration.getSubtitlesCodepage())) {
// Manual setting
subcp = configuration.getSubtitlesCodepage();
} else if (isNotBlank(SubtitleUtils.getSubCpOptionForMencoder(params.sid))) {
// Autodetect charset (blank mencoder_subcp config option)
subcp = SubtitleUtils.getSubCpOptionForMencoder(params.sid);
}
if (isNotBlank(subcp)) {
sb.append("-subcp ").append(subcp).append(' ');
if (configuration.isMencoderSubFribidi()) {
sb.append("-fribidi-charset ").append(subcp).append(' ');
}
}
}
}
}
st = new StringTokenizer(sb.toString(), " ");
{
// Old length
int i = overriddenMainArgs.length;
overriddenMainArgs = Arrays.copyOf(overriddenMainArgs, overriddenMainArgs.length + st.countTokens());
boolean handleToken = false;
while (st.hasMoreTokens()) {
String s = st.nextToken();
if (handleToken) {
s = "-quiet";
handleToken = false;
}
if ((!configuration.isMencoderAss() || isDVD) && s.contains("-ass")) {
s = "-quiet";
handleToken = true;
}
overriddenMainArgs[i++] = s;
}
}
List<String> cmdList = new ArrayList<>();
cmdList.add(executable());
// Choose which time to seek to
cmdList.add("-ss");
cmdList.add((params.timeseek > 0) ? "" + params.timeseek : "0");
if (isDVD) {
cmdList.add("-dvd-device");
}
String frameRateRatio = media.getValidFps(true);
String frameRateNumber = media.getValidFps(false);
// Input filename
if (avisynth && !filename.toLowerCase().endsWith(".iso")) {
File avsFile = AviSynthMEncoder.getAVSScript(filename, params.sid, params.fromFrame, params.toFrame, frameRateRatio, frameRateNumber, configuration);
cmdList.add(ProcessUtil.getShortFileNameIfWideChars(avsFile.getAbsolutePath()));
} else {
if (params.stdin != null) {
cmdList.add("-");
} else {
if (isDVD) {
String dvdFileName = filename.replace("\\VIDEO_TS", "");
cmdList.add(dvdFileName);
} else {
cmdList.add(filename);
}
}
}
if (isDVD) {
cmdList.add("dvd://" + media.getDvdtrack());
}
for (String arg : args()) {
if (arg.contains("format=mpeg2") && media.getAspectRatioDvdIso() != null && media.getAspectRatioMencoderMpegopts(true) != null) {
cmdList.add(arg + ":vaspect=" + media.getAspectRatioMencoderMpegopts(true));
} else {
cmdList.add(arg);
}
}
if (!dtsRemux && !encodedAudioPassthrough && !pcm && !avisynth() && params.aid != null && media.getAudioTracksList().size() > 1) {
cmdList.add("-aid");
// TODO Need to add support for LAVF demuxing
boolean lavf = false;
cmdList.add("" + (lavf ? params.aid.getId() + 1 : params.aid.getId()));
}
/*
* Handle subtitles
*
* Try to reconcile the fact that the handling of "Definitely disable subtitles" is spread out
* over net.pms.encoders.Player.setAudioAndSubs and here by setting both of MEncoder's "disable
* subs" options if any of the internal conditions for disabling subtitles are met.
*/
if (isDisableSubtitles(params)) {
// Ensure that internal subtitles are not automatically loaded
cmdList.add("-nosub");
// Ensure that external subtitles are not automatically loaded
cmdList.add("-noautosub");
} else {
// Note: isEmbedded() and isExternal() are mutually exclusive
if (params.sid.isEmbedded()) {
// internal (embedded) subs
// Ensure that external subtitles are not automatically loaded
cmdList.add("-noautosub");
// Specify which internal subtitle we want
cmdList.add("-sid");
cmdList.add("" + params.sid.getId());
} else if (externalSubtitlesFileName != null) {
// confirm the mutual exclusion
assert params.sid.isExternal();
// Ensure that internal subtitles are not automatically loaded
cmdList.add("-nosub");
if (params.sid.getType() == SubtitleType.VOBSUB) {
cmdList.add("-vobsub");
cmdList.add(externalSubtitlesFileName.substring(0, externalSubtitlesFileName.length() - 4));
cmdList.add("-slang");
cmdList.add("" + params.sid.getLang());
} else if (!params.sid.isStreamable() && !params.mediaRenderer.streamSubsForTranscodedVideo()) {
// when subs are streamable do not transcode them
cmdList.add("-sub");
DLNAMediaSubtitle convertedSubs = dlna.getMediaSubtitle();
if (media.is3d()) {
if (convertedSubs != null && convertedSubs.getConvertedFile() != null) {
// subs are already converted to 3D so use them
cmdList.add(convertedSubs.getConvertedFile().getAbsolutePath().replace(",", "\\,"));
} else if (params.sid.getType() != SubtitleType.ASS) {
// When subs are not converted and they are not in the ASS format and video is 3D then subs need conversion to 3D
File subsFilename = SubtitleUtils.getSubtitles(dlna, media, params, configuration, SubtitleType.ASS);
cmdList.add(subsFilename.getAbsolutePath().replace(",", "\\,"));
}
} else {
// Commas in MEncoder separate multiple subtitle files
cmdList.add(externalSubtitlesFileName.replace(",", "\\,"));
}
if (params.sid.isExternalFileUtf()) {
// Append -utf8 option for UTF-8 external subtitles
cmdList.add("-utf8");
}
}
}
}
// -ofps
// where a framerate is required, use the input framerate or 24000/1001
String framerate = (frameRateRatio != null) ? frameRateRatio : "24000/1001";
String ofps = framerate;
// Optional -fps or -mc
if (configuration.isMencoderForceFps()) {
if (!configuration.isFix25FPSAvMismatch()) {
cmdList.add("-fps");
cmdList.add(framerate);
} else if (frameRateRatio != null) {
// XXX not sure why this "fix" requires the input to have a valid framerate, but that's the logic in the old (cmdArray) code
cmdList.add("-mc");
cmdList.add("0.005");
ofps = "25";
}
}
// Make MEncoder output framerate correspond to InterFrame
if (avisynth() && configuration.getAvisynthInterFrame() && !"60000/1001".equals(frameRateRatio) && !"50".equals(frameRateRatio) && !"60".equals(frameRateRatio)) {
switch(frameRateRatio) {
case "25":
ofps = "50";
break;
case "30":
ofps = "60";
break;
default:
ofps = "60000/1001";
break;
}
}
cmdList.add("-ofps");
cmdList.add(ofps);
if (filename.toLowerCase().endsWith(".evo")) {
cmdList.add("-psprobe");
cmdList.add("10000");
}
boolean deinterlace = configuration.isMencoderYadif();
// Check if the media renderer supports this resolution
boolean isResolutionTooHighForRenderer = !params.mediaRenderer.isResolutionCompatibleWithRenderer(media.getWidth(), media.getHeight());
// Video scaler and overscan compensation
boolean scaleBool = false;
if (isResolutionTooHighForRenderer || (configuration.isMencoderScaler() && (configuration.getMencoderScaleX() != 0 || configuration.getMencoderScaleY() != 0)) || (intOCW > 0 || intOCH > 0)) {
scaleBool = true;
}
int scaleWidth = 0;
int scaleHeight = 0;
String vfValue = "";
if (media.getWidth() > 0 && media.getHeight() > 0) {
scaleWidth = media.getWidth();
scaleHeight = media.getHeight();
}
double videoAspectRatio = (double) media.getWidth() / (double) media.getHeight();
double rendererAspectRatio = 1.777777777777778;
if (params.mediaRenderer.isMaximumResolutionSpecified()) {
rendererAspectRatio = (double) params.mediaRenderer.getMaxVideoWidth() / (double) params.mediaRenderer.getMaxVideoHeight();
}
if ((deinterlace || scaleBool) && !avisynth()) {
StringBuilder vfValueOverscanPrepend = new StringBuilder();
StringBuilder vfValueOverscanMiddle = new StringBuilder();
StringBuilder vfValueVS = new StringBuilder();
StringBuilder vfValueComplete = new StringBuilder();
String deinterlaceComma = "";
/*
* Implement overscan compensation settings
*
* This feature takes into account aspect ratio,
* making it less blunt than the Video Scaler option
*/
if (intOCW > 0 || intOCH > 0) {
int intOCWPixels = (media.getWidth() / 100) * intOCW;
int intOCHPixels = (media.getHeight() / 100) * intOCH;
scaleWidth += intOCWPixels;
scaleHeight += intOCHPixels;
// See if the video needs to be scaled down
if (params.mediaRenderer.isMaximumResolutionSpecified() && ((scaleWidth > params.mediaRenderer.getMaxVideoWidth()) || (scaleHeight > params.mediaRenderer.getMaxVideoHeight()))) {
double overscannedAspectRatio = scaleWidth / (double) scaleHeight;
if (overscannedAspectRatio > rendererAspectRatio) {
// Limit video by width
scaleWidth = params.mediaRenderer.getMaxVideoWidth();
scaleHeight = (int) Math.round(params.mediaRenderer.getMaxVideoWidth() / overscannedAspectRatio);
} else {
// Limit video by height
scaleWidth = (int) Math.round(params.mediaRenderer.getMaxVideoHeight() * overscannedAspectRatio);
scaleHeight = params.mediaRenderer.getMaxVideoHeight();
}
}
scaleWidth = convertToModX(scaleWidth, 4);
scaleHeight = convertToModX(scaleHeight, 4);
vfValueOverscanPrepend.append("softskip,expand=-").append(intOCWPixels).append(":-").append(intOCHPixels);
vfValueOverscanMiddle.append(",scale=").append(scaleWidth).append(':').append(scaleHeight);
}
/*
* Video Scaler and renderer-specific resolution-limiter
*/
if (configuration.isMencoderScaler()) {
// Use the manual, user-controlled scaler
if (configuration.getMencoderScaleX() != 0) {
if (configuration.getMencoderScaleX() <= params.mediaRenderer.getMaxVideoWidth()) {
scaleWidth = configuration.getMencoderScaleX();
} else {
scaleWidth = params.mediaRenderer.getMaxVideoWidth();
}
}
if (configuration.getMencoderScaleY() != 0) {
if (configuration.getMencoderScaleY() <= params.mediaRenderer.getMaxVideoHeight()) {
scaleHeight = configuration.getMencoderScaleY();
} else {
scaleHeight = params.mediaRenderer.getMaxVideoHeight();
}
}
scaleWidth = convertToModX(scaleWidth, 4);
scaleHeight = convertToModX(scaleHeight, 4);
LOGGER.info("Setting video resolution to: " + scaleWidth + "x" + scaleHeight + ", your Video Scaler setting");
vfValueVS.append("scale=").append(scaleWidth).append(':').append(scaleHeight);
} else if (isResolutionTooHighForRenderer) {
/*
* First we deal with some exceptions, then if they are not matched we will
* let the renderer limits work.
*
* This is so, for example, we can still define a maximum resolution of
* 1920x1080 in the renderer config file but still support 1920x1088 when
* it's needed, otherwise we would either resize 1088 to 1080, meaning the
* ugly (unused) bottom 8 pixels would be displayed, or we would limit all
* videos to 1088 causing the bottom 8 meaningful pixels to be cut off.
*/
if (media.getWidth() == 3840 && media.getHeight() <= 1080) {
// Full-SBS
scaleWidth = 1920;
scaleHeight = media.getHeight();
} else if (media.getWidth() == 1920 && media.getHeight() == 2160) {
// Full-OU
scaleWidth = 1920;
scaleHeight = 1080;
} else if (media.getWidth() == 1920 && media.getHeight() == 1088) {
// SAT capture
scaleWidth = 1920;
scaleHeight = 1088;
} else {
// Passed the exceptions, now we allow the renderer to define the limits
if (videoAspectRatio > rendererAspectRatio) {
scaleWidth = params.mediaRenderer.getMaxVideoWidth();
scaleHeight = (int) Math.round(params.mediaRenderer.getMaxVideoWidth() / videoAspectRatio);
} else {
scaleWidth = (int) Math.round(params.mediaRenderer.getMaxVideoHeight() * videoAspectRatio);
scaleHeight = params.mediaRenderer.getMaxVideoHeight();
}
}
scaleWidth = convertToModX(scaleWidth, 4);
scaleHeight = convertToModX(scaleHeight, 4);
LOGGER.info("Setting video resolution to: " + scaleWidth + "x" + scaleHeight + ", the maximum your renderer supports");
vfValueVS.append("scale=").append(scaleWidth).append(':').append(scaleHeight);
}
// Put the string together taking into account overscan compensation and video scaler
if (intOCW > 0 || intOCH > 0) {
vfValueComplete.append(vfValueOverscanPrepend).append(vfValueOverscanMiddle).append(",harddup");
LOGGER.info("Setting video resolution to: " + scaleWidth + "x" + scaleHeight + ", to fit your overscan compensation");
} else {
vfValueComplete.append(vfValueVS);
}
if (deinterlace) {
deinterlaceComma = ",";
}
vfValue = (deinterlace ? "yadif" : "") + (scaleBool ? deinterlaceComma + vfValueComplete : "");
}
/*
* Make sure the video is mod4 unless the renderer has specified
* that it doesn't care, and make sure the aspect ratio is 16/9
* if the renderer needs it.
*
* The PS3 and possibly other renderers sometimes display mod2
* videos in black and white with diagonal strips of color.
*
* TODO: Integrate this with the other stuff so that "expand" only
* ever appears once in the MEncoder CMD.
*/
if (!isDVD && ((((scaleWidth % 4 != 0) || (scaleHeight % 4 != 0)) && !params.mediaRenderer.isMuxNonMod4Resolution()) || ((params.mediaRenderer.isKeepAspectRatio() || params.mediaRenderer.isKeepAspectRatioTranscoding()) && !"16:9".equals(media.getAspectRatioContainer()))) && !configuration.isMencoderScaler()) {
String vfValuePrepend = "expand=";
if (params.mediaRenderer.isKeepAspectRatio() || params.mediaRenderer.isKeepAspectRatioTranscoding()) {
String resolution = dlna.getResolutionForKeepAR(scaleWidth, scaleHeight);
scaleWidth = Integer.parseInt(substringBefore(resolution, "x"));
scaleHeight = Integer.parseInt(substringAfter(resolution, "x"));
/**
* Now we know which resolution we want the video to be, let's see if MEncoder
* can be trusted to output it using only the expand filter, or if we need to
* be extra careful and use scale too (which is slower).
*
* For now I'm not sure exactly how MEncoder decides which resolution to
* output so this is some cautious math. If someone does extensive testing
* in the future it can be made less cautious.
*/
if ((scaleWidth + 4) > params.mediaRenderer.getMaxVideoWidth() || (scaleHeight + 4) > params.mediaRenderer.getMaxVideoHeight()) {
vfValuePrepend += "::::0:16/9,scale=" + scaleWidth + ":" + scaleHeight;
} else {
vfValuePrepend += "::::0:16/9:4";
}
} else {
vfValuePrepend += "-" + (scaleWidth % 4) + ":-" + (scaleHeight % 4);
}
vfValuePrepend += ",softskip";
if (isNotBlank(vfValue)) {
vfValuePrepend += ",";
}
vfValue = vfValuePrepend + vfValue;
}
if (isNotBlank(vfValue)) {
cmdList.add("-vf");
cmdList.add(vfValue);
}
if (configuration.getMencoderMT() && !avisynth && !isDVD && !(media.getCodecV() != null && (media.getCodecV().startsWith("mpeg2")))) {
cmdList.add("-lavdopts");
cmdList.add("fast");
}
boolean disableMc0AndNoskip = false;
// 3) append expertOptions to cmdList
if (expertOptions != null && expertOptions.length > 0) {
// remove this option (key) from the cmdList in pass 2.
// if the boolean value is true, also remove the option's corresponding value
Map<String, Boolean> removeCmdListOption = new HashMap<>();
// if this option (key) is defined in cmdList, merge this string value into the
// option's value in pass 2. the value is a string format template into which the
// cmdList option value is injected
Map<String, String> mergeCmdListOption = new HashMap<>();
// merges that are performed in pass 2 are logged in this map; the key (string) is
// the option name and the value is a boolean indicating whether the option was merged
// or not. the map is populated after pass 1 with the options from mergeCmdListOption
// and all values initialised to false. if an option was merged, it is not appended
// to cmdList
Map<String, Boolean> mergedCmdListOption = new HashMap<>();
// pass 1: process expertOptions
for (int i = 0; i < expertOptions.length; ++i) {
switch(expertOptions[i]) {
case "-noass":
// remove -ass from cmdList in pass 2.
// -ass won't have been added in this method (getSpecificCodecOptions
// has been called multiple times above to check for -noass and -nomux)
// but it may have been added via the renderer or global MEncoder options.
// XXX: there are currently 10 other -ass options (-ass-color, -ass-border-color &c.).
// technically, they should all be removed...
// false: option does not have a corresponding value
removeCmdListOption.put("-ass", false);
// remove -noass from expertOptions in pass 3
expertOptions[i] = REMOVE_OPTION;
break;
case "-nomux":
expertOptions[i] = REMOVE_OPTION;
break;
case "-mt":
// not an MEncoder option so remove it from exportOptions.
// multi-threaded MEncoder is used by default, so this is obsolete (TODO: Remove it from the description)
expertOptions[i] = REMOVE_OPTION;
break;
case "-ofps":
// replace the cmdList version with the expertOptions version i.e. remove the former
removeCmdListOption.put("-ofps", true);
// skip (i.e. leave unchanged) the exportOptions value
++i;
break;
case "-fps":
removeCmdListOption.put("-fps", true);
++i;
break;
case "-ovc":
removeCmdListOption.put("-ovc", true);
++i;
break;
case "-channels":
removeCmdListOption.put("-channels", true);
++i;
break;
case "-oac":
removeCmdListOption.put("-oac", true);
++i;
break;
case "-quality":
// XXX like the old (cmdArray) code, this clobbers the old -lavcopts value
String lavcopts = String.format("autoaspect=1:vcodec=%s:acodec=%s:abitrate=%s:threads=%d:%s", vcodec, (configuration.isMencoderAc3Fixed() ? "ac3_fixed" : "ac3"), CodecUtil.getAC3Bitrate(configuration, params.aid), configuration.getMencoderMaxThreads(), expertOptions[i + 1]);
// append bitrate-limiting options if configured
lavcopts = addMaximumBitrateConstraints(lavcopts, media, lavcopts, params.mediaRenderer, "");
// a string format with no placeholders, so the cmdList option value is ignored.
// note: we protect "%" from being interpreted as a format by converting it to "%%",
// which is then turned back into "%" when the format is processed
mergeCmdListOption.put("-lavcopts", lavcopts.replace("%", "%%"));
// remove -quality <value>
expertOptions[i] = expertOptions[i + 1] = REMOVE_OPTION;
++i;
break;
case "-mpegopts":
mergeCmdListOption.put("-mpegopts", "%s:" + expertOptions[i + 1].replace("%", "%%"));
// merge if cmdList already contains -mpegopts, but don't append if it doesn't (parity with the old (cmdArray) version)
expertOptions[i] = expertOptions[i + 1] = REMOVE_OPTION;
++i;
break;
case "-vf":
mergeCmdListOption.put("-vf", "%s," + expertOptions[i + 1].replace("%", "%%"));
++i;
break;
case "-af":
mergeCmdListOption.put("-af", "%s," + expertOptions[i + 1].replace("%", "%%"));
++i;
break;
case "-nosync":
disableMc0AndNoskip = true;
expertOptions[i] = REMOVE_OPTION;
break;
case "-mc":
disableMc0AndNoskip = true;
break;
default:
break;
}
}
for (String key : mergeCmdListOption.keySet()) {
mergedCmdListOption.put(key, false);
}
// pass 2: process cmdList
List<String> transformedCmdList = new ArrayList<>();
for (int i = 0; i < cmdList.size(); ++i) {
String option = cmdList.get(i);
// we remove an option by *not* adding it to transformedCmdList
if (removeCmdListOption.containsKey(option)) {
if (isTrue(removeCmdListOption.get(option))) {
// true: remove (i.e. don't add) the corresponding value
++i;
}
} else {
transformedCmdList.add(option);
if (mergeCmdListOption.containsKey(option)) {
String format = mergeCmdListOption.get(option);
String value = String.format(format, cmdList.get(i + 1));
// record the fact that an expertOption value has been merged into this cmdList value
mergedCmdListOption.put(option, true);
transformedCmdList.add(value);
++i;
}
}
}
cmdList = transformedCmdList;
// pass 3: append expertOptions to cmdList
for (int i = 0; i < expertOptions.length; ++i) {
String option = expertOptions[i];
if (!option.equals(REMOVE_OPTION)) {
if (isTrue(mergedCmdListOption.get(option))) {
// true: this option and its value have already been merged into existing cmdList options
// skip the value
++i;
} else {
cmdList.add(option);
}
}
}
}
if ((pcm || dtsRemux || encodedAudioPassthrough || ac3Remux) || (configuration.isMencoderNoOutOfSync() && !disableMc0AndNoskip)) {
if (configuration.isFix25FPSAvMismatch()) {
cmdList.add("-mc");
cmdList.add("0.005");
} else if (configuration.isMencoderNoOutOfSync() && !disableMc0AndNoskip) {
cmdList.add("-mc");
cmdList.add("0");
if (!params.mediaRenderer.isDisableMencoderNoskip()) {
cmdList.add("-noskip");
}
}
}
if (params.timeend > 0) {
cmdList.add("-endpos");
cmdList.add("" + params.timeend);
}
// Force srate because MEncoder doesn't like anything other than 48khz for AC-3
String rate = "" + params.mediaRenderer.getTranscodedVideoAudioSampleRate();
if (!pcm && !dtsRemux && !ac3Remux && !encodedAudioPassthrough) {
cmdList.add("-af");
String af = "lavcresample=" + rate;
if (configuration.isMEncoderNormalizeVolume()) {
af += ":volnorm=1";
}
cmdList.add(af);
cmdList.add("-srate");
cmdList.add(rate);
}
// https://code.google.com/p/ps3mediaserver/issues/detail?id=911
if (params.stdin != null) {
cmdList.add("-cache");
cmdList.add("8192");
}
PipeProcess pipe = null;
ProcessWrapperImpl pw;
if (pcm || dtsRemux || encodedAudioPassthrough) {
// Transcode video, demux audio, remux with tsMuxeR
boolean channels_filter_present = false;
for (String s : cmdList) {
if (isNotBlank(s) && s.startsWith("channels")) {
channels_filter_present = true;
break;
}
}
if (params.avidemux) {
pipe = new PipeProcess("mencoder" + System.currentTimeMillis(), (pcm || dtsRemux || encodedAudioPassthrough || ac3Remux) ? null : params);
params.input_pipes[0] = pipe;
cmdList.add("-o");
cmdList.add(pipe.getInputPipe());
if (pcm && !channels_filter_present && params.aid != null) {
String mixer = AudioUtils.getLPCMChannelMappingForMencoder(params.aid);
if (isNotBlank(mixer)) {
cmdList.add("-af");
cmdList.add(mixer);
}
}
String[] cmdArray = new String[cmdList.size()];
cmdList.toArray(cmdArray);
pw = new ProcessWrapperImpl(cmdArray, params);
PipeProcess videoPipe = new PipeProcess("videoPipe" + System.currentTimeMillis(), "out", "reconnect");
PipeProcess audioPipe = new PipeProcess("audioPipe" + System.currentTimeMillis(), "out", "reconnect");
ProcessWrapper videoPipeProcess = videoPipe.getPipeProcess();
ProcessWrapper audioPipeProcess = audioPipe.getPipeProcess();
params.output_pipes[0] = videoPipe;
params.output_pipes[1] = audioPipe;
pw.attachProcess(videoPipeProcess);
pw.attachProcess(audioPipeProcess);
videoPipeProcess.runInNewThread();
audioPipeProcess.runInNewThread();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
videoPipe.deleteLater();
audioPipe.deleteLater();
} else {
for (ListIterator<String> it = cmdList.listIterator(); it.hasNext(); ) {
String option = it.next();
if (option.equals("-oac")) {
it.set("-nosound");
if (it.hasNext()) {
it.next();
it.remove();
}
break;
}
}
pipe = new PipeProcess(System.currentTimeMillis() + "tsmuxerout.ts");
TsMuxeRVideo ts = new TsMuxeRVideo();
File f = new File(configuration.getTempFolder(), "pms-tsmuxer.meta");
String[] cmd = new String[] { ts.executable(), f.getAbsolutePath(), pipe.getInputPipe() };
pw = new ProcessWrapperImpl(cmd, params);
PipeIPCProcess ffVideoPipe = new PipeIPCProcess(System.currentTimeMillis() + "ffmpegvideo", System.currentTimeMillis() + "videoout", false, true);
cmdList.add("-o");
cmdList.add(ffVideoPipe.getInputPipe());
OutputParams ffparams = new OutputParams(configuration);
ffparams.maxBufferSize = 1;
ffparams.stdin = params.stdin;
String[] cmdArray = new String[cmdList.size()];
cmdList.toArray(cmdArray);
ProcessWrapperImpl ffVideo = new ProcessWrapperImpl(cmdArray, ffparams);
ProcessWrapper ff_video_pipe_process = ffVideoPipe.getPipeProcess();
pw.attachProcess(ff_video_pipe_process);
ff_video_pipe_process.runInNewThread();
ffVideoPipe.deleteLater();
pw.attachProcess(ffVideo);
ffVideo.runInNewThread();
String aid = null;
if (media.getAudioTracksList().size() > 1 && params.aid != null) {
if (media.getContainer() != null && (media.getContainer().equals(FormatConfiguration.AVI) || media.getContainer().equals(FormatConfiguration.FLV))) {
// TODO confirm (MP4s, OGMs and MOVs already tested: first aid is 0; AVIs: first aid is 1)
// For AVIs, FLVs and MOVs MEncoder starts audio tracks numbering from 1
aid = "" + (params.aid.getId() + 1);
} else {
// Everything else from 0
aid = "" + params.aid.getId();
}
}
PipeIPCProcess ffAudioPipe = new PipeIPCProcess(System.currentTimeMillis() + "ffmpegaudio01", System.currentTimeMillis() + "audioout", false, true);
StreamModifier sm = new StreamModifier();
sm.setPcm(pcm);
sm.setDtsEmbed(dtsRemux);
sm.setEncodedAudioPassthrough(encodedAudioPassthrough);
sm.setSampleFrequency(48000);
sm.setBitsPerSample(16);
String mixer = null;
if (pcm && !dtsRemux && !encodedAudioPassthrough) {
// LPCM always outputs 5.1/7.1 for multichannel tracks. Downmix with player if needed!
mixer = getLPCMChannelMappingForMencoder(params.aid);
}
sm.setNbChannels(channels);
// It seems that -really-quiet prevents MEncoder from stopping the pipe output after some time
// -mc 0.1 makes the DTS-HD extraction work better with latest MEncoder builds, and has no impact on the regular DTS one
// TODO: See if these notes are still true, and if so leave specific revisions/release names of the latest version tested.
String[] ffmpegLPCMextract = new String[] { executable(), "-ss", "0", filename, "-really-quiet", "-msglevel", "statusline=2", "-channels", "" + channels, "-ovc", "copy", "-of", "rawaudio", "-mc", (dtsRemux || encodedAudioPassthrough) ? "0.1" : "0", "-noskip", (aid == null) ? "-quiet" : "-aid", (aid == null) ? "-quiet" : aid, "-oac", (ac3Remux || dtsRemux || encodedAudioPassthrough) ? "copy" : "pcm", (isNotBlank(mixer) && !channels_filter_present) ? "-af" : "-quiet", (isNotBlank(mixer) && !channels_filter_present) ? mixer : "-quiet", "-srate", "48000", "-o", ffAudioPipe.getInputPipe() };
if (!params.mediaRenderer.isMuxDTSToMpeg()) {
// No need to use the PCM trick when media renderer supports DTS
ffAudioPipe.setModifier(sm);
}
if (media.getDvdtrack() > 0) {
ffmpegLPCMextract[3] = "-dvd-device";
ffmpegLPCMextract[4] = filename;
ffmpegLPCMextract[5] = "dvd://" + media.getDvdtrack();
} else if (params.stdin != null) {
ffmpegLPCMextract[3] = "-";
}
if (filename.toLowerCase().endsWith(".evo")) {
ffmpegLPCMextract[4] = "-psprobe";
ffmpegLPCMextract[5] = "1000000";
}
if (params.timeseek > 0) {
ffmpegLPCMextract[2] = "" + params.timeseek;
}
OutputParams ffaudioparams = new OutputParams(configuration);
ffaudioparams.maxBufferSize = 1;
ffaudioparams.stdin = params.stdin;
ProcessWrapperImpl ffAudio = new ProcessWrapperImpl(ffmpegLPCMextract, ffaudioparams);
params.stdin = null;
try (PrintWriter pwMux = new PrintWriter(f)) {
pwMux.println("MUXOPT --no-pcr-on-video-pid --no-asyncio --new-audio-pes --vbr --vbv-len=500");
String videoType = "V_MPEG-2";
if (params.no_videoencode && params.forceType != null) {
videoType = params.forceType;
}
String fps = "";
if (params.forceFps != null) {
fps = "fps=" + params.forceFps + ", ";
}
String audioType;
if (ac3Remux) {
audioType = "A_AC3";
} else if (dtsRemux) {
if (params.mediaRenderer.isMuxDTSToMpeg()) {
// Renderer can play proper DTS track
audioType = "A_DTS";
} else {
// DTS padded in LPCM trick
audioType = "A_LPCM";
}
} else {
// DTS padded in LPCM trick
audioType = "A_LPCM";
}
/*
* MEncoder bug (confirmed with MEncoder r35003 + FFmpeg 0.11.1)
* Audio delay is ignored when playing from file start (-ss 0)
* Override with tsmuxer.meta setting
*/
String timeshift = "";
if (mencoderAC3RemuxAudioDelayBug) {
timeshift = "timeshift=" + params.aid.getAudioProperties().getAudioDelay() + "ms, ";
}
pwMux.println(videoType + ", \"" + ffVideoPipe.getOutputPipe() + "\", " + fps + "level=4.1, insertSEI, contSPS, track=1");
pwMux.println(audioType + ", \"" + ffAudioPipe.getOutputPipe() + "\", " + timeshift + "track=2");
}
ProcessWrapper pipe_process = pipe.getPipeProcess();
pw.attachProcess(pipe_process);
pipe_process.runInNewThread();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
pipe.deleteLater();
params.input_pipes[0] = pipe;
ProcessWrapper ff_pipe_process = ffAudioPipe.getPipeProcess();
pw.attachProcess(ff_pipe_process);
ff_pipe_process.runInNewThread();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
ffAudioPipe.deleteLater();
pw.attachProcess(ffAudio);
ffAudio.runInNewThread();
}
} else {
boolean directpipe = Platform.isMac() || Platform.isFreeBSD();
if (directpipe) {
cmdList.add("-o");
cmdList.add("-");
cmdList.add("-really-quiet");
cmdList.add("-msglevel");
cmdList.add("statusline=2");
params.input_pipes = new PipeProcess[2];
} else {
pipe = new PipeProcess("mencoder" + System.currentTimeMillis(), (pcm || dtsRemux || encodedAudioPassthrough) ? null : params);
params.input_pipes[0] = pipe;
cmdList.add("-o");
cmdList.add(pipe.getInputPipe());
}
String[] cmdArray = new String[cmdList.size()];
cmdList.toArray(cmdArray);
cmdArray = finalizeTranscoderArgs(filename, dlna, media, params, cmdArray);
pw = new ProcessWrapperImpl(cmdArray, params);
if (!directpipe) {
ProcessWrapper mkfifo_process = pipe.getPipeProcess();
pw.attachProcess(mkfifo_process);
/*
* It can take a long time for Windows to create a named pipe (and
* mkfifo can be slow if /tmp isn't memory-mapped), so run this in
* the current thread.
*/
mkfifo_process.runInSameThread();
pipe.deleteLater();
}
}
pw.runInNewThread();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
configuration = prev;
return pw;
}
use of net.pms.configuration.PmsConfiguration in project UniversalMediaServer by UniversalMediaServer.
the class Player method setSubtitleOutputParameters.
/**
* This method populates the supplied {@link OutputParams} object with the correct subtitles (sid)
* based on the given filename, its MediaInfo metadata and PMS configuration settings.
*
* TODO: Rewrite this crazy method to be more concise and logical.
*
* @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.
*/
public static void setSubtitleOutputParameters(String fileName, DLNAMediaInfo media, OutputParams params) {
// Use device-specific pms conf
PmsConfiguration configuration = PMS.getConfiguration(params);
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;
}
/**
* Check for live subtitles
*/
if (params.sid != null && !StringUtils.isEmpty(params.sid.getLiveSubURL())) {
LOGGER.debug("Live subtitles " + params.sid.getLiveSubURL());
try {
matchedSub = params.sid;
String file = OpenSubtitle.fetchSubs(matchedSub.getLiveSubURL(), matchedSub.getLiveSubFile());
if (!StringUtils.isEmpty(file)) {
matchedSub.setExternalFile(new File(file), null);
params.sid = matchedSub;
return;
}
} catch (IOException e) {
}
}
StringTokenizer st = new StringTokenizer(configuration.getAudioSubLanguages(), ";");
/**
* Check for external and internal subtitles matching the user's language
* preferences
*/
boolean matchedInternalSubtitles = false;
boolean matchedExternalSubtitles = false;
while (st.hasMoreTokens()) {
String pair = st.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")) {
/**
* Ignore the "off" language for external subtitles if the user setting is enabled
* TODO: Prioritize multiple external subtitles properly instead of just taking the first one we load
*/
if (configuration.isForceExternalSubtitles()) {
for (DLNAMediaSubtitle present_sub : media.getSubtitleTracksList()) {
if (present_sub.getExternalFile() != null) {
matchedSub = present_sub;
matchedExternalSubtitles = true;
LOGGER.trace("Ignoring the \"off\" language because there are external subtitles");
break;
}
}
}
if (!matchedExternalSubtitles) {
matchedSub = new DLNAMediaSubtitle();
matchedSub.setLang("off");
}
} else {
for (DLNAMediaSubtitle present_sub : media.getSubtitleTracksList()) {
if (present_sub.matchCode(sub) || sub.equals("*")) {
if (present_sub.getExternalFile() != null) {
if (configuration.isAutoloadExternalSubtitles()) {
// Subtitle is external and we want external subtitles, look no further
matchedSub = present_sub;
LOGGER.trace("Matched external subtitles track: " + matchedSub);
break;
} else {
// Subtitle is external but we do not want external subtitles, keep searching
LOGGER.trace("External subtitles ignored because of user setting: " + present_sub);
}
} else if (!matchedInternalSubtitles) {
matchedSub = present_sub;
LOGGER.trace("Matched internal subtitles track: " + matchedSub);
if (configuration.isAutoloadExternalSubtitles()) {
// Subtitle is internal and we will wait to see if an external one is available instead
matchedInternalSubtitles = true;
} else {
// Subtitle is internal and we will use it
break;
}
}
}
}
}
if (matchedSub != null && !matchedInternalSubtitles) {
break;
}
}
}
}
/**
* Check for external subtitles that were skipped in the above code block
* because they didn't match language preferences, if there wasn't already
* a match and the user settings specify it.
*/
if (matchedSub == null && configuration.isForceExternalSubtitles()) {
for (DLNAMediaSubtitle present_sub : media.getSubtitleTracksList()) {
if (present_sub.getExternalFile() != null) {
matchedSub = present_sub;
LOGGER.trace("Matched external subtitles track that did not match language preferences: " + matchedSub);
break;
}
}
}
/**
* Disable chosen subtitles if the user has disabled all subtitles or
* if the language preferences have specified the "off" language.
*
* TODO: Can't we save a bunch of looping by checking for isDisableSubtitles
* just after the Live Subtitles check above?
*/
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;
}
}
/**
* Check for forced subtitles.
*/
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")) {
st = new StringTokenizer(configuration.getForcedSubtitleTags(), ",");
while (sub.getSubtitlesTrackTitleFromMetadata() != null && st.hasMoreTokens()) {
String forcedTags = st.nextToken();
forcedTags = forcedTags.trim();
if (sub.getSubtitlesTrackTitleFromMetadata().toLowerCase().contains(forcedTags) && Iso639.isCodesMatching(sub.getLang(), configuration.getForcedSubtitleLanguage())) {
LOGGER.trace("Forcing preferred subtitles: " + sub.getLang() + "/" + sub.getSubtitlesTrackTitleFromMetadata());
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) {
st = new StringTokenizer(UMSUtils.getLangList(params.mediaRenderer), ",");
while (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) && !(!configuration.isAutoloadExternalSubtitles() && sub.getExternalFile() != null)) {
params.sid = sub;
LOGGER.trace("Matched subtitles track: " + params.sid);
return;
}
}
}
}
}
}
Aggregations