<commit_before>/*
 * Copyright (C) 2011, British Broadcasting Corporation
 * All Rights Reserved.
 *
 * Author: <NAME>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the British Broadcasting Corporation nor the names
 *       of its contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define __STDC_FORMAT_MACROS 1

#include <cstdio>
#include <cstring>
#include <cerrno>

#include <string>
#include <vector>

#include <bmx/mxf_reader/MXFFileReader.h>
#include <bmx/mxf_reader/MXFGroupReader.h>
#include <bmx/mxf_reader/MXFSequenceReader.h>
#include <bmx/essence_parser/SoundConversion.h>
#include <bmx/clip_writer/ClipWriter.h>
#include <bmx/as02/AS02PictureTrack.h>
#include <bmx/as02/AS02PCMTrack.h>
#include <bmx/avid_mxf/AvidPCMTrack.h>
#include <bmx/mxf_op1a/OP1APCMTrack.h>
#include <bmx/URI.h>
#include <bmx/MXFUtils.h>
#include "../AppUtils.h"
#include "../AS11Helper.h"
#include <bmx/BMXException.h>
#include <bmx/Logging.h>

#include <mxf/mxf_avid.h>

using namespace std;
using namespace bmx;
using namespace mxfpp;



typedef struct
{
    FrameBuffer *input_buffer;
    ClipWriterTrack *track;
    EssenceType essence_type;
    bool is_picture;
    uint32_t channel_count;
    uint32_t channel_index;
    bool is_d10_aes3;
    uint32_t bits_per_sample;
    uint16_t block_align;
} OutputTrack;

typedef struct
{
    const char *position_str;
    AvidLocator locator;
} LocatorOption;


static const char DEFAULT_SHIM_NAME[]       = "Sample File";
static const char DEFAULT_SHIM_ID[]         = "http://bbc.co.uk/rd/as02/default-shim.txt";
static const char DEFAULT_SHIM_ANNOTATION[] = "Default AS-02 shim";


extern bool BMX_REGRESSION_TEST;



static bool parse_mic_type(const char *mic_type_str, MICType *mic_type)
{
    if (strcmp(mic_type_str, "md5") == 0)
        *mic_type = MD5_MIC_TYPE;
    else if (strcmp(mic_type_str, "none") == 0)
        *mic_type = NONE_MIC_TYPE;
    else
        return false;

    return true;
}

static bool parse_clip_type(const char *clip_type_str, ClipWriterType *clip_type)
{
    if (strcmp(clip_type_str, "as02") == 0)
        *clip_type = CW_AS02_CLIP_TYPE;
    else if (strcmp(clip_type_str, "as11op1a") == 0)
        *clip_type = CW_AS11_OP1A_CLIP_TYPE;
    else if (strcmp(clip_type_str, "as11d10") == 0)
        *clip_type = CW_AS11_D10_CLIP_TYPE;
    else if (strcmp(clip_type_str, "op1a") == 0)
        *clip_type = CW_OP1A_CLIP_TYPE;
    else if (strcmp(clip_type_str, "avid") == 0)
        *clip_type = CW_AVID_CLIP_TYPE;
    else if (strcmp(clip_type_str, "d10") == 0)
        *clip_type = CW_D10_CLIP_TYPE;
    else
        return false;

    return true;
}

static string get_version_info()
{
    char buffer[256];
    sprintf(buffer, "bmxtranswrap, %s v%s (%s %s)", PACKAGE_NAME, PACKAGE_VERSION, __DATE__, __TIME__);
    return buffer;
}

static void usage(const char *cmd)
{
    fprintf(stderr, "%s\n", get_version_info().c_str());
    fprintf(stderr, "Usage: %s <<options>> [<mxf input>]+\n", cmd);
    fprintf(stderr, "Options (* means option is required):\n");
    fprintf(stderr, "  -h | --help             Show usage and exit\n");
    fprintf(stderr, "  -v | --version          Print version info\n");
    fprintf(stderr, "  -p                      Print progress percentage to stdout\n");
    fprintf(stderr, "  -l <file>               Log filename. Default log to stderr/stdout\n");
    fprintf(stderr, "  -t <type>               Clip type: as02, as11op1a, as11d10, op1a, avid, d10. Default is as02\n");
    fprintf(stderr, "* -o <name>               as02: <name> is a bundle name\n");
    fprintf(stderr, "                          as11op1a/as11d10/op1a/d10: <name> is a filename\n");
    fprintf(stderr, "                          avid: <name> is a filename prefix\n");
    fprintf(stderr, "  -y <hh:mm:sscff>        Override input start timecode. Is drop frame when c is not ':'. Default 00:00:00:00\n");
    fprintf(stderr, "  --tc-rate <rate>        Start timecode rate to use when input is audio only\n");
    fprintf(stderr, "                          Values are 25 (default), 30 (30000/1001), 50 or 60 (60000/1001)\n");
    fprintf(stderr, "  --clip <name>           Set the clip name\n");
    fprintf(stderr, "  --start <frame>         Set the start frame. Default is 0\n");
    fprintf(stderr, "  --dur <frame>           Set the duration in frames. Default is minimum input duration\n");
    fprintf(stderr, "  --group                 Use the group reader instead of the sequence reader\n");
    fprintf(stderr, "                          Use this option if the files have different material packages\n");
    fprintf(stderr, "                          but actually belong to the same virtual package / group\n");
    fprintf(stderr, "  --no-reorder            Don't attempt to order the inputs in a sequence\n");
    fprintf(stderr, "                          Use this option for files with broken timecode\n");
    fprintf(stderr, "  --avcihead <format> <file> <offset>\n");
    fprintf(stderr, "                          Default AVC-Intra sequence header data (512 bytes) to use when the input file does not have it\n");
    fprintf(stderr, "                          <format> is a comma separated list of one or more of the following integer values:\n");
    size_t i;
    for (i = 0; i < get_num_avci_header_formats(); i++)
        fprintf(stderr, "                              %2"PRIszt": %s\n", i, get_avci_header_format_string(i));
    fprintf(stderr, "                          or set <format> to 'all' for all formats listed above\n");
    fprintf(stderr, "                          The 512 bytes are extracted from <file> starting at <offset> bytes\n");
    fprintf(stderr, "                              and incrementing 512 bytes for each format in the list\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  as02:\n");
    fprintf(stderr, "    --mic-type <type>       Media integrity check type: 'md5' or 'none'. Default 'md5'\n");
    fprintf(stderr, "    --mic-file              Calculate checksum for entire essence component file. Default is essence only\n");
    fprintf(stderr, "    --shim-name <name>      Set ShimName element value in shim.xml file to <name>. Default is '%s'\n", DEFAULT_SHIM_NAME);
    fprintf(stderr, "    --shim-id <id>          Set ShimID element value in shim.xml file to <id>. Default is '%s'\n", DEFAULT_SHIM_ID);
    fprintf(stderr, "    --shim-annot <str>      Set AnnotationText element value in shim.xml file to <str>. Default is '%s'\n", DEFAULT_SHIM_ANNOTATION);
    fprintf(stderr, "\n");
    fprintf(stderr, "  as02/as11op1a/op1a:\n");
    fprintf(stderr, "    --part <interval>       Video essence partition interval in frames, or seconds with 's' suffix. Default single partition\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  as11op1a/as11d10:\n");
    fprintf(stderr, "    --dm <fwork> <name> <value>    Set descriptive framework property. <fwork> is 'as11' or 'dpp'\n");
    fprintf(stderr, "    --dm-file <fwork> <name>       Parse and set descriptive framework properties from text file <name>. <fwork> is 'as11' or 'dpp'\n");
    fprintf(stderr, "    --seg <name>                   Parse and set segmentation data from text file <name>\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  as02/as11op1a/as11d10/op1a/d10:\n");
    fprintf(stderr, "    --afd <value>           Active Format Descriptor code. Default is input file's value or not set\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  as11op1a/as11d10/op1a/d10:\n");
    fprintf(stderr, "    --single-pass           Write file in a single pass\n");
    fprintf(stderr, "                            For as11op1a/op1a the header and body partitions will be incomplete\n");
    fprintf(stderr, "                            For as11d10/d10 all the partitions will be complete\n");
    fprintf(stderr, "    --file-md5              Calculate an MD5 checksum of the file. This requires writing in a single pass (--single-pass is assumed)\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  avid:\n");
    fprintf(stderr, "    --project <name>        Set the Avid project name\n");
    fprintf(stderr, "    --tape <name>           Source tape name\n");
    fprintf(stderr, "    --comment <string>      Add 'Comments' user comment to the MaterialPackage\n");
    fprintf(stderr, "    --desc <string>         Add 'Descript' user comment to the MaterialPackage\n");
    fprintf(stderr, "    --tag <name> <value>    Add <name> user comment to the MaterialPackage. Option can be used multiple times\n");
    fprintf(stderr, "    --locator <position> <comment> <color>\n");
    fprintf(stderr, "                            Add locator at <position> with <comment> and <color>\n");
    fprintf(stderr, "                            <position> format is o?hh:mm:sscff, where the optional 'o' indicates it is an offset\n");
}

int main(int argc, const char** argv)
{
    Rational timecode_rate = FRAME_RATE_25;
    bool timecode_rate_set = false;
    vector<const char *> input_filenames;
    const char *log_filename = 0;
    ClipWriterType clip_type = CW_AS02_CLIP_TYPE;
    const char *output_name = "";
    Timecode start_timecode;
    const char *start_timecode_str = 0;
    int64_t start = 0;
    int64_t duration = -1;
    const char *clip_name = 0;
    MICType mic_type = MD5_MIC_TYPE;
    MICScope ess_component_mic_scope = ESSENCE_ONLY_MIC_SCOPE;
    const char *partition_interval_str = 0;
    int64_t partition_interval = 0;
    const char *shim_name = 0;
    const char *shim_id = 0;
    const char *shim_annot = 0;
    const char *project_name = 0;
    const char *tape_name = 0;
    map<string, string> user_comments;
    vector<LocatorOption> locators;
    AS11Helper as11_helper;
    const char *segmentation_filename = 0;
    bool do_print_version = false;
    bool use_group_reader = false;
    bool keep_input_order = false;
    uint8_t user_afd = 0;
    vector<AVCIHeaderInput> avci_header_inputs;
    bool show_progress = false;
    bool single_pass = false;
    bool file_md5 = false;
    int value;
    int cmdln_index;

    if (argc == 1) {
        usage(argv[0]);
        return 0;
    }

    for (cmdln_index = 1; cmdln_index < argc; cmdln_index++)
    {
        if (strcmp(argv[cmdln_index], "--help") == 0 ||
            strcmp(argv[cmdln_index], "-h") == 0)
        {
            usage(argv[0]);
            return 0;
        }
        else if (strcmp(argv[cmdln_index], "--version") == 0 ||
                 strcmp(argv[cmdln_index], "-v") == 0)
        {
            if (argc == 2) {
                printf("%s\n", get_version_info().c_str());
                return 0;
            }
            do_print_version = true;
        }
        else if (strcmp(argv[cmdln_index], "-p") == 0)
        {
            show_progress = true;
        }
        else if (strcmp(argv[cmdln_index], "-l") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            log_filename = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "-t") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!parse_clip_type(argv[cmdln_index + 1], &clip_type))
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "-o") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            output_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "-y") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            start_timecode_str = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--tc-rate") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (sscanf(argv[cmdln_index + 1], "%u", &value) != 1 ||
                (value != 25 && value != 30 && value != 50 && value != 60))
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            if (value == 25)
                timecode_rate = FRAME_RATE_25;
            else if (value == 30)
                timecode_rate = FRAME_RATE_2997;
            else if (value == 50)
                timecode_rate = FRAME_RATE_50;
            else
                timecode_rate = FRAME_RATE_5994;
            timecode_rate_set = true;
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--clip") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            clip_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--start") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (sscanf(argv[cmdln_index + 1], "%"PRId64, &start) != 1 || start < 0)
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--dur") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (sscanf(argv[cmdln_index + 1], "%"PRId64, &duration) != 1 || duration < 0)
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--group") == 0)
        {
            use_group_reader = true;
        }
        else if (strcmp(argv[cmdln_index], "--no-reorder") == 0)
        {
            keep_input_order = true;
        }
        else if (strcmp(argv[cmdln_index], "--avcihead") == 0)
        {
            if (cmdln_index + 3 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!parse_avci_header(argv[cmdln_index + 1], argv[cmdln_index + 2], argv[cmdln_index + 3],
                                   &avci_header_inputs))
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid values '%s', '%s', '%s' for option '%s'\n",
                        argv[cmdln_index + 1], argv[cmdln_index + 2], argv[cmdln_index + 3], argv[cmdln_index]);
                return 1;
            }
            cmdln_index += 3;
        }
        else if (strcmp(argv[cmdln_index], "--mic-type") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!parse_mic_type(argv[cmdln_index + 1], &mic_type))
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--mic-file") == 0)
        {
            ess_component_mic_scope = ENTIRE_FILE_MIC_SCOPE;
        }
        else if (strcmp(argv[cmdln_index], "--shim-name") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            shim_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--shim-id") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            shim_id = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--shim-annot") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            shim_annot = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--part") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            partition_interval_str = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--dm") == 0)
        {
            if (cmdln_index + 3 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!as11_helper.SetFrameworkProperty(argv[cmdln_index + 1], argv[cmdln_index + 2], argv[cmdln_index + 3]))
            {
                usage(argv[0]);
                fprintf(stderr, "Failed to set '%s' framework property '%s' to '%s'\n",
                        argv[cmdln_index + 1], argv[cmdln_index + 2], argv[cmdln_index + 3]);
                return 1;
            }
            cmdln_index += 3;
        }
        else if (strcmp(argv[cmdln_index], "--dm-file") == 0)
        {
            if (cmdln_index + 2 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!as11_helper.ParseFrameworkFile(argv[cmdln_index + 1], argv[cmdln_index + 2]))
            {
                usage(argv[0]);
                fprintf(stderr, "Failed to parse '%s' framework file '%s'\n",
                        argv[cmdln_index + 1], argv[cmdln_index + 2]);
                return 1;
            }
            cmdln_index += 2;
        }
        else if (strcmp(argv[cmdln_index], "--seg") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            segmentation_filename = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--afd") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (sscanf(argv[cmdln_index + 1], "%d", &value) != 1 ||
                value <= 0 || value > 255)
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            user_afd = value;
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--single-pass") == 0)
        {
            single_pass = true;
        }
        else if (strcmp(argv[cmdln_index], "--file-md5") == 0)
        {
            file_md5 = true;
        }
        else if (strcmp(argv[cmdln_index], "--project") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            project_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--tape") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            tape_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--comment") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            user_comments["Comments"] = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--desc") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for %s\n", argv[cmdln_index]);
                return 1;
            }
            user_comments["Descript"] = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--tag") == 0)
        {
            if (cmdln_index + 2 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for %s\n", argv[cmdln_index]);
                return 1;
            }
            user_comments[argv[cmdln_index + 1]] = argv[cmdln_index + 2];
            cmdln_index += 2;
        }
        else if (strcmp(argv[cmdln_index], "--locator") == 0)
        {
            if (cmdln_index + 3 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for %s\n", argv[cmdln_index]);
                return 1;
            }
            LocatorOption locator;
            locator.position_str = argv[cmdln_index + 1];
            locator.locator.comment = argv[cmdln_index + 2];
            if (!parse_color(argv[cmdln_index + 3], &locator.locator.color))
            {
                usage(argv[0]);
                fprintf(stderr, "Failed to read --locator <color> value '%s'\n", argv[cmdln_index + 3]);
                return 1;
            }
            locators.push_back(locator);
            cmdln_index += 3;
        }
        else if (strcmp(argv[cmdln_index], "--regtest") == 0)
        {
            BMX_REGRESSION_TEST = true;
        }
        else
        {
            break;
        }
    }

    for (; cmdln_index < argc; cmdln_index++)
        input_filenames.push_back(argv[cmdln_index]);

    if (input_filenames.empty()) {
        usage(argv[0]);
        fprintf(stderr, "No input filenames\n");
        return 1;
    }


    if (log_filename) {
        if (!open_log_file(log_filename))
            return 1;
    }

    connect_libmxf_logging();

    if (BMX_REGRESSION_TEST) {
        mxf_set_regtest_funcs();
        mxf_avid_set_regtest_funcs();
    }

    if (do_print_version)
        log_info("%s\n", get_version_info().c_str());


    int cmd_result = 0;
    try
    {
        // open an MXFReader using the input filenames

        MXFReader *reader;
        if (use_group_reader) {
            MXFGroupReader *group_reader = new MXFGroupReader();
            size_t i;
            for (i = 0; i < input_filenames.size(); i++) {
                MXFFileReader *file_reader = new MXFFileReader();
                MXFFileReader::OpenResult result = file_reader->Open(input_filenames[i]);
                if (result != MXFFileReader::MXF_RESULT_SUCCESS) {
                    log_error("Failed to open MXF file '%s': %s\n", input_filenames[i],
                              MXFFileReader::ResultToString(result).c_str());
                    delete file_reader;
                    throw false;
                }

                group_reader->AddReader(file_reader);
            }
            if (!group_reader->Finalize())
                throw false;

            reader = group_reader;
        } else if (input_filenames.size() > 1) {
            MXFSequenceReader *seq_reader = new MXFSequenceReader();
            size_t i;
            for (i = 0; i < input_filenames.size(); i++) {
                MXFFileReader *file_reader = new MXFFileReader();
                MXFFileReader::OpenResult result = file_reader->Open(input_filenames[i]);
                if (result != MXFFileReader::MXF_RESULT_SUCCESS) {
                    log_error("Failed to open MXF file '%s': %s\n", input_filenames[i],
                              MXFFileReader::ResultToString(result).c_str());
                    delete file_reader;
                    throw false;
                }

                seq_reader->AddReader(file_reader);
            }
            if (!seq_reader->Finalize(false, keep_input_order))
                throw false;

            reader = seq_reader;
        } else {
            MXFFileReader *file_reader = new MXFFileReader();
            MXFFileReader::OpenResult result = file_reader->Open(input_filenames[0]);
            if (result != MXFFileReader::MXF_RESULT_SUCCESS) {
                log_error("Failed to open MXF file '%s': %s\n", input_filenames[0],
                          MXFFileReader::ResultToString(result).c_str());
                delete file_reader;
                throw false;
            }

            reader = file_reader;
        }

        Rational frame_rate = reader->GetSampleRate();


        // check support for tracks and disable unsupported track types

        size_t i;
        for (i = 0; i < reader->GetNumTrackReaders(); i++) {
            MXFTrackReader *track_reader = reader->GetTrackReader(i);
            const MXFTrackInfo *input_track_info = track_reader->GetTrackInfo();

            const MXFSoundTrackInfo *input_sound_info = dynamic_cast<const MXFSoundTrackInfo*>(input_track_info);
            const MXFPictureTrackInfo *input_picture_info = dynamic_cast<const MXFPictureTrackInfo*>(input_track_info);

            bool is_supported = true;
            if (input_track_info->essence_type == WAVE_PCM)
            {
                Rational sampling_rate = input_sound_info->sampling_rate;
                if (!ClipWriterTrack::IsSupported(clip_type, WAVE_PCM, sampling_rate)) {
                    log_warn("Track %"PRIszt" essence type '%s' @%d/%d sps not supported by clip type '%s'\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type),
                             sampling_rate.numerator, sampling_rate.denominator,
                             ClipWriter::ClipWriterTypeToString(clip_type).c_str());
                    is_supported = false;
                } else if (input_sound_info->bits_per_sample == 0 || input_sound_info->bits_per_sample > 32) {
                    log_warn("Track %"PRIszt" (%s) bits per sample %u not supported\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type),
                             input_sound_info->bits_per_sample);
                    is_supported = false;
                }
            }
            else if (input_track_info->IsD10Audio()) // essence_type == SOUND_ESSENCE
            {
                if (input_sound_info->sampling_rate.numerator != 48000 ||
                    input_sound_info->sampling_rate.denominator != 1)
                {
                    log_warn("Track %"PRIszt" essence type D-10 AES-3 audio sampling rate %d/%d not supported\n",
                             i,
                             input_sound_info->sampling_rate.numerator, input_sound_info->sampling_rate.denominator);
                    is_supported = false;
                }
                else if (input_sound_info->bits_per_sample == 0 || input_sound_info->bits_per_sample > 32)
                {
                    log_warn("Track %"PRIszt" essence type D-10 AES-3 audio bits per sample %u not supported\n",
                             i,
                             input_sound_info->bits_per_sample);
                    is_supported = false;
                }
            }
            else if (input_track_info->essence_type == UNKNOWN_ESSENCE_TYPE ||
                     input_track_info->essence_type == PICTURE_ESSENCE ||
                     input_track_info->essence_type == SOUND_ESSENCE)
            {
                log_warn("Track %"PRIszt" has unknown essence type\n", i);
                is_supported = false;
            }
            else
            {
                if (input_track_info->edit_rate != frame_rate) {
                    log_warn("Track %"PRIszt" (essence type '%s') edit rate %d/%d does not equals clip edit rate %d/%d\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type),
                             input_track_info->edit_rate.numerator, input_track_info->edit_rate.denominator,
                             frame_rate.numerator, frame_rate.denominator,
                             ClipWriter::ClipWriterTypeToString(clip_type).c_str());
                    is_supported = false;
                } else if (!ClipWriterTrack::IsSupported(clip_type, input_track_info->essence_type, frame_rate)) {
                    log_warn("Track %"PRIszt" essence type '%s' @%d/%d fps not supported by clip type '%s'\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type),
                             frame_rate.numerator, frame_rate.denominator,
                             ClipWriter::ClipWriterTypeToString(clip_type).c_str());
                    is_supported = false;
                }

                if ((input_track_info->essence_type == D10_30 ||
                        input_track_info->essence_type == D10_40 ||
                        input_track_info->essence_type == D10_50) &&
                    input_picture_info->d10_frame_size == 0)
                {
                    log_warn("Track %"PRIszt" (essence type '%s') has zero frame size\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type));
                    is_supported = false;
                }
                else if ((input_track_info->essence_type == AVCI100_1080I ||
                               input_track_info->essence_type == AVCI100_1080P ||
                               input_track_info->essence_type == AVCI100_720P ||
                               input_track_info->essence_type == AVCI50_1080I ||
                               input_track_info->essence_type == AVCI50_1080P ||
                               input_track_info->essence_type == AVCI50_720P) &&
                         !track_reader->HaveAVCIHeader() &&
                         !have_avci_header_data(input_track_info->essence_type, input_track_info->edit_rate,
                                                avci_header_inputs))
                {
                    log_warn("Track %"PRIszt" (essence type '%s') does not have sequence and picture parameter sets\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type));
                    is_supported = false;
                }
            }

            if (!is_supported) {
                log_warn("Ignoring unsupported track %"PRIszt" (essence type '%s')\n",
                          i, essence_type_to_string(input_track_info->essence_type));
            }

            track_reader->SetEnable(is_supported);
        }

        if (!reader->IsEnabled()) {
            log_error("All tracks are disabled\n");
            throw false;
        }


        // set read limits

        int64_t read_start = start;
        if (read_start >= reader->GetDuration()) {
            log_error("Start position %"PRId64" is >= input duration %"PRId64"\n",
                      start, reader->GetDuration());
            throw false;
        }
        int64_t output_duration;
        if (duration >= 0) {
            output_duration = duration;
            if (read_start + output_duration > reader->GetDuration()) {
                log_warn("Limiting duration %"PRId64" because it exceeds the available duration %"PRId64"\n",
                          duration, reader->GetDuration() - read_start);
                output_duration = reader->GetDuration() - read_start;
            }
        } else {
            output_duration = reader->GetDuration() - read_start;
        }

        int16_t precharge = 0;
        int16_t rollout = 0;
        if (output_duration > 0) {
            precharge = reader->GetMaxPrecharge(read_start, true);
            rollout = reader->GetMaxRollout(read_start + output_duration - 1, true);
            reader->SetReadLimits(read_start + precharge, - precharge + output_duration + rollout, true);
        }

<commit_msg>bmxtranswrap: adjust duration and start if precharge and avid clip type<commit_after>/*
 * Copyright (C) 2011, British Broadcasting Corporation
 * All Rights Reserved.
 *
 * Author: <NAME>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the British Broadcasting Corporation nor the names
 *       of its contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define __STDC_FORMAT_MACROS 1

#include <cstdio>
#include <cstring>
#include <cerrno>

#include <string>
#include <vector>

#include <bmx/mxf_reader/MXFFileReader.h>
#include <bmx/mxf_reader/MXFGroupReader.h>
#include <bmx/mxf_reader/MXFSequenceReader.h>
#include <bmx/essence_parser/SoundConversion.h>
#include <bmx/clip_writer/ClipWriter.h>
#include <bmx/as02/AS02PictureTrack.h>
#include <bmx/as02/AS02PCMTrack.h>
#include <bmx/avid_mxf/AvidPCMTrack.h>
#include <bmx/mxf_op1a/OP1APCMTrack.h>
#include <bmx/URI.h>
#include <bmx/MXFUtils.h>
#include "../AppUtils.h"
#include "../AS11Helper.h"
#include <bmx/BMXException.h>
#include <bmx/Logging.h>

#include <mxf/mxf_avid.h>

using namespace std;
using namespace bmx;
using namespace mxfpp;



typedef struct
{
    FrameBuffer *input_buffer;
    ClipWriterTrack *track;
    EssenceType essence_type;
    bool is_picture;
    uint32_t channel_count;
    uint32_t channel_index;
    bool is_d10_aes3;
    uint32_t bits_per_sample;
    uint16_t block_align;
} OutputTrack;

typedef struct
{
    const char *position_str;
    AvidLocator locator;
} LocatorOption;


static const char DEFAULT_SHIM_NAME[]       = "Sample File";
static const char DEFAULT_SHIM_ID[]         = "http://bbc.co.uk/rd/as02/default-shim.txt";
static const char DEFAULT_SHIM_ANNOTATION[] = "Default AS-02 shim";


extern bool BMX_REGRESSION_TEST;



static bool parse_mic_type(const char *mic_type_str, MICType *mic_type)
{
    if (strcmp(mic_type_str, "md5") == 0)
        *mic_type = MD5_MIC_TYPE;
    else if (strcmp(mic_type_str, "none") == 0)
        *mic_type = NONE_MIC_TYPE;
    else
        return false;

    return true;
}

static bool parse_clip_type(const char *clip_type_str, ClipWriterType *clip_type)
{
    if (strcmp(clip_type_str, "as02") == 0)
        *clip_type = CW_AS02_CLIP_TYPE;
    else if (strcmp(clip_type_str, "as11op1a") == 0)
        *clip_type = CW_AS11_OP1A_CLIP_TYPE;
    else if (strcmp(clip_type_str, "as11d10") == 0)
        *clip_type = CW_AS11_D10_CLIP_TYPE;
    else if (strcmp(clip_type_str, "op1a") == 0)
        *clip_type = CW_OP1A_CLIP_TYPE;
    else if (strcmp(clip_type_str, "avid") == 0)
        *clip_type = CW_AVID_CLIP_TYPE;
    else if (strcmp(clip_type_str, "d10") == 0)
        *clip_type = CW_D10_CLIP_TYPE;
    else
        return false;

    return true;
}

static string get_version_info()
{
    char buffer[256];
    sprintf(buffer, "bmxtranswrap, %s v%s (%s %s)", PACKAGE_NAME, PACKAGE_VERSION, __DATE__, __TIME__);
    return buffer;
}

static void usage(const char *cmd)
{
    fprintf(stderr, "%s\n", get_version_info().c_str());
    fprintf(stderr, "Usage: %s <<options>> [<mxf input>]+\n", cmd);
    fprintf(stderr, "Options (* means option is required):\n");
    fprintf(stderr, "  -h | --help             Show usage and exit\n");
    fprintf(stderr, "  -v | --version          Print version info\n");
    fprintf(stderr, "  -p                      Print progress percentage to stdout\n");
    fprintf(stderr, "  -l <file>               Log filename. Default log to stderr/stdout\n");
    fprintf(stderr, "  -t <type>               Clip type: as02, as11op1a, as11d10, op1a, avid, d10. Default is as02\n");
    fprintf(stderr, "* -o <name>               as02: <name> is a bundle name\n");
    fprintf(stderr, "                          as11op1a/as11d10/op1a/d10: <name> is a filename\n");
    fprintf(stderr, "                          avid: <name> is a filename prefix\n");
    fprintf(stderr, "  -y <hh:mm:sscff>        Override input start timecode. Is drop frame when c is not ':'. Default 00:00:00:00\n");
    fprintf(stderr, "  --tc-rate <rate>        Start timecode rate to use when input is audio only\n");
    fprintf(stderr, "                          Values are 25 (default), 30 (30000/1001), 50 or 60 (60000/1001)\n");
    fprintf(stderr, "  --clip <name>           Set the clip name\n");
    fprintf(stderr, "  --start <frame>         Set the start frame. Default is 0\n");
    fprintf(stderr, "  --dur <frame>           Set the duration in frames. Default is minimum input duration\n");
    fprintf(stderr, "  --group                 Use the group reader instead of the sequence reader\n");
    fprintf(stderr, "                          Use this option if the files have different material packages\n");
    fprintf(stderr, "                          but actually belong to the same virtual package / group\n");
    fprintf(stderr, "  --no-reorder            Don't attempt to order the inputs in a sequence\n");
    fprintf(stderr, "                          Use this option for files with broken timecode\n");
    fprintf(stderr, "  --avcihead <format> <file> <offset>\n");
    fprintf(stderr, "                          Default AVC-Intra sequence header data (512 bytes) to use when the input file does not have it\n");
    fprintf(stderr, "                          <format> is a comma separated list of one or more of the following integer values:\n");
    size_t i;
    for (i = 0; i < get_num_avci_header_formats(); i++)
        fprintf(stderr, "                              %2"PRIszt": %s\n", i, get_avci_header_format_string(i));
    fprintf(stderr, "                          or set <format> to 'all' for all formats listed above\n");
    fprintf(stderr, "                          The 512 bytes are extracted from <file> starting at <offset> bytes\n");
    fprintf(stderr, "                              and incrementing 512 bytes for each format in the list\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  as02:\n");
    fprintf(stderr, "    --mic-type <type>       Media integrity check type: 'md5' or 'none'. Default 'md5'\n");
    fprintf(stderr, "    --mic-file              Calculate checksum for entire essence component file. Default is essence only\n");
    fprintf(stderr, "    --shim-name <name>      Set ShimName element value in shim.xml file to <name>. Default is '%s'\n", DEFAULT_SHIM_NAME);
    fprintf(stderr, "    --shim-id <id>          Set ShimID element value in shim.xml file to <id>. Default is '%s'\n", DEFAULT_SHIM_ID);
    fprintf(stderr, "    --shim-annot <str>      Set AnnotationText element value in shim.xml file to <str>. Default is '%s'\n", DEFAULT_SHIM_ANNOTATION);
    fprintf(stderr, "\n");
    fprintf(stderr, "  as02/as11op1a/op1a:\n");
    fprintf(stderr, "    --part <interval>       Video essence partition interval in frames, or seconds with 's' suffix. Default single partition\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  as11op1a/as11d10:\n");
    fprintf(stderr, "    --dm <fwork> <name> <value>    Set descriptive framework property. <fwork> is 'as11' or 'dpp'\n");
    fprintf(stderr, "    --dm-file <fwork> <name>       Parse and set descriptive framework properties from text file <name>. <fwork> is 'as11' or 'dpp'\n");
    fprintf(stderr, "    --seg <name>                   Parse and set segmentation data from text file <name>\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  as02/as11op1a/as11d10/op1a/d10:\n");
    fprintf(stderr, "    --afd <value>           Active Format Descriptor code. Default is input file's value or not set\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  as11op1a/as11d10/op1a/d10:\n");
    fprintf(stderr, "    --single-pass           Write file in a single pass\n");
    fprintf(stderr, "                            For as11op1a/op1a the header and body partitions will be incomplete\n");
    fprintf(stderr, "                            For as11d10/d10 all the partitions will be complete\n");
    fprintf(stderr, "    --file-md5              Calculate an MD5 checksum of the file. This requires writing in a single pass (--single-pass is assumed)\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  avid:\n");
    fprintf(stderr, "    --project <name>        Set the Avid project name\n");
    fprintf(stderr, "    --tape <name>           Source tape name\n");
    fprintf(stderr, "    --comment <string>      Add 'Comments' user comment to the MaterialPackage\n");
    fprintf(stderr, "    --desc <string>         Add 'Descript' user comment to the MaterialPackage\n");
    fprintf(stderr, "    --tag <name> <value>    Add <name> user comment to the MaterialPackage. Option can be used multiple times\n");
    fprintf(stderr, "    --locator <position> <comment> <color>\n");
    fprintf(stderr, "                            Add locator at <position> with <comment> and <color>\n");
    fprintf(stderr, "                            <position> format is o?hh:mm:sscff, where the optional 'o' indicates it is an offset\n");
}

int main(int argc, const char** argv)
{
    Rational timecode_rate = FRAME_RATE_25;
    bool timecode_rate_set = false;
    vector<const char *> input_filenames;
    const char *log_filename = 0;
    ClipWriterType clip_type = CW_AS02_CLIP_TYPE;
    const char *output_name = "";
    Timecode start_timecode;
    const char *start_timecode_str = 0;
    int64_t start = 0;
    int64_t duration = -1;
    const char *clip_name = 0;
    MICType mic_type = MD5_MIC_TYPE;
    MICScope ess_component_mic_scope = ESSENCE_ONLY_MIC_SCOPE;
    const char *partition_interval_str = 0;
    int64_t partition_interval = 0;
    const char *shim_name = 0;
    const char *shim_id = 0;
    const char *shim_annot = 0;
    const char *project_name = 0;
    const char *tape_name = 0;
    map<string, string> user_comments;
    vector<LocatorOption> locators;
    AS11Helper as11_helper;
    const char *segmentation_filename = 0;
    bool do_print_version = false;
    bool use_group_reader = false;
    bool keep_input_order = false;
    uint8_t user_afd = 0;
    vector<AVCIHeaderInput> avci_header_inputs;
    bool show_progress = false;
    bool single_pass = false;
    bool file_md5 = false;
    int value;
    int cmdln_index;

    if (argc == 1) {
        usage(argv[0]);
        return 0;
    }

    for (cmdln_index = 1; cmdln_index < argc; cmdln_index++)
    {
        if (strcmp(argv[cmdln_index], "--help") == 0 ||
            strcmp(argv[cmdln_index], "-h") == 0)
        {
            usage(argv[0]);
            return 0;
        }
        else if (strcmp(argv[cmdln_index], "--version") == 0 ||
                 strcmp(argv[cmdln_index], "-v") == 0)
        {
            if (argc == 2) {
                printf("%s\n", get_version_info().c_str());
                return 0;
            }
            do_print_version = true;
        }
        else if (strcmp(argv[cmdln_index], "-p") == 0)
        {
            show_progress = true;
        }
        else if (strcmp(argv[cmdln_index], "-l") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            log_filename = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "-t") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!parse_clip_type(argv[cmdln_index + 1], &clip_type))
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "-o") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            output_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "-y") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            start_timecode_str = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--tc-rate") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (sscanf(argv[cmdln_index + 1], "%u", &value) != 1 ||
                (value != 25 && value != 30 && value != 50 && value != 60))
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            if (value == 25)
                timecode_rate = FRAME_RATE_25;
            else if (value == 30)
                timecode_rate = FRAME_RATE_2997;
            else if (value == 50)
                timecode_rate = FRAME_RATE_50;
            else
                timecode_rate = FRAME_RATE_5994;
            timecode_rate_set = true;
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--clip") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            clip_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--start") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (sscanf(argv[cmdln_index + 1], "%"PRId64, &start) != 1 || start < 0)
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--dur") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (sscanf(argv[cmdln_index + 1], "%"PRId64, &duration) != 1 || duration < 0)
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--group") == 0)
        {
            use_group_reader = true;
        }
        else if (strcmp(argv[cmdln_index], "--no-reorder") == 0)
        {
            keep_input_order = true;
        }
        else if (strcmp(argv[cmdln_index], "--avcihead") == 0)
        {
            if (cmdln_index + 3 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!parse_avci_header(argv[cmdln_index + 1], argv[cmdln_index + 2], argv[cmdln_index + 3],
                                   &avci_header_inputs))
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid values '%s', '%s', '%s' for option '%s'\n",
                        argv[cmdln_index + 1], argv[cmdln_index + 2], argv[cmdln_index + 3], argv[cmdln_index]);
                return 1;
            }
            cmdln_index += 3;
        }
        else if (strcmp(argv[cmdln_index], "--mic-type") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!parse_mic_type(argv[cmdln_index + 1], &mic_type))
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--mic-file") == 0)
        {
            ess_component_mic_scope = ENTIRE_FILE_MIC_SCOPE;
        }
        else if (strcmp(argv[cmdln_index], "--shim-name") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            shim_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--shim-id") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            shim_id = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--shim-annot") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            shim_annot = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--part") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            partition_interval_str = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--dm") == 0)
        {
            if (cmdln_index + 3 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!as11_helper.SetFrameworkProperty(argv[cmdln_index + 1], argv[cmdln_index + 2], argv[cmdln_index + 3]))
            {
                usage(argv[0]);
                fprintf(stderr, "Failed to set '%s' framework property '%s' to '%s'\n",
                        argv[cmdln_index + 1], argv[cmdln_index + 2], argv[cmdln_index + 3]);
                return 1;
            }
            cmdln_index += 3;
        }
        else if (strcmp(argv[cmdln_index], "--dm-file") == 0)
        {
            if (cmdln_index + 2 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (!as11_helper.ParseFrameworkFile(argv[cmdln_index + 1], argv[cmdln_index + 2]))
            {
                usage(argv[0]);
                fprintf(stderr, "Failed to parse '%s' framework file '%s'\n",
                        argv[cmdln_index + 1], argv[cmdln_index + 2]);
                return 1;
            }
            cmdln_index += 2;
        }
        else if (strcmp(argv[cmdln_index], "--seg") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            segmentation_filename = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--afd") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            if (sscanf(argv[cmdln_index + 1], "%d", &value) != 1 ||
                value <= 0 || value > 255)
            {
                usage(argv[0]);
                fprintf(stderr, "Invalid value '%s' for option '%s'\n", argv[cmdln_index + 1], argv[cmdln_index]);
                return 1;
            }
            user_afd = value;
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--single-pass") == 0)
        {
            single_pass = true;
        }
        else if (strcmp(argv[cmdln_index], "--file-md5") == 0)
        {
            file_md5 = true;
        }
        else if (strcmp(argv[cmdln_index], "--project") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            project_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--tape") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            tape_name = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--comment") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for option '%s'\n", argv[cmdln_index]);
                return 1;
            }
            user_comments["Comments"] = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--desc") == 0)
        {
            if (cmdln_index + 1 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument for %s\n", argv[cmdln_index]);
                return 1;
            }
            user_comments["Descript"] = argv[cmdln_index + 1];
            cmdln_index++;
        }
        else if (strcmp(argv[cmdln_index], "--tag") == 0)
        {
            if (cmdln_index + 2 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for %s\n", argv[cmdln_index]);
                return 1;
            }
            user_comments[argv[cmdln_index + 1]] = argv[cmdln_index + 2];
            cmdln_index += 2;
        }
        else if (strcmp(argv[cmdln_index], "--locator") == 0)
        {
            if (cmdln_index + 3 >= argc)
            {
                usage(argv[0]);
                fprintf(stderr, "Missing argument(s) for %s\n", argv[cmdln_index]);
                return 1;
            }
            LocatorOption locator;
            locator.position_str = argv[cmdln_index + 1];
            locator.locator.comment = argv[cmdln_index + 2];
            if (!parse_color(argv[cmdln_index + 3], &locator.locator.color))
            {
                usage(argv[0]);
                fprintf(stderr, "Failed to read --locator <color> value '%s'\n", argv[cmdln_index + 3]);
                return 1;
            }
            locators.push_back(locator);
            cmdln_index += 3;
        }
        else if (strcmp(argv[cmdln_index], "--regtest") == 0)
        {
            BMX_REGRESSION_TEST = true;
        }
        else
        {
            break;
        }
    }

    for (; cmdln_index < argc; cmdln_index++)
        input_filenames.push_back(argv[cmdln_index]);

    if (input_filenames.empty()) {
        usage(argv[0]);
        fprintf(stderr, "No input filenames\n");
        return 1;
    }


    if (log_filename) {
        if (!open_log_file(log_filename))
            return 1;
    }

    connect_libmxf_logging();

    if (BMX_REGRESSION_TEST) {
        mxf_set_regtest_funcs();
        mxf_avid_set_regtest_funcs();
    }

    if (do_print_version)
        log_info("%s\n", get_version_info().c_str());


    int cmd_result = 0;
    try
    {
        // open an MXFReader using the input filenames

        MXFReader *reader;
        if (use_group_reader) {
            MXFGroupReader *group_reader = new MXFGroupReader();
            size_t i;
            for (i = 0; i < input_filenames.size(); i++) {
                MXFFileReader *file_reader = new MXFFileReader();
                MXFFileReader::OpenResult result = file_reader->Open(input_filenames[i]);
                if (result != MXFFileReader::MXF_RESULT_SUCCESS) {
                    log_error("Failed to open MXF file '%s': %s\n", input_filenames[i],
                              MXFFileReader::ResultToString(result).c_str());
                    delete file_reader;
                    throw false;
                }

                group_reader->AddReader(file_reader);
            }
            if (!group_reader->Finalize())
                throw false;

            reader = group_reader;
        } else if (input_filenames.size() > 1) {
            MXFSequenceReader *seq_reader = new MXFSequenceReader();
            size_t i;
            for (i = 0; i < input_filenames.size(); i++) {
                MXFFileReader *file_reader = new MXFFileReader();
                MXFFileReader::OpenResult result = file_reader->Open(input_filenames[i]);
                if (result != MXFFileReader::MXF_RESULT_SUCCESS) {
                    log_error("Failed to open MXF file '%s': %s\n", input_filenames[i],
                              MXFFileReader::ResultToString(result).c_str());
                    delete file_reader;
                    throw false;
                }

                seq_reader->AddReader(file_reader);
            }
            if (!seq_reader->Finalize(false, keep_input_order))
                throw false;

            reader = seq_reader;
        } else {
            MXFFileReader *file_reader = new MXFFileReader();
            MXFFileReader::OpenResult result = file_reader->Open(input_filenames[0]);
            if (result != MXFFileReader::MXF_RESULT_SUCCESS) {
                log_error("Failed to open MXF file '%s': %s\n", input_filenames[0],
                          MXFFileReader::ResultToString(result).c_str());
                delete file_reader;
                throw false;
            }

            reader = file_reader;
        }

        Rational frame_rate = reader->GetSampleRate();


        // check support for tracks and disable unsupported track types

        size_t i;
        for (i = 0; i < reader->GetNumTrackReaders(); i++) {
            MXFTrackReader *track_reader = reader->GetTrackReader(i);
            const MXFTrackInfo *input_track_info = track_reader->GetTrackInfo();

            const MXFSoundTrackInfo *input_sound_info = dynamic_cast<const MXFSoundTrackInfo*>(input_track_info);
            const MXFPictureTrackInfo *input_picture_info = dynamic_cast<const MXFPictureTrackInfo*>(input_track_info);

            bool is_supported = true;
            if (input_track_info->essence_type == WAVE_PCM)
            {
                Rational sampling_rate = input_sound_info->sampling_rate;
                if (!ClipWriterTrack::IsSupported(clip_type, WAVE_PCM, sampling_rate)) {
                    log_warn("Track %"PRIszt" essence type '%s' @%d/%d sps not supported by clip type '%s'\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type),
                             sampling_rate.numerator, sampling_rate.denominator,
                             ClipWriter::ClipWriterTypeToString(clip_type).c_str());
                    is_supported = false;
                } else if (input_sound_info->bits_per_sample == 0 || input_sound_info->bits_per_sample > 32) {
                    log_warn("Track %"PRIszt" (%s) bits per sample %u not supported\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type),
                             input_sound_info->bits_per_sample);
                    is_supported = false;
                }
            }
            else if (input_track_info->IsD10Audio()) // essence_type == SOUND_ESSENCE
            {
                if (input_sound_info->sampling_rate.numerator != 48000 ||
                    input_sound_info->sampling_rate.denominator != 1)
                {
                    log_warn("Track %"PRIszt" essence type D-10 AES-3 audio sampling rate %d/%d not supported\n",
                             i,
                             input_sound_info->sampling_rate.numerator, input_sound_info->sampling_rate.denominator);
                    is_supported = false;
                }
                else if (input_sound_info->bits_per_sample == 0 || input_sound_info->bits_per_sample > 32)
                {
                    log_warn("Track %"PRIszt" essence type D-10 AES-3 audio bits per sample %u not supported\n",
                             i,
                             input_sound_info->bits_per_sample);
                    is_supported = false;
                }
            }
            else if (input_track_info->essence_type == UNKNOWN_ESSENCE_TYPE ||
                     input_track_info->essence_type == PICTURE_ESSENCE ||
                     input_track_info->essence_type == SOUND_ESSENCE)
            {
                log_warn("Track %"PRIszt" has unknown essence type\n", i);
                is_supported = false;
            }
            else
            {
                if (input_track_info->edit_rate != frame_rate) {
                    log_warn("Track %"PRIszt" (essence type '%s') edit rate %d/%d does not equals clip edit rate %d/%d\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type),
                             input_track_info->edit_rate.numerator, input_track_info->edit_rate.denominator,
                             frame_rate.numerator, frame_rate.denominator,
                             ClipWriter::ClipWriterTypeToString(clip_type).c_str());
                    is_supported = false;
                } else if (!ClipWriterTrack::IsSupported(clip_type, input_track_info->essence_type, frame_rate)) {
                    log_warn("Track %"PRIszt" essence type '%s' @%d/%d fps not supported by clip type '%s'\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type),
                             frame_rate.numerator, frame_rate.denominator,
                             ClipWriter::ClipWriterTypeToString(clip_type).c_str());
                    is_supported = false;
                }

                if ((input_track_info->essence_type == D10_30 ||
                        input_track_info->essence_type == D10_40 ||
                        input_track_info->essence_type == D10_50) &&
                    input_picture_info->d10_frame_size == 0)
                {
                    log_warn("Track %"PRIszt" (essence type '%s') has zero frame size\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type));
                    is_supported = false;
                }
                else if ((input_track_info->essence_type == AVCI100_1080I ||
                               input_track_info->essence_type == AVCI100_1080P ||
                               input_track_info->essence_type == AVCI100_720P ||
                               input_track_info->essence_type == AVCI50_1080I ||
                               input_track_info->essence_type == AVCI50_1080P ||
                               input_track_info->essence_type == AVCI50_720P) &&
                         !track_reader->HaveAVCIHeader() &&
                         !have_avci_header_data(input_track_info->essence_type, input_track_info->edit_rate,
                                                avci_header_inputs))
                {
                    log_warn("Track %"PRIszt" (essence type '%s') does not have sequence and picture parameter sets\n",
                             i,
                             essence_type_to_string(input_track_info->essence_type));
                    is_supported = false;
                }
            }

            if (!is_supported) {
                log_warn("Ignoring unsupported track %"PRIszt" (essence type '%s')\n",
                          i, essence_type_to_string(input_track_info->essence_type));
            }

            track_reader->SetEnable(is_supported);
        }

        if (!reader->IsEnabled()) {
            log_error("All tracks are disabled\n");
            throw false;
        }


        // set read limits

        int64_t read_start = start;
        if (read_start >= reader->GetDuration()) {
            log_error("Start position %"PRId64" is >= input duration %"PRId64"\n",
                      start, reader->GetDuration());
            throw false;
        }
        int64_t output_duration;
        if (duration >= 0) {
            output_duration = duration;
            if (read_start + output_duration > reader->GetDuration()) {
                log_warn("Limiting duration %"PRId64" because it exceeds the available duration %"PRId64"\n",
                          duration, reader->GetDuration() - read_start);
                output_duration = reader->GetDuration() - read_start;
            }
        } else {
            output_duration = reader->GetDuration() - read_start;
        }

        int16_t precharge = 0;
        int16_t rollout = 0;
        if (output_duration > 0) {
            precharge = reader->GetMaxPrecharge(read_start, true);
            rollout = reader->GetMaxRollout(read_start + output_duration - 1, true);

            if (clip_type == CW_AVID_CLIP_TYPE && precharge != 0) {
                output_duration += (- precharge);
                log_warn("Avid clip type does not support %d precharge. Duration has changed to %"PRId64"\n",
                         precharge, output_duration);
                read_start += precharge;
                precharge = 0;
            }

            reader->SetReadLimits(read_start + precharge, - precharge + output_duration + rollout, true);
        }


{'fraction_non_alphanumeric': 0.11561457174638487, 'fraction_numerical': 0.015711902113459398, 'mean_word_length': 1.7329761361909104, 'pattern_counts': {'":': 2, '<': 183, '<?xml version=': 0, '>': 355, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '800490', 'n_tokens_mistral': 22123, 'n_tokens_neox': 20930, 'n_words': 5076}