// -------------------------------------------------------------------------------------------------------------------- // // 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 HandBrakeWPF.Services.Scan { using System; using System.Collections.Generic; using System.Diagnostics; using System.Windows.Media.Imaging; using HandBrake.ApplicationServices.Interop; 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 HandBrake.ApplicationServices.Model; using HandBrake.ApplicationServices.Services.Logging; using HandBrake.ApplicationServices.Services.Logging.Interfaces; using HandBrake.ApplicationServices.Services.Logging.Model; using HandBrakeWPF.Properties; using HandBrakeWPF.Services.Encode.Model; using HandBrakeWPF.Services.Encode.Model.Models; using HandBrakeWPF.Services.Scan.EventArgs; using HandBrakeWPF.Services.Scan.Interfaces; using HandBrakeWPF.Services.Scan.Model; using HandBrakeWPF.Utilities; using Chapter = HandBrakeWPF.Services.Scan.Model.Chapter; using ScanProgressEventArgs = HandBrake.ApplicationServices.Interop.EventArgs.ScanProgressEventArgs; using Subtitle = HandBrakeWPF.Services.Scan.Model.Subtitle; using Title = HandBrakeWPF.Services.Scan.Model.Title; /// /// Scan a Source /// public class LibScan : IScan { #region Private Variables private readonly ILog log = LogService.GetLogger(); private string currentSourceScanPath; private IHandBrakeInstance instance; private Action postScanOperation; private bool isCancelled = false; #endregion /// /// Initializes a new instance of the class. /// public LibScan() { this.IsScanning = false; } #region Events /// /// Scan has Started /// public event EventHandler ScanStarted; /// /// 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; } #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.instance.Dispose(); } catch (Exception) { // Do Nothing } } // Handle the post scan operation. this.postScanOperation = postAction; // Create a new HandBrake Instance. 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 { this.ServiceLogMessage("Stopping Scan."); this.IsScanning = false; this.instance.StopScan(); } catch (Exception exc) { this.isCancelled = false; this.ScanCompleted?.Invoke(this, new ScanCompletedEventArgs(false, exc, Resources.ScanService_ScanStopFailed, null)); // Do Nothing. } } /// /// Cancel the current scan. /// public void Cancel() { this.isCancelled = true; this.Stop(); } /// /// 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 { Cropping = new Cropping(job.Cropping), MaxWidth = job.MaxWidth ?? 0, MaxHeight = job.MaxHeight ?? 0, KeepDisplayAspect = job.KeepDisplayAspect, TitleNumber = job.Title, Anamorphic = job.Anamorphic, Modulus = job.Modulus, Width = job.Width ?? 0, Height = job.Height ?? 0, PixelAspectX = job.PixelAspectX, PixelAspectY = job.PixelAspectY }; bitmapImage = BitmapUtilities.ConvertToBitmapImage(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 { 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, configuraiton.ScalingMode == VideoScaler.BicubicCl); if (this.ScanStarted != null) this.ScanStarted(this, System.EventArgs.Empty); } catch (Exception exc) { this.ServiceLogMessage("Scan Failed ..." + Environment.NewLine + exc); this.Stop(); this.ScanCompleted?.Invoke(this, new ScanCompletedEventArgs(false, exc, "An Error has occured in ScanService.ScanSource()", null)); } } #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 ..."); bool cancelled = this.isCancelled; this.isCancelled = false; // 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. Source sourceData = null; if (this.instance?.Titles != null) { sourceData = new Source { Titles = ConvertTitles(this.instance.Titles), ScanPath = path }; } this.IsScanning = false; if (this.postScanOperation != null) { try { this.postScanOperation(true, sourceData); } catch (Exception exc) { Debug.WriteLine(exc); } this.postScanOperation = null; // Reset } else { this.ScanCompleted?.Invoke(this, new ScanCompletedEventArgs(cancelled, null, string.Empty, sourceData)); } } /// /// 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); } } /// /// Convert Interop Title objects to App Services Title object /// /// /// The titles. /// /// /// The convert titles. /// internal static List ConvertTitles(JsonScanObject titles) { List<Title> titleList = new List<Title>(); foreach (SourceTitle 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 (SourceChapter 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 (SourceAudioTrack track in title.AudioList) { converted.AudioTracks.Add(new Audio(currentAudioTrack, track.Language, track.LanguageCode, track.Description, track.Codec, track.SampleRate, track.BitRate, track.ChannelLayout)); currentAudioTrack++; } int currentSubtitleTrack = 1; foreach (SourceSubtitleTrack 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; } /// <summary> /// The service log message. /// </summary> /// <param name="message"> /// The message. /// </param> protected void ServiceLogMessage(string message) { this.log.LogMessage(string.Format("{0} # {1}{0}", Environment.NewLine, message), LogMessageType.ScanOrEncode, LogLevel.Info); } #endregion } }