// --------------------------------------------------------------------------------------------------------------------
//
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
//
//
// Scan a Source
//
// --------------------------------------------------------------------------------------------------------------------
namespace HandBrake.ApplicationServices.Services.Scan
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows.Media.Imaging;
using HandBrake.ApplicationServices.Model;
using HandBrake.ApplicationServices.Services.Encode.Model;
using HandBrake.ApplicationServices.Services.Scan.EventArgs;
using HandBrake.ApplicationServices.Services.Scan.Interfaces;
using HandBrake.ApplicationServices.Services.Scan.Model;
using HandBrake.ApplicationServices.Utilities;
using HandBrake.ApplicationServices.Interop;
using HandBrake.ApplicationServices.Interop.EventArgs;
using HandBrake.ApplicationServices.Interop.HbLib;
using HandBrake.ApplicationServices.Interop.Interfaces;
using HandBrake.ApplicationServices.Interop.Json.Scan;
using HandBrake.ApplicationServices.Interop.Model;
using HandBrake.ApplicationServices.Interop.Model.Preview;
using Chapter = HandBrake.ApplicationServices.Services.Scan.Model.Chapter;
using ScanProgressEventArgs = HandBrake.ApplicationServices.Interop.EventArgs.ScanProgressEventArgs;
using Subtitle = HandBrake.ApplicationServices.Services.Scan.Model.Subtitle;
using SubtitleType = HandBrake.ApplicationServices.Services.Encode.Model.Models.SubtitleType;
using Title = HandBrake.ApplicationServices.Services.Scan.Model.Title;
///
/// Scan a Source
///
public class LibScan : IScan
{
#region Private Variables
///
/// Lock for the log file
///
static readonly object LogLock = new object();
///
/// Log data from HandBrakeInstance
///
private readonly StringBuilder logging;
///
/// The Log File Header
///
private readonly StringBuilder header;
///
/// The Current source scan path.
///
private string currentSourceScanPath;
///
/// The log dir.
///
private static string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs";
///
/// The dvd info path.
///
private string dvdInfoPath = Path.Combine(logDir, string.Format("last_scan_log{0}.txt", GeneralUtilities.ProcessId));
///
/// The scan log.
///
private StreamWriter scanLog;
///
/// LibHB Instance
///
private IHandBrakeInstance instance;
///
/// The post scan operation.
///
private Action postScanOperation;
#endregion
///
/// Initializes a new instance of the class.
///
public LibScan()
{
this.logging = new StringBuilder();
this.header = GeneralUtilities.CreateLogHeader();
}
#region Events
///
/// Scan has Started
///
public event EventHandler ScanStared;
///
/// Scan has completed
///
public event ScanCompletedStatus ScanCompleted;
///
/// Encode process has progressed
///
public event ScanProgessStatus ScanStatusChanged;
#endregion
#region Properties
///
/// Gets a value indicating whether IsScanning.
///
public bool IsScanning { get; private set; }
///
/// Gets the Souce Data.
///
public Source SouceData { get; private set; }
///
/// Gets ActivityLog.
///
public string ActivityLog
{
get
{
string noLog = "There is no log information to display." + Environment.NewLine + Environment.NewLine
+ "This window will only display logging information after you have scanned a source." + Environment.NewLine
+ Environment.NewLine + "You can find previous log files in the log directory or by clicking the 'Open Log Directory' button above.";
return string.IsNullOrEmpty(this.logging.ToString()) ? noLog : this.header + this.logging.ToString();
}
}
#endregion
#region Public Methods
///
/// Scan a Source Path.
/// Title 0: scan all
///
///
/// Path to the file to scan
///
///
/// int title number. 0 for scan all
///
///
/// The post Action.
///
///
/// The configuraiton.
///
public void Scan(string sourcePath, int title, Action postAction, HBConfiguration configuraiton)
{
// Try to cleanup any previous scan instances.
if (this.instance != null)
{
try
{
this.scanLog.Close();
this.scanLog.Dispose();
this.instance.Dispose();
}
catch (Exception)
{
// Do Nothing
}
}
// Handle the post scan operation.
this.postScanOperation = postAction;
// Clear down the logging
this.logging.Clear();
try
{
// Make we don't pick up a stale last_scan_log_xyz.txt (and that we have rights to the file)
if (File.Exists(this.dvdInfoPath))
{
File.Delete(this.dvdInfoPath);
}
}
catch (Exception exc)
{
Debug.WriteLine(exc);
}
if (!Directory.Exists(Path.GetDirectoryName(this.dvdInfoPath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(this.dvdInfoPath));
}
// Create a new scan log.
this.scanLog = new StreamWriter(this.dvdInfoPath);
// Create a new HandBrake Instance.
HandBrakeUtils.MessageLogged += this.HandBrakeInstanceMessageLogged;
HandBrakeUtils.ErrorLogged += this.HandBrakeInstanceErrorLogged;
this.instance = HandBrakeInstanceManager.GetScanInstance(configuraiton.Verbosity);
this.instance.ScanProgress += this.InstanceScanProgress;
this.instance.ScanCompleted += this.InstanceScanCompleted;
// Start the scan on a back
this.ScanSource(sourcePath, title, configuraiton.PreviewScanCount, configuraiton);
}
///
/// Kill the scan
///
public void Stop()
{
try
{
ServiceLogMessage("Stopping Scan.");
this.IsScanning = false;
this.instance.StopScan();
if (this.scanLog != null)
{
this.scanLog.Close();
this.scanLog.Dispose();
}
}
catch (Exception)
{
// Do Nothing.
}
}
///
/// Get a Preview image for the current job and preview number.
///
///
/// The job.
///
///
/// The preview.
///
///
/// The configuraiton.
///
///
/// The .
///
public BitmapImage GetPreview(EncodeTask job, int preview, HBConfiguration configuraiton)
{
if (this.instance == null)
{
return null;
}
BitmapImage bitmapImage = null;
try
{
PreviewSettings settings = new PreviewSettings(job);
bitmapImage = this.instance.GetPreview(settings, preview);
}
catch (AccessViolationException e)
{
Console.WriteLine(e);
}
return bitmapImage;
}
#endregion
#region Private Methods
///
/// Start a scan for a given source path and title
///
///
/// Path to the source file
///
///
/// the title number to look at
///
///
/// The preview Count.
///
///
/// The configuraiton.
///
private void ScanSource(object sourcePath, int title, int previewCount, HBConfiguration configuraiton)
{
try
{
this.logging.Clear();
string source = sourcePath.ToString().EndsWith("\\") ? string.Format("\"{0}\\\\\"", sourcePath.ToString().TrimEnd('\\'))
: "\"" + sourcePath + "\"";
this.currentSourceScanPath = source;
this.IsScanning = true;
TimeSpan minDuration = TimeSpan.FromSeconds(configuraiton.MinScanDuration);
HandBrakeUtils.SetDvdNav(!configuraiton.IsDvdNavDisabled);
this.ServiceLogMessage("Starting Scan ...");
this.instance.StartScan(sourcePath.ToString(), previewCount, minDuration, title != 0 ? title : 0);
if (this.ScanStared != null)
this.ScanStared(this, System.EventArgs.Empty);
}
catch (Exception exc)
{
this.ServiceLogMessage("Scan Failed ..." + Environment.NewLine + exc);
this.Stop();
if (this.ScanCompleted != null)
this.ScanCompleted(this, new ScanCompletedEventArgs(false, exc, "An Error has occured in ScanService.ScanSource()"));
}
}
#endregion
#region HandBrakeInstance Event Handlers
///
/// Scan Completed Event Handler
///
///
/// The sender.
///
///
/// The EventArgs.
///
private void InstanceScanCompleted(object sender, System.EventArgs e)
{
this.ServiceLogMessage("Scan Finished ...");
// Write the log file out before we start processing incase we crash.
try
{
if (this.scanLog != null)
{
this.scanLog.Flush();
}
}
catch (Exception exc)
{
Debug.WriteLine(exc);
}
HandBrakeUtils.MessageLogged -= this.HandBrakeInstanceMessageLogged;
HandBrakeUtils.ErrorLogged -= this.HandBrakeInstanceErrorLogged;
// TODO -> Might be a better place to fix this.
string path = this.currentSourceScanPath;
if (this.currentSourceScanPath.Contains("\""))
{
path = this.currentSourceScanPath.Trim('\"');
}
// Process into internal structures.
if (this.instance != null && this.instance.Titles != null)
{
this.SouceData = new Source { Titles = ConvertTitles(this.instance.Titles), ScanPath = path };
}
this.IsScanning = false;
if (this.postScanOperation != null)
{
this.postScanOperation(true);
}
else
{
if (this.ScanCompleted != null) this.ScanCompleted(this, new ScanCompletedEventArgs(false, null, string.Empty));
}
}
///
/// Scan Progress Event Handler
///
///
/// The sender.
///
///
/// The EventArgs.
///
private void InstanceScanProgress(object sender, ScanProgressEventArgs e)
{
if (this.ScanStatusChanged != null)
{
EventArgs.ScanProgressEventArgs eventArgs =
new EventArgs.ScanProgressEventArgs
{
CurrentTitle = e.CurrentTitle,
Titles = e.Titles,
Percentage = Math.Round((decimal)e.Progress * 100, 0)
};
this.ScanStatusChanged(this, eventArgs);
}
}
///
/// Log a message
///
///
/// The sender.
///
///
/// The MessageLoggedEventArgs.
///
private void HandBrakeInstanceErrorLogged(object sender, MessageLoggedEventArgs e)
{
this.LogMessage(e.Message);
}
///
/// Log a message
///
///
/// The sender.
///
///
/// The MessageLoggedEventArgs.
///
private void HandBrakeInstanceMessageLogged(object sender, MessageLoggedEventArgs e)
{
this.LogMessage(e.Message);
}
///
/// Convert Interop Title objects to App Services Title object
///
///
/// The titles.
///
///
/// The feature Title.
///
///
/// The convert titles.
///
internal static List ConvertTitles(JsonScanObject titles)
{
List titleList = new List();
foreach (TitleList title in titles.TitleList)
{
Title converted = new Title
{
TitleNumber = title.Index,
Duration = new TimeSpan(0, title.Duration.Hours, title.Duration.Minutes, title.Duration.Seconds),
Resolution = new Size(title.Geometry.Width, title.Geometry.Height),
AngleCount = title.AngleCount,
ParVal = new Size(title.Geometry.PAR.Num, title.Geometry.PAR.Den),
AutoCropDimensions = new Cropping
{
Top = title.Crop[0],
Bottom = title.Crop[1],
Left = title.Crop[2],
Right = title.Crop[3]
},
Fps = ((double)title.FrameRate.Num) / title.FrameRate.Den,
SourceName = title.Path,
MainTitle = titles.MainFeature == title.Index,
Playlist = title.Type == 1 ? string.Format(" {0:d5}.MPLS", title.Playlist).Trim() : null,
FramerateNumerator = title.FrameRate.Num,
FramerateDenominator = title.FrameRate.Den
};
int currentTrack = 1;
foreach (ChapterList chapter in title.ChapterList)
{
string chapterName = !string.IsNullOrEmpty(chapter.Name) ? chapter.Name : string.Empty;
converted.Chapters.Add(new Chapter(currentTrack, chapterName, new TimeSpan(chapter.Duration.Hours, chapter.Duration.Minutes, chapter.Duration.Seconds)));
currentTrack++;
}
int currentAudioTrack = 1;
foreach (AudioList track in title.AudioList)
{
converted.AudioTracks.Add(new Audio(currentAudioTrack, track.Language, track.LanguageCode, track.Description, string.Empty, track.SampleRate, track.BitRate));
currentAudioTrack++;
}
int currentSubtitleTrack = 1;
foreach (SubtitleList track in title.SubtitleList)
{
SubtitleType convertedType = new SubtitleType();
switch (track.Source)
{
case 0:
convertedType = SubtitleType.VobSub;
break;
case 4:
convertedType = SubtitleType.UTF8Sub;
break;
case 5:
convertedType = SubtitleType.TX3G;
break;
case 6:
convertedType = SubtitleType.SSA;
break;
case 1:
convertedType = SubtitleType.SRT;
break;
case 2:
convertedType = SubtitleType.CC;
break;
case 3:
convertedType = SubtitleType.CC;
break;
case 7:
convertedType = SubtitleType.PGS;
break;
}
bool canBurn = HBFunctions.hb_subtitle_can_burn(track.Source) > 0;
bool canSetForcedOnly = HBFunctions.hb_subtitle_can_force(track.Source) > 0;
converted.Subtitles.Add(new Subtitle(track.Source, currentSubtitleTrack, track.Language, track.LanguageCode, convertedType, canBurn, canSetForcedOnly));
currentSubtitleTrack++;
}
titleList.Add(converted);
}
return titleList;
}
///
/// The log message.
///
///
/// The message.
///
private void LogMessage(string message)
{
lock (LogLock)
{
if (this.scanLog != null)
{
this.scanLog.WriteLine(message);
}
this.logging.AppendLine(message);
}
}
///
/// The service log message.
///
///
/// The message.
///
protected void ServiceLogMessage(string message)
{
this.LogMessage(string.Format("# {0}", message));
}
#endregion
}
}