// -------------------------------------------------------------------------------------------------------------------- // // 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.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.Interop; using HandBrake.Interop.EventArgs; using HandBrake.Interop.Interfaces; using HandBrake.Interop.Model; using HandBrake.Interop.Model.Scan; using Chapter = HandBrake.ApplicationServices.Services.Scan.Model.Chapter; using ScanProgressEventArgs = HandBrake.Interop.EventArgs.ScanProgressEventArgs; using Size = System.Drawing.Size; 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 { /* * TODO * 1. Expose the Previews code. * 2. Cleanup old instances. * */ #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.CreateCliLogHeader(); } #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) { // Do nothing. } 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() { ServiceLogMessage("Stopping Scan."); this.instance.StopScan(); try { 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; } EncodeJob encodeJob = InteropModelCreator.GetEncodeJob(job, configuraiton); BitmapImage bitmapImage = null; try { bitmapImage = this.instance.GetPreview(encodeJob, 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; if (this.ScanStared != null) this.ScanStared(this, System.EventArgs.Empty); 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); } 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("Starting Completed ..."); // Write the log file out before we start processing incase we crash. try { if (this.scanLog != null) { this.scanLog.Flush(); } } catch (Exception) { // Do Nothing. } 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. this.SouceData = new Source { Titles = ConvertTitles(this.instance.Titles, this.instance.FeatureTitle), 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(IEnumerable<Interop.Model.Scan.Title> titles, int featureTitle) { List<Title> titleList = new List<Title>(); foreach (Interop.Model.Scan.Title title in titles) { Title converted = new Title { TitleNumber = title.TitleNumber, Duration = title.Duration, Resolution = new Size(title.Resolution.Width, title.Resolution.Height), AngleCount = title.AngleCount, ParVal = new Size(title.ParVal.Width, title.ParVal.Height), AutoCropDimensions = title.AutoCropDimensions, Fps = title.Framerate, SourceName = title.Path, MainTitle = title.TitleNumber == featureTitle, Playlist = title.InputType == InputType.Bluray ? string.Format(" {0:d5}.MPLS", title.Playlist).Trim() : null, FramerateNumerator = title.FramerateNumerator, FramerateDenominator = title.FramerateDenominator }; foreach (Interop.Model.Scan.Chapter chapter in title.Chapters) { string chapterName = !string.IsNullOrEmpty(chapter.Name) ? chapter.Name : string.Empty; converted.Chapters.Add(new Chapter(chapter.ChapterNumber, chapterName, chapter.Duration)); } foreach (AudioTrack track in title.AudioTracks) { converted.AudioTracks.Add(new Audio(track.TrackNumber, track.Language, track.LanguageCode, track.Description, string.Empty, track.SampleRate, track.Bitrate)); } foreach (Interop.Model.Scan.Subtitle track in title.Subtitles) { SubtitleType convertedType = new SubtitleType(); switch (track.SubtitleSource) { case SubtitleSource.VobSub: convertedType = SubtitleType.VobSub; break; case SubtitleSource.UTF8: convertedType = SubtitleType.UTF8Sub; break; case SubtitleSource.TX3G: convertedType = SubtitleType.TX3G; break; case SubtitleSource.SSA: convertedType = SubtitleType.SSA; break; case SubtitleSource.SRT: convertedType = SubtitleType.SRT; break; case SubtitleSource.CC608: convertedType = SubtitleType.CC; break; case SubtitleSource.CC708: convertedType = SubtitleType.CC; break; case SubtitleSource.PGS: convertedType = SubtitleType.PGS; break; } converted.Subtitles.Add(new Subtitle(track.SubtitleSourceInt, track.TrackNumber, track.Language, track.LanguageCode, convertedType, track.CanBurn, track.CanSetForcedOnly)); } titleList.Add(converted); } return titleList; } /// <summary> /// The log message. /// </summary> /// <param name="message"> /// The message. /// </param> private void LogMessage(string message) { lock (LogLock) { if (this.scanLog != null) { this.scanLog.WriteLine(message); } this.logging.AppendLine(message); } } /// <summary> /// The service log message. /// </summary> /// <param name="message"> /// The message. /// </param> protected void ServiceLogMessage(string message) { this.LogMessage(string.Format("# {0}", message)); } #endregion } }