DVD with Subtitles
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:\SimpleDVDWithSubtitles
directory. We will refer to that as the DVD Project Directory
.
Source Video
Download the file A Música Irresistivel de Benny Goodman 1956.mp4
from the Internet Archive to the DVD Project Directory
.
Rename the file from A Música Irresistivel de Benny Goodman 1956.mp4
to movie.mp4
.
Subtitle Files
Clone the DVDBuilder-Samples repository. Copy french
, portuguese
and spanish
directories from projects\SimpleDVDWithSubtitles
to 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.
string sourceMp4 = Path.Combine(DVDProjectDirectory, "movie.mp4");
string sourceMpeg2 = Path.Combine(DVDProjectDirectory, "movie.mpg");
ConvertSourceToMpeg2(sourceMp4, sourceMpeg2);
using avb = PrimoSoftware.AVBlocks;
private static void ConvertSourceToMpeg2(string inputFile, string outputFile)
{
if (File.Exists(outputFile))
File.Delete(outputFile);
var inputInfo = new avb.MediaInfo() {
InputFile = inputFile
};
if (inputInfo.Load())
{
var inputSocket = avb.MediaSocket.FromMediaInfo(inputInfo);
var outputSocket =
avb.MediaSocket.FromPreset(avb.Preset.Video.DVD.NTSC_16x9_PCM);
outputSocket.File = outputFile;
using (var transcoder = new avb.Transcoder())
{
// setup
transcoder.AllowDemoMode = true;
transcoder.Inputs.Add(inputSocket);
transcoder.Outputs.Add(outputSocket);
// handle progress event to report progress
transcoder.OnProgress += (sender, args) => {
DrawTextProgressBar((int)args.CurrentTime, (int)args.TotalTime);
};
// handle continue event to stop transcoding
transcoder.OnContinue += (sender, args) => {
// stop transcoder after 3 minutes and 20 seconds
if (args.CurrentTime >= 3 * 60 + 20)
args.Continue = false;
};
if (transcoder.Open())
{
transcoder.Run();
transcoder.Close();
}
}
}
}
Encode subtitles
// Spanish
Console.WriteLine(" Spanish \n");
string subtitle1 = Path.Combine(DVDProjectDirectory, "spanish/spanish.stl");
string encodedSubtitle1 = Path.Combine(DVDProjectDirectory, "spanish_sub.mpg");
EncodeSubtitle(subtitle1, encodedSubtitle1);
// French
Console.WriteLine(" French \n");
string subtitle2 = Path.Combine(DVDProjectDirectory, "french/french.stl");
string encodedSubtitle2 = Path.Combine(DVDProjectDirectory, "french_sub.mpg");
EncodeSubtitle(subtitle2, encodedSubtitle2);
// Portuguese
Console.WriteLine(" Portuguese \n");
string subtitle3 = Path.Combine(DVDProjectDirectory, "portuguese/portuguese.stl");
string encodedSubtitle3 = Path.Combine(DVDProjectDirectory, "portuguese_sub.mpg");
EncodeSubtitle(subtitle3, encodedSubtitle3);
private static void EncodeSubtitle(string subtitle, string encodedSubtitle)
{
if (File.Exists(encodedSubtitle))
File.Delete(encodedSubtitle);
using (var subpicEncoder = new dvdb.SubpictureEncoder())
{
if (!subpicEncoder.Encode(subtitle, encodedSubtitle))
{
Console.WriteLine(subpicEncoder.Error.Message);
}
}
}
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>
<subpictureStreams>
<!--
Declare subtitle streams for the DVD menu
Actual streams will be passed in the videoObject of each title below
-->
<!-- Spanish, French and Portuguese subtitles -->
<stream languageCode="ES" mpegStreamID="0xBD" mpegSubstreamID="0x20"/>
<stream languageCode="FR" mpegStreamID="0xBD" mpegSubstreamID="0x21"/>
<stream languageCode="PT" mpegStreamID="0xBD" mpegSubstreamID="0x22"/>
</subpictureStreams>
<titles>
<title id='1' chapters='00:00:00;'>
<videoObject file='movie.mpg'>
<!--
Add subtitle streams. The order and streams must match the streams from the titleSet above:
spanish_sub.mpg - encoded from spanish/spanish.stl
french_sub.mpg - encoded from french/french.stl
portuguese_sub.mpg - encoded from portuguese/portuguese.stl
-->
<subpictureStream file='spanish_sub.mpg' />
<subpictureStream file='french_sub.mpg' />
<subpictureStream file='portuguese_sub.mpg' />
</videoObject>
<subpicturePalette>
<!-- Not used - black -->
<color index="0" value="#000000" />
<!-- Text color - white -->
<color index="1" value="#FFFFFF" />
<!-- Text outer outline color / outline 1 - off-black -->
<color index="2" value="#202020" />
<!-- Text inner outline color / outline 2 - gray -->
<color index="3" value="#808080" />
<!--
Text background color - blue
This should not be visible unless you set $BackgroundContrast to > 0 in the source STL file
-->
<color index="4" value="#0000FF" />
</subpicturePalette>
</title>
</titles>
</titleSet>
</dvd>
Build DVD Structures
Simply create a DVDBuilder object, set the ProjectFile
and OutputFolder
properties, and call the Build
method.
string project = Path.Combine(DVDProjectDirectory, "project.xml");
string dvdFiles = Path.Combine(DVDProjectDirectory, "dvd");
BuildDVDStructures(project, dvdFiles);
using dvdb = PrimoSoftware.DVDBuilder;
private static void BuildDVDStructures(string project, string dvdFiles)
{
if (Directory.Exists(dvdFiles))
Directory.Delete(dvdFiles, true);
using (var dvdBuilder = new dvdb.DVDBuilder())
{
// handle progress event to report progress
dvdBuilder.OnProgress += (sender, args) => {
DrawTextProgressBar(args.Percent, 100, "%");
};
dvdBuilder.ProjectFile = project;
dvdBuilder.OutputFolder = dvdFiles;
if (!dvdBuilder.Build())
{
Console.WriteLine(dvdBuilder.Error.Message);
}
}
}
Complete C# Code
Here is the complete code.
using System;
using System.IO;
using avb = PrimoSoftware.AVBlocks;
using dvdb = PrimoSoftware.DVDBuilder;
namespace SimpleDVDWithSubtitles
{
class Program
{
const string DVDProjectDirectory = "C:/SimpleDVDWithSubtitles";
// 3 minutes and 20 seconds
const int clipTimeSeconds = 3 * 60 + 20;
static void Main(string[] args)
{
// Video encoding
PrimoSoftware.AVBlocks.Library.Initialize();
PrimoSoftware.AVBlocks.Library.SetLicense("license_xml_string");
// DVD authoring
PrimoSoftware.DVDBuilder.Library.Initialize();
PrimoSoftware.DVDBuilder.Library.SetLicense("license_xml_string");
// Step 1: Convert source video
Console.WriteLine("Converting source video ... \n");
string sourceMp4 = Path.Combine(DVDProjectDirectory, "movie.mp4");
string sourceMpeg2 = Path.Combine(DVDProjectDirectory, "movie.mpg");
ConvertSourceToMpeg2(sourceMp4, sourceMpeg2);
// Step 2: Encode subtitles
Console.WriteLine("\n\n Encoding subtitles ... \n");
// Spanish
Console.WriteLine(" Spanish \n");
string subtitle1 = Path.Combine(DVDProjectDirectory, "spanish/spanish.stl");
string encodedSubtitle1 = Path.Combine(DVDProjectDirectory, "spanish_sub.mpg");
EncodeSubtitle(subtitle1, encodedSubtitle1);
// French
Console.WriteLine(" French \n");
string subtitle2 = Path.Combine(DVDProjectDirectory, "french/french.stl");
string encodedSubtitle2 = Path.Combine(DVDProjectDirectory, "french_sub.mpg");
EncodeSubtitle(subtitle2, encodedSubtitle2);
// Portuguese
Console.WriteLine(" Portuguese \n");
string subtitle3 = Path.Combine(DVDProjectDirectory, "portuguese/portuguese.stl");
string encodedSubtitle3 = Path.Combine(DVDProjectDirectory, "portuguese_sub.mpg");
EncodeSubtitle(subtitle3, encodedSubtitle3);
// Step 3: Build DVD structure
Console.WriteLine("Building DVD structure ... \n");
string project = Path.Combine(DVDProjectDirectory, "project.xml");
string dvdFiles = Path.Combine(DVDProjectDirectory, "dvd");
BuildDVDStructures(project, dvdFiles);
PrimoSoftware.DVDBuilder.Library.Shutdown();
PrimoSoftware.AVBlocks.Library.Shutdown();
}
private static void ConvertSourceToMpeg2(string inputFile, string outputFile)
{
if (File.Exists(outputFile))
File.Delete(outputFile);
var inputInfo = new avb.MediaInfo() {
InputFile = inputFile
};
if (inputInfo.Load())
{
var inputSocket = avb.MediaSocket.FromMediaInfo(inputInfo);
var outputSocket =
avb.MediaSocket.FromPreset(avb.Preset.Video.DVD.NTSC_16x9_PCM);
outputSocket.File = outputFile;
using (var transcoder = new avb.Transcoder())
{
// setup
transcoder.AllowDemoMode = true;
transcoder.Inputs.Add(inputSocket);
transcoder.Outputs.Add(outputSocket);
// handle progress event to report progress
transcoder.OnProgress += (sender, args) => {
DrawTextProgressBar((int)args.CurrentTime, clipTimeSeconds, "seconds");
};
// handle continue event to stop transcoding
transcoder.OnContinue += (sender, args) => {
// stop transcoder after 3 minutes and 20 seconds
if (args.CurrentTime >= clipTimeSeconds + 1)
args.Continue = false;
};
if (transcoder.Open())
{
transcoder.Run();
transcoder.Close();
}
}
}
}
private static void EncodeSubtitle(string subtitle, string encodedSubtitle)
{
if (File.Exists(encodedSubtitle))
File.Delete(encodedSubtitle);
using (var subpicEncoder = new dvdb.SubpictureEncoder())
{
if (!subpicEncoder.Encode(subtitle, encodedSubtitle))
{
Console.WriteLine(subpicEncoder.Error.Message);
}
}
}
private static void BuildDVDStructures(string project, string dvdFiles)
{
if (Directory.Exists(dvdFiles))
Directory.Delete(dvdFiles, true);
using (var dvdBuilder = new dvdb.DVDBuilder())
{
// handle progress event to report progress
dvdBuilder.OnProgress += (sender, args) => {
DrawTextProgressBar(args.Percent, 100, "%");
};
dvdBuilder.ProjectFile = project;
dvdBuilder.OutputFolder = dvdFiles;
if (!dvdBuilder.Build())
{
Console.WriteLine(dvdBuilder.Error.Message);
}
}
}
private static void DrawTextProgressBar(int progress, int total, string units)
{
// draw empty progress bar
Console.CursorLeft = 0;
Console.Write("["); //start
Console.CursorLeft = 32;
Console.Write("]"); //end
Console.CursorLeft = 1;
float oneChunk = 30.0f / total;
// draw filled part
int position = 1;
for (int i = 0; i < oneChunk * progress; i++)
{
Console.BackgroundColor = ConsoleColor.Gray;
Console.CursorLeft = position++;
Console.Write(" ");
}
// draw unfilled part
for (int i = position; i <= 31; i++)
{
Console.BackgroundColor = ConsoleColor.Green;
Console.CursorLeft = position++;
Console.Write(" ");
}
// draw totals
Console.CursorLeft = 35;
Console.BackgroundColor = ConsoleColor.Black;
Console.Write(progress.ToString() + " of " + total.ToString() + " " + units); // blanks at the end remove any excess
}
}
}
Last updated on November 10th, 2017 12:00:00 AM