Simple DVD

This topic describes the steps needed to create a DVD from an MP4 video.

Project Directory

You need a directory to store the source video and the DVDBuilder project. This can be anywhere on your machine. For example create C:\ElephantDVD directory. We will refer to that as the DVD Project Directory.

Source Video

Download the clip Elephant_512kb.mp4 from the Internet Archive to the DVD Project Directory.

Convert Source Video To MPEG-2

DVDBuilder accepts only MPEG-2 Program Stream as input, but you can use AVBlocks to convert the source video from MP4 to MPG (MPEG-2 Program Stream). AVBlocks is already included with DVDBuilder.


wstring source_mp4 = fs::path(DVDProjectDirectory).append(L"Elephant_512kb.mp4");
wstring source_mpeg2 = fs::path(DVDProjectDirectory).append(L"Elephant.mpg");

convert_source_to_mpeg2(source_mp4, source_mpeg2);

namespace p = primo;
namespace avb = primo::avblocks;
namespace fs = experimental::filesystem;

void convert_source_to_mpeg2(wstring input_file, wstring output_file)
{
    using namespace avb;
    using namespace avb::Library;

    if (fs::exists(output_file))
        fs::remove(output_file);

    auto inputInfo = p::make_ref(
        createMediaInfo()
    );

    inputInfo->setInputFile(input_file.c_str());

    if (inputInfo->load()) {
        auto inputSocket = p::make_ref(
            createMediaSocket(inputInfo.get())
        );

        auto outputSocket = p::make_ref(
            createMediaSocket(Preset::Video::DVD::NTSC_16x9_PCM)
        );

        outputSocket->setFile(output_file.c_str());

        auto transcoder = p::make_ref(
            createTranscoder()
        );

        transcoder->setAllowDemoMode(true);

        transcoder->inputs()->add(inputSocket.get());
        transcoder->outputs()->add(outputSocket.get());

        if (transcoder->open()) {
            transcoder->run();
            transcoder->close();
        }
    }
}

Create DVDBuilder Project

DVDBuilder project is an XML file that describes a video DVD.

In the DVD Project Directory create a file project.xml with the following contents:


<?xml version='1.0' encoding='utf-8'?>
<dvd version='2.3' xmlns='http://www.primosoftware.com/dvdbuilder/2.3'>
    <videoManager firstPlayNavigate='Title=1'/>
    <titleSet>
        <titles>
            <title id='1' chapters='00:00:00;'>
                <videoObject file='Elephant.mpg'/>
            </title>
        </titles>
    </titleSet>
</dvd>

Build DVD Structures

Simply create a DVDBuilder object, set the ProjectFile and OutputFolder properties, and call the Build method.


wstring project = fs::path(DVDProjectDirectory).append(L"project.xml");
wstring dvd_files = fs::path(DVDProjectDirectory).append(L"dvd");

build_dvd_structures(project, dvd_files);

namespace p = primo;
namespace dvdb = primo::dvdbuilder;
namespace fs = experimental::filesystem;

void build_dvd_structures(wstring project, wstring dvd_files)
{
    using namespace dvdb::Library;

    if (fs::exists(dvd_files))
        fs::remove_all(dvd_files);

    auto dvdBuilder = p::make_ref(
        createDVDBuilder()
    );

    dvdBuilder->setProjectFile(project.c_str());
    dvdBuilder->setOutputFolder(dvd_files.c_str());

    dvdBuilder->build();
}

Burn DVD

You can use PrimoBurner for DVD burning. A limited version of PrimoBurner, that supports DVD burning only, is included with DVDBuilder.

Create Engine


namespace p = primo;
namespace pb = primo::burner;

void burn_to_dvd(wstring output, char drive_letter)
{
    using namespace pb::Library;

    auto engine = p::make_ref(
        createEngine()
    );

    engine->initialize();

    burn_to_dvd(engine.get(), drive_letter, output);

    engine->shutdown();
}

Create Device


namespace p = primo;
namespace pb = primo::burner;

void burn_to_dvd(pb::Engine* engine, char drive_letter, wstring dvd_files)
{
    using namespace pb::Library;

    int deviceIndex = driveLetterToDeviceIndex(drive_letter);

    auto enumerator = p::make_ref(
        engine->createDeviceEnumerator()
    );

    auto device = p::make_ref(
        enumerator->createDevice(deviceIndex, true)
    );

    if (device) {
        burn_to_dvd(device.get(), dvd_files);
    }
}

Use VideoDVD To Prepare DVD Layout


namespace p = primo;
namespace pb = primo::burner;

void burn_to_dvd(pb::Device* device, wstring dvd_files)
{
    using namespace pb;
    using namespace pb::Library;

    // Use VideoDVD to prepare the DVD image layout
    auto videoDVD = p::make_ref(
        createVideoDVD()
    );

    // set the dvd files as layout
    videoDVD->setImageLayoutFromFolder(dvd_files.c_str());
    videoDVD->setValidation(VideoDVDValidation::Minimal);

    // get the actual DVD layout
    auto preparedImageLayout = p::make_ref(
        videoDVD->copyImageLayout()
    );

    // Burn
    burn_to_dvd(device, preparedImageLayout.get());
}

Burn DVD Layout


namespace p = primo;
namespace pb = primo::burner;

filetime_t to_filetime(const std::chrono::system_clock::time_point& tp);

void burn_to_dvd(pb::Device* device, pb::DataFile* imageLayout)
{
    using namespace pb;
    using namespace pb::Library;

    if (!check_device_and_media(device))
    {
        wcout << L"Please insert a DVD-R or DVD+R in burner and try again." << endl;

        // media is not DVD+R or DVD-R, or not blank
        device->eject(true, true);

        return;
    }

    // Use DataDisc to burn the DVD image layout
    auto dataDisc = p::make_ref(
        createDataDisc()
    );

    // set device, and write method
    dataDisc->setDevice(device);
    dataDisc->setWriteMethod(WriteMethod::DVDDao);

    // close the disc for true playback compatibility
    // this will result in slower disc burning
    dataDisc->setCloseDisc(true);

    // DataDisc needs this to create the correct disc image
    dataDisc->setDVDVideo(true);

    // set volume label and creation date
    auto creationTime = to_filetime(chrono::system_clock::now());
    auto volumeLabel = L"DVDVIDEO";
    dataDisc->isoVolumeProps()->setVolumeLabel(volumeLabel);
    dataDisc->isoVolumeProps()->setVolumeCreationTime(creationTime);

    dataDisc->udfVolumeProps()->setVolumeLabel(volumeLabel);
    dataDisc->udfVolumeProps()->setVolumeCreationTime(creationTime);

    // set the image layout as prepared by VideoDVD
    dataDisc->setImageLayout(imageLayout);

    // this may take some time
    dataDisc->writeToDisc(true);
}

Complete C++ Code

Here is the complete code.


// SimpleDVD.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

using namespace std;

// or you can use boost::filesystem, http://www.boost.org
namespace fs = experimental::filesystem;

namespace p = primo;
namespace avb = primo::avblocks;
namespace dvdb = primo::dvdbuilder;
namespace pb = primo::burner;

const wstring DVDProjectDirectory = L"C:/ElephantDVD";
const char DVDBurnerDriveLetter = 'E';

void convert_source_to_mpeg2(wstring inputFile, wstring outputFile);
void build_dvd_structures(wstring project, wstring dvdFiles);
void burn_to_dvd(wstring output, char driveLetter);

int main()
{
    // Video encoding
    primo::avblocks::Library::initialize();
    primo::avblocks::Library::setLicense("license_xml_string");

    // DVD authoring
    primo::dvdbuilder::Library::setLicense("license_xml_string");

    // DVD burning
    primo::burner::Library::setLicense("license_xml_string");

    // Step 1: Convert sources
    wstring source_mp4 = fs::path(DVDProjectDirectory).append(L"Elephant_512kb.mp4");
    wstring source_mpeg2 = fs::path(DVDProjectDirectory).append(L"Elephant.mpg");

    convert_source_to_mpeg2(source_mp4, source_mpeg2);

    // Step 2: Build DVD structures
    wstring project = fs::path(DVDProjectDirectory).append(L"project.xml");
    wstring dvd_files = fs::path(DVDProjectDirectory).append(L"dvd");

    build_dvd_structures(project, dvd_files);

    // Step 3: Burn to DVD
    burn_to_dvd(dvd_files, DVDBurnerDriveLetter);

    primo::avblocks::Library::shutdown();

    return 0;
}

// Convert sources
void convert_source_to_mpeg2(wstring input_file, wstring output_file)
{
    using namespace avb;
    using namespace avb::Library;

    if (fs::exists(output_file))
        fs::remove(output_file);

    auto inputInfo = p::make_ref(
        createMediaInfo()
    );

    inputInfo->setInputFile(input_file.c_str());

    if (inputInfo->load()) {
        auto inputSocket = p::make_ref(
            createMediaSocket(inputInfo.get())
        );

        auto outputSocket = p::make_ref(
            createMediaSocket(Preset::Video::DVD::NTSC_16x9_PCM)
        );

        outputSocket->setFile(output_file.c_str());

        auto transcoder = p::make_ref(
            createTranscoder()
        );

        transcoder->setAllowDemoMode(true);

        transcoder->inputs()->add(inputSocket.get());
        transcoder->outputs()->add(outputSocket.get());

        if (transcoder->open()) {
            transcoder->run();
            transcoder->close();
        }
    }
}

// Build DVD structures
void build_dvd_structures(wstring project, wstring dvd_files)
{
    using namespace dvdb::Library;

    if (fs::exists(dvd_files))
        fs::remove_all(dvd_files);

    auto dvdBuilder = p::make_ref(
        createDVDBuilder()
    );

    dvdBuilder->setProjectFile(project.c_str());
    dvdBuilder->setOutputFolder(dvd_files.c_str());

    dvdBuilder->build();
}

// Burn to DVD
void wait_for_unit_ready(pb::Device* device)
{
    using namespace primo::scsi;

    int error = device->unitReadyState();
    while (true)
    {
        if (ScsiSense::Success == error)
            break;

        if (ScsiSense::MediumNotPresent == error)
            break;

        if (ScsiSense::MediumNotPresentTrayClosed == error)
            break;

        if (ScsiSense::MediumNotPresentTrayOpen == error)
            break;

        this_thread::sleep_for(1s);
        error = device->unitReadyState();
    }
}

bool check_device_and_media(pb::Device* device)
{
    using namespace pb;

    // close the device tray and refresh disc information
    if (device->eject(false))
    {
        // wait for the device to become ready
        wait_for_unit_ready(device);

        // refresh disc information. Need to call this method when media changes
        device->refresh();
    }

    // check if disc is present
    if (MediaReady::Present != device->mediaState())
        return false;

    // check if disc is blank
    if (!device->isMediaBlank())
        return false;

    // for simplicity only accept DVD-R and DVD+R
    auto mp = device->mediaProfile();
    if (MediaProfile::DVDPlusR != mp && MediaProfile::DVDMinusRSeq != mp)
        return false;

    return true;
}

// see the full definition below
filetime_t to_filetime(const std::chrono::system_clock::time_point& tp);

void burn_to_dvd(pb::Device* device, pb::DataFile* imageLayout)
{
    using namespace pb;
    using namespace pb::Library;

    if (!check_device_and_media(device))
    {
        wcout << L"Please insert a DVD-R or DVD+R in burner and try again." << endl;

        // media is not DVD+R or DVD-R, or not blank
        device->eject(true, true);

        return;
    }

    // Use DataDisc to burn the DVD image layout
    auto dataDisc = p::make_ref(
        createDataDisc()
    );

    // set device, and write method
    dataDisc->setDevice(device);
    dataDisc->setWriteMethod(WriteMethod::DVDDao);

    // close the disc for true playback compatibility
    // this will result in slower disc burning
    dataDisc->setCloseDisc(true);

    // DataDisc needs this to create the correct disc image
    dataDisc->setDVDVideo(true);

    // set volume label and creation date
    auto creationTime = to_filetime(chrono::system_clock::now());
    auto volumeLabel = L"DVDVIDEO";
    dataDisc->isoVolumeProps()->setVolumeLabel(volumeLabel);
    dataDisc->isoVolumeProps()->setVolumeCreationTime(creationTime);

    dataDisc->udfVolumeProps()->setVolumeLabel(volumeLabel);
    dataDisc->udfVolumeProps()->setVolumeCreationTime(creationTime);

    // set the image layout as prepared by VideoDVD
    dataDisc->setImageLayout(imageLayout);

    // this may take some time
    dataDisc->writeToDisc(true);
}

void burn_to_dvd(pb::Device* device, wstring dvd_files)
{
    using namespace pb;
    using namespace pb::Library;

    // Use VideoDVD to prepare the DVD image layout
    auto videoDVD = p::make_ref(
        createVideoDVD()
    );

    // set the dvd files as layout
    videoDVD->setImageLayoutFromFolder(dvd_files.c_str());
    videoDVD->setValidation(VideoDVDValidation::Minimal);

    // get the actual DVD layout
    auto preparedImageLayout = p::make_ref(
        videoDVD->copyImageLayout()
    );

    // Burn
    burn_to_dvd(device, preparedImageLayout.get());
}

void burn_to_dvd(pb::Engine* engine, char drive_letter, wstring dvd_files)
{
    using namespace pb::Library;

    int deviceIndex = driveLetterToDeviceIndex(drive_letter);

    auto enumerator = p::make_ref(
        engine->createDeviceEnumerator()
    );

    auto device = p::make_ref(
        enumerator->createDevice(deviceIndex, true)
    );

    if (device) {
        burn_to_dvd(device.get(), dvd_files);
    }
}

void burn_to_dvd(wstring output, char drive_letter)
{
    using namespace pb::Library;

    auto engine = p::make_ref(
        createEngine()
    );

    engine->initialize();

    burn_to_dvd(engine.get(), drive_letter, output);

    engine->shutdown();
}

// Utilities
/**
    Converts a time_point to filetime_t (FILETIME)

    @ref https://msdn.microsoft.com/en-us/library/windows/desktop/ms724228%28v=vs.85%29.aspx
*/
filetime_t to_filetime(const std::chrono::system_clock::time_point& tp)
{
    time_t t = std::chrono::system_clock::to_time_t(tp);
    int64_t ll = Int32x32To64(t, 10000000) + 116444736000000000;

    filetime_t ft = { 0, 0 };
    ft.dwLowDateTime = static_cast<uint32_t>(ll);
    ft.dwHighDateTime = static_cast<uint32_t>(ll >> 32);

    return ft;
}


Last updated on March 1st, 2016 12:00:00 AM