// --------------------------------------------------------------------------------------------------------------------
//
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
//
//
// An object that represents a single Title of a DVD
//
// --------------------------------------------------------------------------------------------------------------------
namespace HandBrake.ApplicationServices.Parsing
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using HandBrake.Interop.Model;
using Size = System.Drawing.Size;
///
/// An object that represents a single Title of a DVD
///
public class Title
{
///
/// Initializes a new instance of the class.
///
public Title()
{
this.AudioTracks = new List();
this.Chapters = new List();
this.Subtitles = new List();
}
#region Properties
///
/// Gets or sets a Collection of chapters in this Title
///
public List Chapters { get; set; }
///
/// Gets or sets a Collection of audio tracks associated with this Title
///
public List AudioTracks { get; set; }
///
/// Gets or sets a Collection of subtitles associated with this Title
///
public List Subtitles { get; set; }
///
/// Gets or sets The track number of this Title
///
public int TitleNumber { get; set; }
///
/// Gets or sets Playlist.
///
public string Playlist { get; set; }
///
/// Gets or sets the length in time of this Title
///
public TimeSpan Duration { get; set; }
///
/// Gets or sets the resolution (width/height) of this Title
///
public Size Resolution { get; set; }
///
/// Gets or sets the aspect ratio of this Title
///
public double AspectRatio { get; set; }
///
/// Gets or sets AngleCount.
///
public int AngleCount { get; set; }
///
/// Gets or sets Par Value
///
public Size ParVal { get; set; }
///
/// Gets or sets the automatically detected crop region for this Title.
/// This is an int array with 4 items in it as so:
/// 0: T
/// 1: B
/// 2: L
/// 3: R
///
public Cropping AutoCropDimensions { get; set; }
///
/// Gets or sets the FPS of the source.
///
public double Fps { get; set; }
///
/// Gets or sets a value indicating whether this is a MainTitle.
///
public bool MainTitle { get; set; }
///
/// Gets or sets the Source Name
///
public string SourceName { get; set; }
#endregion
///
/// Parse the Title Information
///
///
/// A StringReader of output data
///
///
/// The is Dvd Nav Disabled.
///
///
/// A Title Object
///
public static Title Parse(StringReader output, bool isDvdNavDisabled)
{
var thisTitle = new Title();
string nextLine = output.ReadLine();
// Get the Title Number
Match m = Regex.Match(nextLine, @"^\+ title ([0-9]*):");
if (m.Success)
thisTitle.TitleNumber = int.Parse(m.Groups[1].Value.Trim());
nextLine = output.ReadLine();
// Detect if this is the main feature
m = Regex.Match(nextLine, @" \+ Main Feature");
if (m.Success)
{
thisTitle.MainTitle = true;
nextLine = output.ReadLine();
}
// Get the stream name for file import
m = Regex.Match(nextLine, @"^ \+ stream:");
if (m.Success)
{
thisTitle.SourceName = nextLine.Replace("+ stream:", string.Empty).Trim();
nextLine = output.ReadLine();
}
// Playlist
m = Regex.Match(nextLine, @"^ \+ playlist:");
if (m.Success)
{
thisTitle.Playlist = nextLine.Replace("+ playlist:", string.Empty).Trim();
nextLine = output.ReadLine();
}
// Jump over the VTS and blocks line
m = Regex.Match(nextLine, @"^ \+ vts:");
if (nextLine.Contains("blocks") || nextLine.Contains("+ vts "))
{
nextLine = output.ReadLine();
}
// Multi-Angle Support if LibDvdNav is enabled
m = Regex.Match(nextLine, @" \+ angle\(s\) ([0-9]*)");
if (m.Success)
{
string angleList = m.Value.Replace("+ angle(s) ", string.Empty).Trim();
int angleCount;
int.TryParse(angleList, out angleCount);
thisTitle.AngleCount = angleCount;
nextLine = output.ReadLine();
}
// Get duration for this title
m = Regex.Match(nextLine, @"^ \+ duration: ([0-9]{2}:[0-9]{2}:[0-9]{2})");
if (m.Success)
thisTitle.Duration = TimeSpan.Parse(m.Groups[1].Value);
// Get resolution, aspect ratio and FPS for this title
m = Regex.Match(output.ReadLine(), @"^ \+ size: ([0-9]*)x([0-9]*), pixel aspect: ([0-9]*)/([0-9]*), display aspect: ([0-9]*\.[0-9]*), ([0-9]*\.[0-9]*) fps");
if (m.Success)
{
thisTitle.Resolution = new Size(int.Parse(m.Groups[1].Value), int.Parse(m.Groups[2].Value));
thisTitle.ParVal = new Size(int.Parse(m.Groups[3].Value), int.Parse(m.Groups[4].Value));
thisTitle.AspectRatio = Math.Round(float.Parse(m.Groups[5].Value, CultureInfo.InvariantCulture), 2);
thisTitle.Fps = float.Parse(m.Groups[6].Value, CultureInfo.InvariantCulture);
}
// Get autocrop region for this title
m = Regex.Match(output.ReadLine(), @"^ \+ autocrop: ([0-9]*)/([0-9]*)/([0-9]*)/([0-9]*)");
if (m.Success)
{
thisTitle.AutoCropDimensions = new Cropping
{
Top = int.Parse(m.Groups[1].Value),
Bottom = int.Parse(m.Groups[2].Value),
Left = int.Parse(m.Groups[3].Value),
Right = int.Parse(m.Groups[4].Value)
};
}
thisTitle.Chapters.AddRange(Chapter.ParseList(output));
thisTitle.AudioTracks.AddRange(AudioHelper.ParseList(output));
thisTitle.Subtitles.AddRange(Subtitle.ParseList(output));
return thisTitle;
}
///
/// Return a list of parsed titles
///
///
/// The Output
///
///
/// The is Dvd Nav Disabled.
///
///
/// A List of titles
///
public static Title[] ParseList(string output, bool isDvdNavDisabled)
{
var titles = new List();
var sr = new StringReader(output);
while (sr.Peek() == '+' || sr.Peek() == ' ')
{
// If the the character is a space, then chances are the line
if (sr.Peek() == ' ') // If the character is a space, then chances are it's the combing detected line.
sr.ReadLine(); // Skip over it
else
titles.Add(Parse(sr, isDvdNavDisabled));
}
return titles.ToArray();
}
///
/// Calcuate the Duration
///
/// The Start Point (Chapters)
/// The End Point (Chapters)
/// A Timespan
public TimeSpan CalculateDuration(int startPoint, int endPoint)
{
IEnumerable chapers =
this.Chapters.Where(c => c.ChapterNumber >= startPoint && c.ChapterNumber <= endPoint);
TimeSpan duration = TimeSpan.FromSeconds(0.0);
duration = chapers.Aggregate(duration, (current, chapter) => current + chapter.Duration);
return duration;
}
///
/// Override of the ToString method to provide an easy way to use this object in the UI
///
/// A string representing this track in the format: {title #} (00:00:00)
public override string ToString()
{
if (!string.IsNullOrEmpty(this.Playlist) && !this.Playlist.StartsWith(" "))
{
this.Playlist = string.Format(" {0}", this.Playlist);
}
return string.Format("{0}{1} ({2:00}:{3:00}:{4:00})", TitleNumber, Playlist, Duration.Hours, Duration.Minutes, Duration.Seconds);
}
}
}