Example usage for java.util List listIterator

List of usage examples for java.util List listIterator

Introduction

In this page you can find the example usage for java.util List listIterator.

Prototype

ListIterator<E> listIterator();

Source Link

Document

Returns a list iterator over the elements in this list (in proper sequence).

Usage

From source file:org.cruk.genologics.api.cache.GenologicsAPICache.java

/**
 * Join point for the {@code GenologicsAPI.loadAll} method.
 * Examines the cache for objects already loaded and only fetches those
 * that are not already seen (or, for stateful objects, those whose requested
 * state is later than that in the cache).
 *
 * @param <E> The type of LIMS entity referred to.
 * @param pjp The join point object./*from  w ww.  ja  v a2  s.  c o m*/
 *
 * @return The list of entities retrieved.
 *
 * @throws Throwable if there is an error.
 *
 * @see GenologicsAPI#loadAll(Collection)
 */
public <E extends Locatable> List<E> loadAll(ProceedingJoinPoint pjp) throws Throwable {
    @SuppressWarnings("unchecked")
    Collection<LimsLink<E>> links = (Collection<LimsLink<E>>) pjp.getArgs()[0];

    List<E> results = new ArrayList<E>(links == null ? 0 : links.size());

    if (links != null && !links.isEmpty()) {
        Ehcache cache = null;

        List<LimsLink<E>> toFetch = new ArrayList<LimsLink<E>>(links.size());
        List<E> alreadyCached = new ArrayList<E>(links.size());

        Boolean cacheable = null;
        String className = null;
        Boolean stateful = null;

        CacheStatefulBehaviour callBehaviour = behaviourOverride.get();
        if (callBehaviour == null) {
            callBehaviour = behaviour;
        }

        behaviourLock.lock();
        try {
            Iterator<LimsLink<E>> linkIterator = links.iterator();

            // Loop through the links requested and accumulate two lists of links:
            // those that are not in the cache and need to be fetched and those that
            // have already been fetched. While doing this, assemble in "results" those
            // entities already in the cache that don't need to be fetch. This list will
            // have nulls inserted where the entity needs to be fetched.

            while (linkIterator.hasNext()) {
                LimsLink<E> link = linkIterator.next();
                if (link == null) {
                    throw new IllegalArgumentException("link contains a null");
                }
                if (link.getUri() == null) {
                    throw new IllegalArgumentException("A link in the collection has no URI set.");
                }

                if (className == null) {
                    className = ClassUtils.getShortClassName(link.getEntityClass());
                    cacheable = isCacheable(link.getEntityClass());
                    stateful = isStateful(link.getEntityClass());
                }

                E entity = null;
                if (!cacheable) {
                    // Fetch always.
                    toFetch.add(link);
                } else {
                    if (cache == null) {
                        cache = getCache(link.getEntityClass());
                    }

                    String key = keyFromLocatable(link);

                    Element wrapper = cache.get(key);
                    if (wrapper == null) {
                        toFetch.add(link);
                    } else {
                        long version = versionFromLocatable(link);

                        switch (callBehaviour) {
                        case ANY:
                            entity = getFromWrapper(wrapper);
                            alreadyCached.add(entity);
                            break;

                        case LATEST:
                            if (version != NO_STATE_VALUE && version > wrapper.getVersion()) {
                                toFetch.add(link);
                            } else {
                                entity = getFromWrapper(wrapper);
                                alreadyCached.add(entity);
                            }
                            break;

                        case EXACT:
                            if (version != NO_STATE_VALUE && version != wrapper.getVersion()) {
                                toFetch.add(link);
                            } else {
                                entity = getFromWrapper(wrapper);
                                alreadyCached.add(entity);
                            }
                            break;
                        }
                    }
                }
                results.add(entity);
            }
        } finally {
            behaviourLock.unlock();
        }

        if (logger.isWarnEnabled()) {
            if (cache.getCacheConfiguration().getMaxEntriesLocalHeap() < links.size()) {
                logger.warn(
                        "{} {}s are requested, but the cache will only hold {}. Repeated fetches of this collection will always call through to the API.",
                        links.size(), className, cache.getCacheConfiguration().getMaxEntriesLocalHeap());
            }
        }

        if (logger.isDebugEnabled()) {
            if (alreadyCached.size() == links.size()) {
                logger.debug("All {} {}s requested are already in the cache.", links.size(), className);
            } else {
                logger.debug("Have {} {}s in the cache; {} to retrieve.", alreadyCached.size(), className,
                        toFetch.size());
            }
        }

        // If there is anything to fetch, perform the call to the API then
        // fill in the nulls in the "results" list from the entities returned
        // from the API.
        // The end result is that newly fetched items are put into the cache
        // and "results" is a fully populated list.

        if (!toFetch.isEmpty()) {
            assert cacheable != null : "No cacheable flag found";
            assert stateful != null : "No stateful flag found";

            Object[] args = { toFetch };
            @SuppressWarnings("unchecked")
            List<E> fetched = (List<E>) pjp.proceed(args);

            ListIterator<E> resultIterator = results.listIterator();
            ListIterator<E> fetchIterator = fetched.listIterator();

            while (resultIterator.hasNext()) {
                E entity = resultIterator.next();
                if (entity == null) {
                    assert fetchIterator.hasNext() : "Run out of items in the fetched list.";
                    entity = fetchIterator.next();
                    resultIterator.set(entity);

                    if (cacheable) {
                        if (!stateful) {
                            // Entities without state will only have been fetched because they
                            // were not in the cache. These should just be added.

                            cache.put(createCacheElement(entity));
                        } else {
                            // Stateful entities may already be in the cache but may have been
                            // fetched because the requested version is newer or of a different
                            // state. Some care needs to be taken to update its cached version
                            // depending on how the cache normally behaves.

                            String key = keyFromLocatable(entity);
                            Element wrapper = cache.get(key);

                            if (wrapper == null) {
                                // Not already cached, so simply add this entity whatever
                                // its state.

                                cache.put(createCacheElement(entity));
                            } else {
                                // As we have a stateful entity, there may be cause
                                // to replace the object in the cache depending on how the
                                // cache normally behaves. Typically this will be replacing the
                                // existing with a newer version or replacing for a difference.
                                // When we don't care about versions, the one already in the cache
                                // can remain.

                                long version = versionFromLocatable(entity);

                                switch (behaviour) {
                                case ANY:
                                    break;

                                case LATEST:
                                    if (version > wrapper.getVersion()) {
                                        cache.put(createCacheElement(entity));
                                    }
                                    break;

                                case EXACT:
                                    if (version != wrapper.getVersion()) {
                                        cache.put(createCacheElement(entity));
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            assert !fetchIterator.hasNext() : "Have further items fetched after populating results list.";
        }
    }

    return results;
}

From source file:net.sf.jasperreports.engine.util.JEditorPaneHtmlMarkupProcessor.java

@Override
public String convert(String srcText) {
    JEditorPane editorPane = new JEditorPane("text/html", srcText);
    editorPane.setEditable(false);//  www. j  a va2  s.  c o m

    List<Element> elements = new ArrayList<Element>();

    Document document = editorPane.getDocument();

    Element root = document.getDefaultRootElement();
    if (root != null) {
        addElements(elements, root);
    }

    int startOffset = 0;
    int endOffset = 0;
    int crtOffset = 0;
    String chunk = null;
    JRPrintHyperlink hyperlink = null;
    Element element = null;
    Element parent = null;
    boolean bodyOccurred = false;
    int[] orderedListIndex = new int[elements.size()];
    String whitespace = "    ";
    String[] whitespaces = new String[elements.size()];
    for (int i = 0; i < elements.size(); i++) {
        whitespaces[i] = "";
    }

    StringBuilder text = new StringBuilder();
    List<JRStyledText.Run> styleRuns = new ArrayList<>();

    for (int i = 0; i < elements.size(); i++) {
        if (bodyOccurred && chunk != null) {
            text.append(chunk);
            Map<Attribute, Object> styleAttributes = getAttributes(element.getAttributes());
            if (hyperlink != null) {
                styleAttributes.put(JRTextAttribute.HYPERLINK, hyperlink);
                hyperlink = null;
            }
            if (!styleAttributes.isEmpty()) {
                styleRuns.add(
                        new JRStyledText.Run(styleAttributes, startOffset + crtOffset, endOffset + crtOffset));
            }
        }

        chunk = null;
        element = elements.get(i);
        parent = element.getParentElement();
        startOffset = element.getStartOffset();
        endOffset = element.getEndOffset();
        AttributeSet attrs = element.getAttributes();

        Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
        Object object = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute);
        if (object instanceof HTML.Tag) {

            HTML.Tag htmlTag = (HTML.Tag) object;
            if (htmlTag == Tag.BODY) {
                bodyOccurred = true;
                crtOffset = -startOffset;
            } else if (htmlTag == Tag.BR) {
                chunk = "\n";
            } else if (htmlTag == Tag.OL) {
                orderedListIndex[i] = 0;
                String parentName = parent.getName().toLowerCase();
                whitespaces[i] = whitespaces[elements.indexOf(parent)] + whitespace;
                if (parentName.equals("li")) {
                    chunk = "";
                } else {
                    chunk = "\n";
                    ++crtOffset;
                }
            } else if (htmlTag == Tag.UL) {
                whitespaces[i] = whitespaces[elements.indexOf(parent)] + whitespace;

                String parentName = parent.getName().toLowerCase();
                if (parentName.equals("li")) {
                    chunk = "";
                } else {
                    chunk = "\n";
                    ++crtOffset;
                }

            } else if (htmlTag == Tag.LI) {

                whitespaces[i] = whitespaces[elements.indexOf(parent)];
                if (element.getElement(0) != null && (element.getElement(0).getName().toLowerCase().equals("ol")
                        || element.getElement(0).getName().toLowerCase().equals("ul"))) {
                    chunk = "";
                } else if (parent.getName().equals("ol")) {
                    int index = elements.indexOf(parent);
                    Object type = parent.getAttributes().getAttribute(HTML.Attribute.TYPE);
                    Object startObject = parent.getAttributes().getAttribute(HTML.Attribute.START);
                    int start = startObject == null ? 0
                            : Math.max(0, Integer.valueOf(startObject.toString()) - 1);
                    String suffix = "";

                    ++orderedListIndex[index];

                    if (type != null) {
                        switch (((String) type).charAt(0)) {
                        case 'A':
                            suffix = getOLBulletChars(orderedListIndex[index] + start, true);
                            break;
                        case 'a':
                            suffix = getOLBulletChars(orderedListIndex[index] + start, false);
                            break;
                        case 'I':
                            suffix = JRStringUtil.getRomanNumeral(orderedListIndex[index] + start, true);
                            break;
                        case 'i':
                            suffix = JRStringUtil.getRomanNumeral(orderedListIndex[index] + start, false);
                            break;
                        case '1':
                        default:
                            suffix = String.valueOf(orderedListIndex[index] + start);
                            break;
                        }
                    } else {
                        suffix += orderedListIndex[index] + start;
                    }
                    chunk = whitespaces[index] + suffix + DEFAULT_BULLET_SEPARATOR + "  ";

                } else {
                    chunk = whitespaces[elements.indexOf(parent)] + DEFAULT_BULLET_CHARACTER + "  ";
                }
                crtOffset += chunk.length();
            } else if (element instanceof LeafElement) {
                if (element instanceof RunElement) {
                    RunElement runElement = (RunElement) element;
                    AttributeSet attrSet = (AttributeSet) runElement.getAttribute(Tag.A);
                    if (attrSet != null) {
                        hyperlink = new JRBasePrintHyperlink();
                        hyperlink.setHyperlinkType(HyperlinkTypeEnum.REFERENCE);
                        hyperlink.setHyperlinkReference((String) attrSet.getAttribute(HTML.Attribute.HREF));
                        hyperlink.setLinkTarget((String) attrSet.getAttribute(HTML.Attribute.TARGET));
                    }
                }
                try {
                    chunk = document.getText(startOffset, endOffset - startOffset);
                } catch (BadLocationException e) {
                    if (log.isDebugEnabled()) {
                        log.debug("Error converting markup.", e);
                    }
                }
            }
        }
    }

    if (chunk != null) {
        if (!"\n".equals(chunk)) {
            text.append(chunk);
            Map<Attribute, Object> styleAttributes = getAttributes(element.getAttributes());
            if (hyperlink != null) {
                styleAttributes.put(JRTextAttribute.HYPERLINK, hyperlink);
                hyperlink = null;
            }
            if (!styleAttributes.isEmpty()) {
                styleRuns.add(
                        new JRStyledText.Run(styleAttributes, startOffset + crtOffset, endOffset + crtOffset));
            }
        } else {
            //final newline, not appending
            //check if there's any style run that would have covered it, that can happen if there's a <li> tag with style
            int length = text.length();
            for (ListIterator<JRStyledText.Run> it = styleRuns.listIterator(); it.hasNext();) {
                JRStyledText.Run run = it.next();
                //only looking at runs that end at the position where the newline should have been
                //we don't want to hide bugs in which runs that span after the text length are created
                if (run.endIndex == length + 1) {
                    if (run.startIndex < run.endIndex - 1) {
                        it.set(new JRStyledText.Run(run.attributes, run.startIndex, run.endIndex - 1));
                    } else {
                        it.remove();
                    }
                }
            }
        }
    }

    JRStyledText styledText = new JRStyledText(null, text.toString());
    for (JRStyledText.Run run : styleRuns) {
        styledText.addRun(run);
    }
    styledText.setGlobalAttributes(new HashMap<Attribute, Object>());

    return JRStyledTextParser.getInstance().write(styledText);
}

From source file:net.pms.encoders.MEncoderVideo.java

@Override
public ProcessWrapper launchTranscode(DLNAResource dlna, DLNAMediaInfo media, OutputParams params)
        throws IOException {
    params.manageFastStart();//from w ww. j  a va2 s  .c  o m

    boolean avisynth = avisynth();

    final String filename = dlna.getSystemName();
    setAudioAndSubs(filename, media, params, configuration);
    String externalSubtitlesFileName = null;

    if (params.sid != null && params.sid.isExternal()) {
        if (params.sid.isExternalFileUtf16()) {
            // convert UTF-16 -> UTF-8
            File convertedSubtitles = new File(PMS.getConfiguration().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);

    dvd = false;

    if (media != null && media.getDvdtrack() > 0) {
        dvd = true;
    }

    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());
    }

    if (params.sid == null && dvd && 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;
        }
    }

    String vcodec = "mpeg2video";

    if (params.mediaRenderer.isTranscodeToWMV()) {
        wmv = true;
        vcodec = "wmv2"; // http://wiki.megaframe.org/wiki/Ubuntu_XBOX_360#MEncoder not usable in streaming
    }

    mpegts = params.mediaRenderer.isTranscodeToMPEGTSAC3();

    /*
     Disable AC-3 remux for stereo tracks with 384 kbits bitrate and PS3 renderer (PS3 FW bug?)
     TODO check new firmwares
     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.
    */
    // final 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 ps3_and_stereo_and_384_kbits = false;

    final boolean isTSMuxerVideoEngineEnabled = PMS.getConfiguration().getEnginesAsList()
            .contains(TsMuxeRVideo.ID);
    final boolean mencoderAC3RemuxAudioDelayBug = (params.aid != null)
            && (params.aid.getAudioProperties().getAudioDelay() != 0) && (params.timeseek == 0);
    if (!mencoderAC3RemuxAudioDelayBug && configuration.isAudioRemuxAC3() && params.aid != null
            && params.aid.isAC3() && !ps3_and_stereo_and_384_kbits && !avisynth()
            && params.mediaRenderer.isTranscodeToAC3()) {
        // AC3 remux takes priority
        ac3Remux = true;
    } else {
        // now check for DTS remux and LPCM streaming
        dtsRemux = isTSMuxerVideoEngineEnabled && configuration.isAudioEmbedDtsInPcm()
                && (!dvd || configuration.isMencoderRemuxMPEG2()) && params.aid != null && params.aid.isDTS()
                && !avisynth() && params.mediaRenderer.isDTSPlayable();
        pcm = isTSMuxerVideoEngineEnabled && configuration.isAudioUsePCM()
                && (!dvd || configuration.isMencoderRemuxMPEG2())
                // disable LPCM transcoding for MP4 container with non-H264 video as workaround for mencoder's A/V sync bug
                && !(media.getContainer().equals("mp4") && !media.getCodecV().equals("h264"))
                && params.aid != null
                && ((params.aid.isDTS() && params.aid.getAudioProperties().getNumberOfChannels() <= 6) || // disable 7.1 DTS-HD => LPCM because of channels mapping bug
                        params.aid.isLossless() || params.aid.isTrueHD()
                        || (!configuration.isMencoderUsePcmForHQAudioOnly() && (params.aid.isAC3()
                                || params.aid.isMP3() || params.aid.isAAC() || params.aid.isVorbis() ||
                                // disable WMA to LPCM transcoding because of mencoder's channel mapping bug
                                // (see CodecUtil.getMixerOutput)
                                // params.aid.isWMA() ||
                                params.aid.isMpegAudio())))
                && params.mediaRenderer.isLPCMPlayable();
    }

    if (dtsRemux || pcm) {
        params.losslessaudio = true;
        params.forceFps = media.getValidFps(false);
    }

    // mpeg2 remux still buggy with mencoder :\
    // TODO when we can still use it?
    ovccopy = false;

    if (pcm && avisynth()) {
        params.avidemux = true;
    }

    int channels;
    if (ac3Remux) {
        channels = params.aid.getAudioProperties().getNumberOfChannels(); // ac3 remux
    } else if (dtsRemux || wmv) {
        channels = 2;
    } else if (pcm) {
        channels = params.aid.getAudioProperties().getNumberOfChannels();
    } else {
        channels = configuration.getAudioChannelCount(); // 5.1 max for ac3 encoding
    }

    logger.trace("channels=" + channels);

    String add = "";
    String rendererMencoderOptions = params.mediaRenderer.getCustomMencoderOptions(); // default: empty string
    String globalMencoderOptions = configuration.getMencoderCustomOptions(); // default: empty string

    if (params.mediaRenderer.isPadVideoWithBlackBordersTo169AR()) {
        rendererMencoderOptions += " -vf softskip,expand=::::1:16/9:4";
    }

    String combinedCustomOptions = defaultString(globalMencoderOptions) + " "
            + defaultString(rendererMencoderOptions);

    if (!combinedCustomOptions.contains("-lavdopts")) {
        add = " -lavdopts debug=0";
    }

    if (isNotBlank(rendererMencoderOptions)) {
        // don't use the renderer-specific options if they break DVD streaming
        // XXX we should weed out the unused/unwanted settings and keep the rest
        // (see sanitizeArgs()) rather than ignoring the options entirely
        if (dvd && rendererMencoderOptions.contains("expand=")) {
            logger.warn("renderer MEncoder options are incompatible with DVD streaming; ignoring: "
                    + rendererMencoderOptions);
            rendererMencoderOptions = null;
        }
    }

    StringTokenizer st = new StringTokenizer(
            "-channels " + channels + (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 = (dvd || filename.toLowerCase().endsWith("dvr-ms")) ? 1
                : configuration.getMencoderMaxThreads();
        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;
        }
    }

    if (configuration.getMPEG2MainSettings() != null) {
        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("/*"));
            }

            // Find out the maximum bandwidth we are supposed to use
            int defaultMaxBitrates[] = getVideoBitrateConfig(configuration.getMaximumBitrate());
            int rendererMaxBitrates[] = new int[2];

            if (params.mediaRenderer.getMaxVideoBitrate() != null) {
                rendererMaxBitrates = getVideoBitrateConfig(params.mediaRenderer.getMaxVideoBitrate());
            }

            if ((rendererMaxBitrates[0] > 0) && (rendererMaxBitrates[0] < defaultMaxBitrates[0])) {
                defaultMaxBitrates = rendererMaxBitrates;
            }

            int maximumBitrate = defaultMaxBitrates[0];

            // 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";
                    }
                }
            }
        }

        // 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)
                : "";

        String encodeSettings = "-lavcopts autoaspect=1:vcodec=" + vcodec
                + (wmv && !params.mediaRenderer.isXBOX() ? ":acodec=wmav2:abitrate=448"
                        : (cbr_settings + ":acodec="
                                + (configuration.isMencoderAc3Fixed() ? "ac3_fixed" : "ac3") + ":abitrate="
                                + CodecUtil.getAC3Bitrate(configuration, params.aid)))
                + ":threads="
                + (wmv && !params.mediaRenderer.isXBOX() ? 1 : configuration.getMencoderMaxThreads())
                + ("".equals(mpeg2Options) ? "" : ":" + mpeg2Options);

        String audioType = "ac3";
        if (dtsRemux) {
            audioType = "dts";
        } else if (pcm) {
            audioType = "pcm";
        }

        encodeSettings = addMaximumBitrateConstraints(encodeSettings, media, mpeg2Options, params.mediaRenderer,
                audioType);
        st = new StringTokenizer(encodeSettings, " ");

        {
            int i = overriddenMainArgs.length; // Old length
            overriddenMainArgs = Arrays.copyOf(overriddenMainArgs,
                    overriddenMainArgs.length + st.countTokens());

            while (st.hasMoreTokens()) {
                overriddenMainArgs[i++] = st.nextToken();
            }
        }
    }

    boolean foundNoassParam = false;

    if (media != null) {
        String expertOptions[] = getSpecificCodecOptions(configuration.getMencoderCodecSpecificConfig(), media,
                params, filename, externalSubtitlesFileName, configuration.isMencoderIntelligentSync(), false);

        for (String s : expertOptions) {
            if (s.equals("-noass")) {
                foundNoassParam = true;
            }
        }
    }

    StringBuilder sb = new StringBuilder();
    // Set subtitles options
    if (!configuration.isDisableSubtitles() && !avisynth() && params.sid != null) {
        int subtitleMargin = 0;
        int userMargin = 0;

        // Use ASS flag (and therefore ASS font styles) for all subtitled files except vobsub, PGS and dvd
        boolean apply_ass_styling = params.sid.getType() != SubtitleType.VOBSUB
                && params.sid.getType() != SubtitleType.PGS && configuration.isMencoderAss() && // GUI: enable subtitles formating
                !foundNoassParam && // GUI: codec specific options
                !dvd;

        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.isMencoderAssDefaultStyle()
                    || params.sid.getType() == SubtitleType.SUBRIP || params.sid.getType() == SubtitleType.TX3G;

            if (override_ass_style) {
                String assSubColor = "ffffff00";
                if (configuration.getSubsColor() != 0) {
                    assSubColor = Integer.toHexString(configuration.getSubsColor());
                    if (assSubColor.length() > 2) {
                        assSubColor = assSubColor.substring(2) + "00";
                    }
                }

                sb.append("-ass-color ").append(assSubColor)
                        .append(" -ass-border-color 00000000 -ass-font-scale ")
                        .append(configuration.getAssScale());

                // set subtitles font
                if (configuration.getFont() != null && configuration.getFont().length() > 0) {
                    // set font with -font option, workaround for
                    // https://github.com/Happy-Neko/ps3mediaserver/commit/52e62203ea12c40628de1869882994ce1065446a#commitcomment-990156 bug
                    sb.append(" -font ").append(configuration.getFont()).append(" ");
                    sb.append(" -ass-force-style FontName=").append(configuration.getFont()).append(",");
                } else {
                    String font = CodecUtil.getDefaultFontPath();
                    if (isNotBlank(font)) {
                        // Variable "font" contains a font path instead of a font name.
                        // Does "-ass-force-style" support font paths? In tests on OS X
                        // the font path is ignored (Outline, Shadow and MarginV are
                        // used, though) and the "-font" definition is used instead.
                        // See: https://github.com/ps3mediaserver/ps3mediaserver/pull/14
                        sb.append(" -font ").append(font).append(" ");
                        sb.append(" -ass-force-style FontName=").append(font).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;
                }

                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 = subtitleMargin + userMargin;

                sb.append(",MarginV=").append(subtitleMargin).append(" ");
            } else if (intOCH > 0) {
                sb.append("-ass-force-style MarginV=").append(subtitleMargin).append(" ");
            }

            // MEncoder is not compiled with fontconfig on Mac OS X, therefore
            // use of the "-ass" option also requires the "-font" option.
            if (Platform.isMac() && sb.toString().indexOf(" -font ") < 0) {
                String font = CodecUtil.getDefaultFontPath();

                if (isNotBlank(font)) {
                    sb.append("-font ").append(font).append(" ");
                }
            }

            // Workaround for MPlayer #2041, remove when that bug is fixed
            if (!params.sid.isEmbedded()) {
                sb.append("-noflip-hebrew ");
            }
            // use PLAINTEXT formating
        } else {
            // set subtitles font
            if (configuration.getFont() != null && configuration.getFont().length() > 0) {
                sb.append(" -font ").append(configuration.getFont()).append(" ");
            } else {
                String font = CodecUtil.getDefaultFontPath();
                if (isNotBlank(font)) {
                    sb.append(" -font ").append(font).append(" ");
                }
            }

            sb.append(" -subfont-text-scale ").append(configuration.getMencoderNoAssScale());
            sb.append(" -subfont-outline ").append(configuration.getMencoderNoAssOutline());
            sb.append(" -subfont-blur ").append(configuration.getMencoderNoAssBlur());

            // 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 = intOCH;
            }

            try {
                userMargin = Integer.parseInt(configuration.getMencoderNoAssSubPos());
            } catch (NumberFormatException n) {
                logger.debug("Could not parse subpos from \"" + configuration.getMencoderNoAssSubPos() + "\"");
            }

            subtitleMargin = subtitleMargin + userMargin;

            sb.append(" -subpos ").append(100 - subtitleMargin).append(" ");
        }

        // Common subtitle options

        // MEncoder on Mac OS X is compiled without fontconfig support.
        // 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(), " ");

    {
        int i = overriddenMainArgs.length; // old 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() || dvd) && s.contains("-ass")) {
                s = "-quiet";
                handleToken = true;
            }

            overriddenMainArgs[i++] = s;
        }
    }

    List<String> cmdList = new ArrayList<String>();

    cmdList.add(executable());

    // timeseek
    // XXX -ss 0 is is included for parity with the old (cmdArray) code: it may be possible to omit it
    cmdList.add("-ss");
    cmdList.add((params.timeseek > 0) ? "" + params.timeseek : "0");

    if (dvd) {
        cmdList.add("-dvd-device");
    }

    // input filename
    if (avisynth && !filename.toLowerCase().endsWith(".iso")) {
        File avsFile = FFmpegAviSynthVideo.getAVSScript(filename, params.sid, params.fromFrame, params.toFrame);
        cmdList.add(ProcessUtil.getShortFileNameIfWideChars(avsFile.getAbsolutePath()));
    } else {
        if (params.stdin != null) {
            cmdList.add("-");
        } else {
            cmdList.add(filename);
        }
    }

    if (dvd) {
        cmdList.add("dvd://" + media.getDvdtrack());
    }

    for (String arg : args()) {
        if (arg.contains("format=mpeg2") && media.getAspect() != null && media.getValidAspect(true) != null) {
            cmdList.add(arg + ":vaspect=" + media.getValidAspect(true));
        } else {
            cmdList.add(arg);
        }
    }

    if (!dtsRemux && !pcm && !avisynth() && params.aid != null && media.getAudioTracksList().size() > 1) {
        cmdList.add("-aid");
        boolean lavf = false; // TODO Need to add support for LAVF demuxing
        cmdList.add("" + (lavf ? params.aid.getId() + 1 : params.aid.getId()));
    }

    /*
     * handle subtitles
     *
     * try to reconcile the fact that the handling of "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
        // MKV: in some circumstances, MEncoder automatically selects an internal sub unless we explicitly disable (internal) subtitles
        // http://www.ps3mediaserver.org/forum/viewtopic.php?f=14&t=15891
        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) { // external subtitles
            assert params.sid.isExternal(); // confirm the mutual exclusion

            // 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 {
                cmdList.add("-sub");
                cmdList.add(externalSubtitlesFileName.replace(",", "\\,")); // Commas in MEncoder separate multiple subtitle files

                if (params.sid.isExternalFileUtf()) {
                    // append -utf8 option for UTF-8 external subtitles
                    cmdList.add("-utf8");
                }
            }
        }
    }

    // -ofps
    String validFramerate = (media != null) ? media.getValidFps(true) : null; // optional input framerate: may be null
    String framerate = (validFramerate != null) ? validFramerate : "24000/1001"; // where a framerate is required, use the input framerate or 24000/1001
    String ofps = framerate;

    // optional -fps or -mc
    if (configuration.isMencoderForceFps()) {
        if (!configuration.isFix25FPSAvMismatch()) {
            cmdList.add("-fps");
            cmdList.add(framerate);
        } else if (validFramerate != 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";
        }
    }

    cmdList.add("-ofps");
    cmdList.add(ofps);

    /*
     * TODO: Move the following block up with the rest of the
     * subtitle stuff
     */
    // external subtitles file
    if (!configuration.isDisableSubtitles() && !avisynth() && params.sid != null && params.sid.isExternal()) {
        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 {
            cmdList.add("-sub");
            cmdList.add(externalSubtitlesFileName.replace(",", "\\,")); // Commas in MEncoder separate multiple subtitle files

            if (params.sid.isExternalFileUtf()) {
                // append -utf8 option for UTF-8 external subtitles
                cmdList.add("-utf8");
            }
        }
    }

    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.isVideoRescale() && media != null
            && ((media.getWidth() > params.mediaRenderer.getMaxVideoWidth())
                    || (media.getHeight() > params.mediaRenderer.getMaxVideoHeight()));

    // Video scaler and overscan compensation
    boolean scaleBool = isResolutionTooHighForRenderer
            || (configuration.isMencoderScaler()
                    && (configuration.getMencoderScaleX() != 0 || configuration.getMencoderScaleY() != 0))
            || (intOCW > 0 || intOCH > 0);

    if ((deinterlace || scaleBool) && !avisynth()) {
        StringBuilder vfValueOverscanPrepend = new StringBuilder();
        StringBuilder vfValueOverscanMiddle = new StringBuilder();
        StringBuilder vfValueVS = new StringBuilder();
        StringBuilder vfValueComplete = new StringBuilder();

        String deinterlaceComma = "";
        int scaleWidth = 0;
        int scaleHeight = 0;
        double rendererAspectRatio;

        // Set defaults
        if (media != null && media.getWidth() > 0 && media.getHeight() > 0) {
            scaleWidth = media.getWidth();
            scaleHeight = media.getHeight();
        }

        /*
         * 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 = scaleWidth + intOCWPixels;
            scaleHeight = scaleHeight + intOCHPixels;

            // See if the video needs to be scaled down
            if (params.mediaRenderer.isVideoRescale() && ((scaleWidth > params.mediaRenderer.getMaxVideoWidth())
                    || (scaleHeight > params.mediaRenderer.getMaxVideoHeight()))) {
                double overscannedAspectRatio = scaleWidth / scaleHeight;
                rendererAspectRatio = params.mediaRenderer.getMaxVideoWidth()
                        / params.mediaRenderer.getMaxVideoHeight();

                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();
                }
            }

            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();
                }
            }

            logger.info("Setting video resolution to: " + scaleWidth + "x" + scaleHeight
                    + ", your Video Scaler setting");

            vfValueVS.append("scale=").append(scaleWidth).append(":").append(scaleHeight);

            /*
             * The video resolution is too big for the renderer so we need to scale it down
             */
        } else if (media != null && media.getWidth() > 0 && media.getHeight() > 0
                && (media.getWidth() > params.mediaRenderer.getMaxVideoWidth()
                        || media.getHeight() > params.mediaRenderer.getMaxVideoHeight())) {
            double videoAspectRatio = (double) media.getWidth() / (double) media.getHeight();
            rendererAspectRatio = (double) params.mediaRenderer.getMaxVideoWidth()
                    / (double) params.mediaRenderer.getMaxVideoHeight();

            /*
             * 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 = 1080;
            } 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();
                }
            }

            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 = ",";
        }

        String vfValue = (deinterlace ? "yadif" : "") + (scaleBool ? deinterlaceComma + vfValueComplete : "");

        if (isNotBlank(vfValue)) {
            cmdList.add("-vf");
            cmdList.add(vfValue);
        }
    }

    /*
     * The PS3 and possibly other renderers display videos incorrectly
     * if the dimensions aren't divisible by 4, so if that is the
     * case we scale it down to the nearest 4.
     * This fixes the long-time bug of videos displaying in black and
     * white with diagonal strips of colour, weird one.
     *
     * TODO: Integrate this with the other stuff so that "scale" only
     * ever appears once in the MEncoder CMD.
     */
    if (media != null && (media.getWidth() % 4 != 0) || media.getHeight() % 4 != 0) {
        int newWidth;
        int newHeight;

        newWidth = (media.getWidth() / 4) * 4;
        newHeight = (media.getHeight() / 4) * 4;

        cmdList.add("-vf");
        cmdList.add("softskip,expand=" + newWidth + ":" + newHeight);
    }

    if (configuration.getMencoderMT() && !avisynth && !dvd && !(startsWith(media.getCodecV(), "mpeg2"))) {
        cmdList.add("-lavdopts");
        cmdList.add("fast");
    }

    boolean disableMc0AndNoskip = false;

    // Process the options for this file in Transcoding Settings -> Mencoder -> Expert Settings: Codec-specific parameters
    // TODO this is better handled by a plugin with scripting support and will be removed
    if (media != null) {
        String expertOptions[] = getSpecificCodecOptions(configuration.getMencoderCodecSpecificConfig(), media,
                params, filename, externalSubtitlesFileName, configuration.isMencoderIntelligentSync(), false);

        // the parameters (expertOptions) are processed in 3 passes
        // 1) process expertOptions
        // 2) process cmdList
        // 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<String, Boolean>();

            // 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<String, String>();

            // 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<String, Boolean>();

            // pass 1: process expertOptions
            for (int i = 0; i < expertOptions.length; ++i) {
                if (expertOptions[i].equals("-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...
                    removeCmdListOption.put("-ass", false); // false: option does not have a corresponding value
                    // remove -noass from expertOptions in pass 3
                    expertOptions[i] = REMOVE_OPTION;
                } else if (expertOptions[i].equals("-nomux")) {
                    expertOptions[i] = REMOVE_OPTION;
                } else if (expertOptions[i].equals("-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;
                } else if (expertOptions[i].equals("-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;
                } else if (expertOptions[i].equals("-fps")) {
                    removeCmdListOption.put("-fps", true);
                    ++i;
                } else if (expertOptions[i].equals("-ovc")) {
                    removeCmdListOption.put("-ovc", true);
                    ++i;
                } else if (expertOptions[i].equals("-channels")) {
                    removeCmdListOption.put("-channels", true);
                    ++i;
                } else if (expertOptions[i].equals("-oac")) {
                    removeCmdListOption.put("-oac", true);
                    ++i;
                } else if (expertOptions[i].equals("-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;
                } else if (expertOptions[i].equals("-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;
                } else if (expertOptions[i].equals("-vf")) {
                    mergeCmdListOption.put("-vf", "%s," + expertOptions[i + 1].replace("%", "%%"));
                    ++i;
                } else if (expertOptions[i].equals("-af")) {
                    mergeCmdListOption.put("-af", "%s," + expertOptions[i + 1].replace("%", "%%"));
                    ++i;
                } else if (expertOptions[i].equals("-nosync")) {
                    disableMc0AndNoskip = true;
                    expertOptions[i] = REMOVE_OPTION;
                } else if (expertOptions[i].equals("-mc")) {
                    disableMc0AndNoskip = true;
                }
            }

            for (String key : mergeCmdListOption.keySet()) {
                mergedCmdListOption.put(key, false);
            }

            // pass 2: process cmdList
            List<String> transformedCmdList = new ArrayList<String>();

            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 != REMOVE_OPTION) {
                    if (isTrue(mergedCmdListOption.get(option))) { // true: this option and its value have already been merged into existing cmdList options
                        ++i; // skip the value
                    } else {
                        cmdList.add(option);
                    }
                }
            }
        }
    }

    if ((pcm || dtsRemux || ac3Remux) || (configuration.isMencoderNoOutOfSync() && !disableMc0AndNoskip)) {
        if (configuration.isFix25FPSAvMismatch()) {
            cmdList.add("-mc");
            cmdList.add("0.005");
        } else {
            cmdList.add("-mc");
            cmdList.add("0");
            cmdList.add("-noskip");
        }
    }

    if (params.timeend > 0) {
        cmdList.add("-endpos");
        cmdList.add("" + params.timeend);
    }

    String rate = "48000";
    if (params.mediaRenderer.isXBOX()) {
        rate = "44100";
    }

    // force srate -> cause ac3's mencoder doesn't like anything other than 48khz
    if (media != null && !pcm && !dtsRemux && !ac3Remux) {
        cmdList.add("-af");
        cmdList.add("lavcresample=" + rate);
        cmdList.add("-srate");
        cmdList.add(rate);
    }

    // add a -cache option for piped media (e.g. rar/zip file entries):
    // 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 = null;

    if (pcm || dtsRemux) {
        // 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 || 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 = 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 {
            // remove the -oac switch, otherwise the "too many video packets" errors appear again
            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(configuration);
            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 != null && 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.setSampleFrequency(48000);
            sm.setBitsPerSample(16);

            String mixer = null;
            if (pcm && !dtsRemux) {
                mixer = getLPCMChannelMappingForMencoder(params.aid); // LPCM always outputs 5.1/7.1 for multichannel tracks. Downmix with player if needed!
            }

            sm.setNbChannels(channels);

            // it seems the -really-quiet prevents mencoder to stop the pipe output after some time...
            // -mc 0.1 make the DTS-HD extraction works better with latest mencoder builds, and makes no impact on the regular DTS one
            String ffmpegLPCMextract[] = new String[] { executable(), "-ss", "0", filename, "-really-quiet",
                    "-msglevel", "statusline=2", "-channels", "" + channels, "-ovc", "copy", "-of", "rawaudio",
                    "-mc", dtsRemux ? "0.1" : "0", "-noskip", (aid == null) ? "-quiet" : "-aid",
                    (aid == null) ? "-quiet" : aid, "-oac", (ac3Remux || dtsRemux) ? "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 != null && 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;

            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 {
                // PCM
                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");
            pwMux.close();

            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) ? 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);
            mkfifo_process.runInNewThread();

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
            }

            pipe.deleteLater();
        }
    }

    pw.runInNewThread();

    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
    }

    return pw;
}