bdsup2sub.cli.CommandLineParser.java Source code

Java tutorial

Introduction

Here is the source code for bdsup2sub.cli.CommandLineParser.java

Source

/*
 * Copyright 2014 Miklos Juhasz (mjuhasz)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package bdsup2sub.cli;

import bdsup2sub.core.*;
import bdsup2sub.utils.FilenameUtils;
import bdsup2sub.utils.optional.Optional;
import bdsup2sub.utils.SubtitleUtils;
import bdsup2sub.utils.ToolBox;
import org.apache.commons.cli.*;

import java.io.File;
import java.util.Arrays;
import java.util.Comparator;

import static bdsup2sub.cli.CommandLineOptions.*;
import static bdsup2sub.core.Configuration.*;
import static bdsup2sub.core.Constants.LANGUAGES;

public class CommandLineParser {

    private boolean printHelpMode;
    private boolean printVersionMode;
    private boolean cliMode;
    private File inputFile;
    private File outputFile;
    private Optional<OutputMode> outputMode = Optional.absent();
    private boolean loadSettings;
    private Optional<Resolution> resolution = Optional.absent();
    private Optional<Double> sourceFrameRate = Optional.absent();
    private Optional<Double> targetFrameRate = Optional.absent();
    private boolean convertFpsMode;
    private boolean synchronizeFpsMode;
    private Optional<Double> delay = Optional.absent();
    private Optional<ScalingFilter> scalingFilter = Optional.absent();
    private Optional<PaletteMode> paletteMode = Optional.absent();
    private Optional<Double> minimumDisplayTime = Optional.absent();
    private Optional<Double> maximumTimeDifference = Optional.absent();
    private Optional<CaptionMoveModeY> moveModeY = Optional.absent();
    private double screenRatio;
    private int moveYOffset;
    private Optional<CaptionMoveModeX> moveModeX = Optional.absent();
    private Optional<Integer> moveXOffset = Optional.absent();
    private Optional<Integer> cropLines = Optional.absent();
    private Optional<Integer> alphaCropThreshold = Optional.absent();
    private Optional<Double> scaleX = Optional.absent();
    private Optional<Double> scaleY = Optional.absent();
    private Optional<Boolean> exportPalette = Optional.absent();
    private Optional<Boolean> exportForcedSubtitlesOnly = Optional.absent();
    private Optional<ForcedFlagState> forcedFlagState = Optional.absent();
    private Optional<Boolean> swapCrCb = Optional.absent();
    private Optional<Boolean> fixInvisibleFrames = Optional.absent();
    private Optional<Boolean> verbose = Optional.absent();
    private Optional<Integer> alphaThreshold = Optional.absent();
    private Optional<Integer> lumLowMedThreshold = Optional.absent();
    private Optional<Integer> lumMedHighThreshold = Optional.absent();
    private Optional<Integer> languageIndex = Optional.absent();

    private File paletteFile;
    private Options options;

    public CommandLineParser() {
        this.options = new CommandLineOptions().getOptions();
    }

    public void parse(String... args) throws ParseException {
        CommandLine line = new PosixParser().parse(options, args);

        if (line.hasOption(HELP)) {
            printHelpMode = true;
        } else if (line.hasOption(VERSION)) {
            printVersionMode = true;
        } else {
            parseInputFileOption(line);
            parseOutputFileOption(line);
            cliMode = line.hasOption(OUTPUT_FILE);
            loadSettings = line.hasOption(LOAD_SETTINGS) || !cliMode;
            parseResolutionOption(line);
            parseTargetFramerateOption(line);
            parseConvertFramerateOption(line);
            parseDelayOption(line);
            parseScalingFilterOption(line);
            parsePaletteModeOption(line);
            parseMinimumDisplayTimeOption(line);
            parseMaxTimeDiffOption(line);
            parseMoveYOption(line);
            parseMoveXOption(line);
            parseCropLinesOption(line);
            parseAlphaCropThresholdOption(line);
            parseScaleOption(line);
            exportPalette = line.hasOption(EXPORT_PALETTE) ? Optional.of(Boolean.TRUE) : Optional.<Boolean>absent();
            exportForcedSubtitlesOnly = line.hasOption(EXPORT_FORCED_SUBTITLES_ONLY) ? Optional.of(Boolean.TRUE)
                    : Optional.<Boolean>absent();
            parseForcedFlagOption(line);
            swapCrCb = line.hasOption(SWAP_CR_CB) ? Optional.of(Boolean.TRUE) : Optional.<Boolean>absent();
            fixInvisibleFrames = line.hasOption(FIX_INVISIBLE_FRAMES) ? Optional.of(Boolean.TRUE)
                    : Optional.<Boolean>absent(); // TODO: accept only for SUB/IDX or SUP/IFO as target
            verbose = line.hasOption(VERBOSE) ? Optional.of(Boolean.TRUE) : Optional.<Boolean>absent();
            parseAlphaThresholdOption(line);
            parseLuminanceThresholdOption(line);
            parseLanguageCodeOption(line);
            parsePaletteFileOption(line);
        }
    }

    private void parseInputFileOption(CommandLine line) throws ParseException {
        if (line.getArgList().isEmpty() && line.hasOption(OUTPUT_FILE)) {
            throw new ParseException("Missing input file.");
        } else if (line.getArgList().size() > 1) {
            throw new ParseException("Too many input files.");
        } else if (line.getArgList().size() == 1) {
            inputFile = new File(line.getArgList().get(0).toString());
            if (!inputFile.exists()) {
                throw new ParseException("Input file not found: " + inputFile.getAbsolutePath());
            }
        }
    }

    private void parseOutputFileOption(CommandLine line) throws ParseException {
        if (line.hasOption(OUTPUT_FILE)) {
            String value = line.getOptionValue(OUTPUT_FILE);
            outputFile = new File(value);

            String extension = FilenameUtils.getExtension(value);
            if (extension.isEmpty()) {
                throw new ParseException("No extension given for output " + outputFile);
            }
            if (extension.equalsIgnoreCase("sup")) {
                outputMode = Optional.of(OutputMode.BDSUP);
            } else if (extension.equalsIgnoreCase("sub") || extension.equals("idx")) {
                outputMode = Optional.of(OutputMode.VOBSUB);
            } else if (extension.equalsIgnoreCase("xml")) {
                outputMode = Optional.of(OutputMode.XML);
            } else if (extension.equalsIgnoreCase("ifo")) {
                outputMode = Optional.of(OutputMode.SUPIFO);
            } else {
                throw new ParseException("Unknown extension of output " + outputFile);
            }
        }
    }

    private void parseResolutionOption(CommandLine line) throws ParseException {
        if (line.hasOption(RESOLUTION)) {
            String value = line.getOptionValue(RESOLUTION);
            if (value.equalsIgnoreCase("keep")) {
                // keep undefined
            } else if (value.equalsIgnoreCase("pal") || value.equalsIgnoreCase("576")) {
                resolution = Optional.of(Resolution.PAL);
            } else if (value.equalsIgnoreCase("ntsc") || value.equalsIgnoreCase("480")) {
                resolution = Optional.of(Resolution.NTSC);
            } else if (value.equalsIgnoreCase("720p") || value.equalsIgnoreCase("720")) {
                resolution = Optional.of(Resolution.HD_720);
            } else if (value.equalsIgnoreCase("1440x1080")) {
                resolution = Optional.of(Resolution.HD_1440x1080);
            } else if (value.equalsIgnoreCase("1080p") || value.equalsIgnoreCase("1080")) {
                resolution = Optional.of(Resolution.HD_1080);
            } else {
                throw new ParseException("Illegal resolution: " + value);
            }
        }
    }

    private void parseTargetFramerateOption(CommandLine line) throws ParseException {
        if (line.hasOption(TARGET_FRAMERATE)) {
            synchronizeFpsMode = true;
            String value = line.getOptionValue(TARGET_FRAMERATE);
            if (value.equalsIgnoreCase("keep")) {
                // keep undefined
            } else {
                targetFrameRate = Optional.of(SubtitleUtils.getFps(value));
                if (targetFrameRate.get() <= 0) {
                    throw new ParseException("Invalid target framerate: " + value);
                }
            }
        }
    }

    private void parseConvertFramerateOption(CommandLine line) throws ParseException {
        if (line.hasOption(CONVERT_FRAMERATE)) {
            convertFpsMode = true;
            if (line.getOptionValues(CONVERT_FRAMERATE).length != 2) {
                throw new ParseException("2 arguments needed for framerate conversion.");
            }
            String value = line.getOptionValues(CONVERT_FRAMERATE)[0];
            if (value.equalsIgnoreCase("auto")) {
                // keep undefined
            } else {
                sourceFrameRate = Optional.of(SubtitleUtils.getFps(value));
                if (sourceFrameRate.get() <= 0) {
                    throw new ParseException("Invalid source framerate: " + value);
                }
            }
            value = line.getOptionValues(CONVERT_FRAMERATE)[1];
            targetFrameRate = Optional.of(SubtitleUtils.getFps(value));
            if (targetFrameRate.get() <= 0) {
                throw new ParseException("Invalid target framerate: " + value);
            }
        }
    }

    private void parseDelayOption(CommandLine line) throws ParseException {
        if (line.hasOption(DELAY)) {
            String value = line.getOptionValue(DELAY);
            try {
                delay = Optional.of(Double.parseDouble(value.trim()));
            } catch (NumberFormatException ex) {
                throw new ParseException("Illegal delay value: " + value);
            }
        }
    }

    private void parseScalingFilterOption(CommandLine line) throws ParseException {
        if (line.hasOption(SCALING_FILTER)) {
            String value = line.getOptionValue(SCALING_FILTER);
            boolean found = false;
            for (ScalingFilter f : ScalingFilter.values()) {
                if (f.toString().equalsIgnoreCase(value)) {
                    scalingFilter = Optional.of(f);
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new ParseException("Illegal scaling filter value: " + value);
            }
        }
    }

    private void parsePaletteModeOption(CommandLine line) throws ParseException {
        if (line.hasOption(PALETTE_MODE)) {
            String value = line.getOptionValue(PALETTE_MODE);
            if (value.equalsIgnoreCase("keep")) {
                paletteMode = Optional.of(PaletteMode.KEEP_EXISTING);
            } else if (value.equalsIgnoreCase("create")) {
                paletteMode = Optional.of(PaletteMode.CREATE_NEW);
            } else if (value.equalsIgnoreCase("dither")) {
                paletteMode = Optional.of(PaletteMode.CREATE_DITHERED);
            } else {
                throw new ParseException("Invalid palette mode: " + value);
            }
        }
    }

    private void parseMinimumDisplayTimeOption(CommandLine line) throws ParseException {
        if (line.hasOption(MIN_DISPLAY_TIME)) {
            String value = line.getOptionValue(MIN_DISPLAY_TIME);
            try {
                minimumDisplayTime = Optional.of(Double.parseDouble(value.trim()));
            } catch (NumberFormatException ex) {
                throw new ParseException("Illegal minimum display time value: " + value);
            }
            if (minimumDisplayTime.get() <= 0) {
                throw new ParseException("Illegal minimum display time value: " + value);
            }
        }
    }

    private void parseMaxTimeDiffOption(CommandLine line) throws ParseException {
        if (line.hasOption(MAX_TIME_DIFF)) {
            String value = line.getOptionValue(MAX_TIME_DIFF);
            try {
                maximumTimeDifference = Optional.of(Double.parseDouble(value.trim()));
            } catch (NumberFormatException ex) {
                throw new ParseException("Illegal maximum merge time difference value: " + value);
            }
            if (maximumTimeDifference.get() < 0) {
                throw new ParseException("Illegal maximum merge time difference value: " + value);
            }
        }
    }

    private void parseMoveYOption(CommandLine line) throws ParseException {
        if (line.hasOption(MOVE_IN) || line.hasOption(MOVE_OUT)) {
            moveModeY = line.hasOption(MOVE_IN) ? Optional.of(CaptionMoveModeY.MOVE_INSIDE_BOUNDS)
                    : Optional.of(CaptionMoveModeY.MOVE_OUTSIDE_BOUNDS);
            String option = line.hasOption(MOVE_IN) ? MOVE_IN : MOVE_OUT;
            if (line.getOptionValues(option).length != 2) {
                throw new ParseException("2 arguments needed for moving captions.");
            }
            screenRatio = ToolBox.getDouble(line.getOptionValues(option)[0]);
            if (screenRatio <= (16.0 / 9)) {
                throw new ParseException("Invalid screen ratio: " + screenRatio);
            }
            moveYOffset = ToolBox.getInt(line.getOptionValues(option)[1]);
            if (moveYOffset < 0) {
                throw new ParseException("Invalid pixel offset: " + moveYOffset);
            }
        }
    }

    private void parseMoveXOption(CommandLine line) throws ParseException {
        if (line.hasOption(MOVE_X)) {
            if (line.getOptionValues(MOVE_X) == null || line.getOptionValues(MOVE_X).length < 1) {
                throw new ParseException("Missing argument for moving captions.");
            }
            String value = line.getOptionValues(MOVE_X)[0];
            if (value.equalsIgnoreCase("left")) {
                moveModeX = Optional.of(CaptionMoveModeX.LEFT);
            } else if (value.equalsIgnoreCase("center")) {
                moveModeX = Optional.of(CaptionMoveModeX.CENTER);
            } else if (value.equalsIgnoreCase("right")) {
                moveModeX = Optional.of(CaptionMoveModeX.RIGHT);
            } else {
                throw new ParseException("Invalid move mode: " + value);
            }

            if ((moveModeX.get() == CaptionMoveModeX.LEFT || moveModeX.get() == CaptionMoveModeX.RIGHT)
                    && line.getOptionValues(MOVE_X).length > 1) {
                value = line.getOptionValues(MOVE_X)[1];
                moveXOffset = Optional.of(ToolBox.getInt(value));
                if (moveXOffset.get() < 0) {
                    throw new ParseException("Invalid pixel offset: " + value);
                }
            }
        }
    }

    private void parseCropLinesOption(CommandLine line) throws ParseException {
        if (line.hasOption(CROP_LINES)) {
            String value = line.getOptionValue(CROP_LINES);
            cropLines = Optional.of(ToolBox.getInt(value.trim()));
            if (cropLines.get() < 0) {
                throw new ParseException("Invalid crop lines value: " + value);
            }
        }
    }

    private void parseAlphaCropThresholdOption(CommandLine line) throws ParseException {
        if (line.hasOption(ALPHA_CROP_THRESHOLD)) {
            String value = line.getOptionValue(ALPHA_CROP_THRESHOLD);
            alphaCropThreshold = Optional.of(ToolBox.getInt(value.trim()));
            if (alphaCropThreshold.get() < 0 || alphaCropThreshold.get() > 255) {
                throw new ParseException("Illegal number range for alpha cropping threshold: " + value);
            }
        }
    }

    private void parseScaleOption(CommandLine line) throws ParseException {
        if (line.hasOption(SCALE)) {
            if (line.getOptionValues(SCALE).length != 2) {
                throw new ParseException("2 arguments needed for scaling.");
            }
            String value = line.getOptionValues(SCALE)[0];
            scaleX = Optional.of(ToolBox.getDouble(value));
            if (scaleX.get() < MIN_FREE_SCALE_FACTOR || scaleX.get() > MAX_FREE_SCALE_FACTOR) {
                throw new ParseException("Invalid x scaling factor: " + value);
            }
            value = line.getOptionValues(SCALE)[1];
            scaleY = Optional.of(ToolBox.getDouble(value));
            if (scaleY.get() < MIN_FREE_SCALE_FACTOR || scaleY.get() > MAX_FREE_SCALE_FACTOR) {
                throw new ParseException("Invalid y scaling factor: " + value);
            }
        }
    }

    private void parseForcedFlagOption(CommandLine line) throws ParseException {
        if (line.hasOption(FORCED_FLAG)) {
            String value = line.getOptionValue(FORCED_FLAG);
            if (value.equalsIgnoreCase("set")) {
                forcedFlagState = Optional.of(ForcedFlagState.SET);
            } else if (value.equalsIgnoreCase("clear")) {
                forcedFlagState = Optional.of(ForcedFlagState.CLEAR);
            } else {
                throw new ParseException("Invalid forced flag state: " + value);
            }
        }
    }

    private void parseAlphaThresholdOption(CommandLine line) throws ParseException {
        if (line.hasOption(ALPHA_THRESHOLD)) { // TODO: accept only for SUB/IDX or SUP/IFO as target
            String value = line.getOptionValue(ALPHA_THRESHOLD);
            alphaThreshold = Optional.of(ToolBox.getInt(value.trim()));
            if (alphaThreshold.get() < 0 || alphaThreshold.get() > 255) {
                throw new ParseException("Illegal number range for alpha threshold: " + value);
            }
        }
    }

    private void parseLuminanceThresholdOption(CommandLine line) throws ParseException {
        if (line.hasOption(LUM_LOW_MED_THRESHOLD)) { // TODO: accept only for SUB/IDX or SUP/IFO as target
            String value = line.getOptionValue(LUM_LOW_MED_THRESHOLD);
            lumLowMedThreshold = Optional.of(ToolBox.getInt(value.trim()));
            if (lumLowMedThreshold.get() < 0 || lumLowMedThreshold.get() > 255) {
                throw new ParseException("Illegal number range for luminance threshold: " + value);
            }
        }
        if (line.hasOption(LUM_MED_HIGH_THRESHOLD)) { // TODO: accept only for SUB/IDX or SUP/IFO as target
            String value = line.getOptionValue(LUM_MED_HIGH_THRESHOLD);
            lumMedHighThreshold = Optional.of(ToolBox.getInt(value.trim()));
            if (lumMedHighThreshold.get() < 0 || lumMedHighThreshold.get() > 255) {
                throw new ParseException("Illegal number range for luminance threshold: " + value);
            }
        }
        if (lumLowMedThreshold.isPresent() || lumMedHighThreshold.isPresent()) {
            int lowMed = lumLowMedThreshold.isPresent() ? lumLowMedThreshold.get()
                    : DEFAULT_LUMINANCE_LOW_MED_THRESHOLD;
            int medHigh = lumMedHighThreshold.isPresent() ? lumMedHighThreshold.get()
                    : DEFAULT_LUMINANCE_MED_HIGH_THRESHOLD;
            if (lowMed > medHigh) {
                throw new ParseException(
                        "Invalid luminance threshold values: " + lumLowMedThreshold + ", " + lumMedHighThreshold);
            }
        }
    }

    private void parseLanguageCodeOption(CommandLine line) throws ParseException {
        if (line.hasOption(LANGUAGE_CODE)) { //TODO: only sub/idx
            String value = line.getOptionValue(LANGUAGE_CODE);
            boolean found = false;
            for (int i = 0; i < LANGUAGES.length; i++)
                if (LANGUAGES[i][1].equalsIgnoreCase(value)) {
                    languageIndex = Optional.of(i);
                    found = true;
                    break;
                }
            if (!found) {
                StringBuilder sb = new StringBuilder();
                sb.append("Unknown language code: " + value + "\n");
                sb.append("Use one of the following 2 character codes:\n");
                for (String[] language : LANGUAGES) {
                    sb.append("    " + language[1] + " - " + language[0] + "\n");
                }
                throw new ParseException(sb.toString());
            }
        }
    }

    private void parsePaletteFileOption(CommandLine line) throws ParseException {
        if (line.hasOption(PALETTE_FILE)) { //TODO: only sub/idx
            String value = line.getOptionValue(PALETTE_FILE);
            paletteFile = new File(value);
            if (!paletteFile.exists()) {
                throw new ParseException("Palette file not found: " + value);
            } else {
                if (!Arrays.equals("#COL".getBytes(), ToolBox.getFileID(value, 4))) {
                    throw new ParseException("Invalid palette file: " + value);
                }
            }
        }
    }

    public boolean isPrintHelpMode() {
        return printHelpMode;
    }

    public boolean isPrintVersionMode() {
        return printVersionMode;
    }

    public boolean isCliMode() {
        return cliMode;
    }

    public File getInputFile() {
        return inputFile;
    }

    public File getOutputFile() {
        return outputFile;
    }

    public Optional<OutputMode> getOutputMode() {
        return outputMode;
    }

    public boolean isLoadSettings() {
        return loadSettings;
    }

    public Optional<Resolution> getResolution() {
        return resolution;
    }

    public Optional<Double> getSourceFrameRate() {
        return sourceFrameRate;
    }

    public Optional<Double> getTargetFrameRate() {
        return targetFrameRate;
    }

    public boolean isConvertFpsMode() {
        return convertFpsMode;
    }

    public boolean isSynchronizeFpsMode() {
        return synchronizeFpsMode;
    }

    public Optional<Double> getDelay() {
        return delay;
    }

    public Optional<ScalingFilter> getScalingFilter() {
        return scalingFilter;
    }

    public Optional<PaletteMode> getPaletteMode() {
        return paletteMode;
    }

    public Optional<Double> getMinimumDisplayTime() {
        return minimumDisplayTime;
    }

    public Optional<Double> getMaximumTimeDifference() {
        return maximumTimeDifference;
    }

    public Optional<CaptionMoveModeY> getMoveModeY() {
        return moveModeY;
    }

    public int getMoveYOffset() {
        return moveYOffset;
    }

    public Optional<CaptionMoveModeX> getMoveModeX() {
        return moveModeX;
    }

    public Optional<Integer> getMoveXOffset() {
        return moveXOffset;
    }

    public double getScreenRatio() {
        return screenRatio;
    }

    public Optional<Integer> getCropLines() {
        return cropLines;
    }

    public Optional<Integer> getAlphaCropThreshold() {
        return alphaCropThreshold;
    }

    public Optional<Double> getScaleX() {
        return scaleX;
    }

    public Optional<Double> getScaleY() {
        return scaleY;
    }

    public Optional<Boolean> isExportPalette() {
        return exportPalette;
    }

    public Optional<Boolean> isExportForcedSubtitlesOnly() {
        return exportForcedSubtitlesOnly;
    }

    public Optional<ForcedFlagState> getForcedFlagState() {
        return forcedFlagState;
    }

    public Optional<Boolean> isSwapCrCb() {
        return swapCrCb;
    }

    public Optional<Boolean> isFixInvisibleFrames() {
        return fixInvisibleFrames;
    }

    public Optional<Boolean> isVerbose() {
        return verbose;
    }

    public Optional<Integer> getAlphaThreshold() {
        return alphaThreshold;
    }

    public Optional<Integer> getLumLowMedThreshold() {
        return lumLowMedThreshold;
    }

    public Optional<Integer> getLumMedHighThreshold() {
        return lumMedHighThreshold;
    }

    public Optional<Integer> getLanguageIndex() {
        return languageIndex;
    }

    public File getPaletteFile() {
        return paletteFile;
    }

    public void printHelp() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.setOptionComparator(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Option opt1 = (Option) o1;
                Option opt2 = (Option) o2;

                int opt1Index = OPTION_ORDER.indexOf(opt1.getOpt());
                int opt2Index = OPTION_ORDER.indexOf(opt2.getOpt());

                return (int) Math.signum(opt1Index - opt2Index);
            }
        });
        formatter.setWidth(79);
        String command = System.getProperty("wrapper") == null ? "java -jar BDSup2Sub" : "bdsup2sub";
        formatter.printHelp(command + " [options] -o <output> <input>", options);
    }
}