Provides access to media information that is only available by opening up the file.
Inheritance: IDisposable
        public async Task StartScan(List<VideoListItem> selection, CancellationToken cancel) {
            await selection.ForEachAsync(5, cancel, async item => {
                MediaInfoReader InfoReader = new MediaInfoReader();
                Media VideoData = PlayerAccess.GetVideoById(item.MediaId.Value);

                if (VideoData != null) {
                    try {
                        // Query the server for media info.
                        SetStatus(item, VideoListItemStatusEnum.DownloadingInfo);
                        var VTask = DownloadBusiness.GetDownloadUrlsAsync(VideoData.DownloadUrl);
                        var VideoList = await VTask;
                        if (VideoList != null) {
                            // Get the highest resolution format.
                            BestFormatInfo VideoFormat = DownloadBusiness.SelectBestFormat(VideoList);
                            if (VideoFormat == null || VideoFormat.BestVideo == null)
                                SetStatus(item, VideoListItemStatusEnum.Failed);
                            else {
                                SetStatus(item, await IsHigherQualityAvailable(Settings.NaturalGroundingFolder + VideoData.FileName, VideoFormat));
                            }
                            if (VideoFormat != null && !string.IsNullOrEmpty(VideoFormat.StatusText))
                                SetStatus(item, item.Status, item.StatusText + string.Format(" ({0})", VideoFormat.StatusText));
                        } else
                            SetStatus(item, VideoListItemStatusEnum.InvalidUrl);
                    } catch {
                        SetStatus(item, VideoListItemStatusEnum.Failed);
                    }
                }
            });
        }
        /// <summary>
        /// Creates an AviSynth script that will auto-pitch the audio to 432hz. You then open this script file in the video player instead of directly opening the file.
        /// </summary>
        /// <param name="inputFile">The video to play.</param>
        /// <param name="infoReader">An object to read media information.</param>
        public static void CreateScript(string inputFile, MediaInfoReader infoReader) {
            bool AviSynthPlus = MpcConfigBusiness.GetAviSynthVersion() == AviSynthVersion.AviSynthPlus;
            AviSynthScriptBuilder Script = new AviSynthScriptBuilder();
            Script.AddPluginPath();
            if (AviSynthPlus) {
                //Script.AppendLine(@"SetFilterMTMode(""DEFAULT_MT_MODE"",2)");
                //Script.AppendLine(@"SetFilterMTMode(""LWLibavVideoSource"",3)");
                //Script.AppendLine(@"SetFilterMTMode(""LWLibavAudioSource"",3)");
                Script.OpenDirect(inputFile, Settings.AutoPitchCache, !string.IsNullOrEmpty(infoReader.AudioFormat), 0);
                Script.AppendLine("Preroll(int(FrameRate*3))");
                // This causes a slight audio delay in AviSynth 2.6
                Script.LoadPluginDll("TimeStretch.dll");
                Script.AppendLine("ResampleAudio(48000)");
                Script.AppendLine("TimeStretchPlugin(pitch = 100.0 * 0.98181819915771484)");
                //Script.AppendLine("Prefetch({0})", CPU);
            } else {
                int CPU = Environment.ProcessorCount / 2;
                Script.AppendLine("SetMTMode(3,{0})", CPU);
                Script.OpenDirect(inputFile, Settings.AutoPitchCache, !string.IsNullOrEmpty(infoReader.AudioFormat), CPU);
                Script.AppendLine("SetMTMode(2)");
                Script.AppendLine("Preroll(int(FrameRate*3))");
                //Script.AppendLine("Loop(int(FrameRate/2), 0, 0)");
                //Script.LoadPluginAvsi("UUSize4.avsi");
                //Script.AppendLine("UUSize4(mod=4)");
                // This slightly slows down playback speed but audio stays in sync
                Script.AppendLine("V = AssumeFPS(432.0 / 440.0 * FrameRate)");
                Script.AppendLine("A = AssumeSampleRate(int(432.0 / 440.0 * AudioRate))");
                Script.AppendLine("AudioDub(V, A)");
            }

            Script.Cleanup();
            Script.WriteToFile(Settings.AutoPitchFile);
            File.Delete(Settings.AutoPitchCache);
        }
 /// <summary>
 /// Applies 432hz auto-pitch if file PixelAspectRatio is 1 and FPS can be read.
 /// </summary>
 /// <param name="video">The video for which to create the auto-pitch script file.</param>
 /// <returns>Whether auto-pitch is enabled for this video.</returns>
 public static bool AppyAutoPitch(Media video) {
     using (MediaInfoReader InfoReader = new MediaInfoReader()) {
         InfoReader.LoadInfo(Settings.NaturalGroundingFolder + video.FileName);
         if (Settings.SavedFile.ChangeAudioPitch && InfoReader.PixelAspectRatio == 1 && !video.DisablePitch && (InfoReader.BitDepth ?? 8) == 8) {
             CreateScript(Settings.NaturalGroundingFolder + video.FileName, InfoReader);
             return true;
         } else
             return false;
     }
 }
        /// <summary>
        /// Returns whether the local file should be replaced by the YouTube version.
        /// </summary>
        /// <param name="localFile">A path to the local file.</param>
        /// <param name="serverFile">The information of the available server file.</param>
        /// <returns>True if the local file should be replaced.</returns>
        public async Task<VideoListItemStatusEnum> IsHigherQualityAvailable(string localFile, BestFormatInfo serverFile) {
            // If there is no local file, it should be downloaded.
            if (!File.Exists(localFile))
                return VideoListItemStatusEnum.HigherQualityAvailable;

            // If local file is FLV and there's another format available, it should be downloaded.
            string LocalFileExt = Path.GetExtension(localFile).ToLower();
            if (LocalFileExt == ".flv" && serverFile.BestVideo.VideoType != VideoType.Flash)
                return VideoListItemStatusEnum.HigherQualityAvailable;

            // Original VCD files and files of unrecognized extensions should not be replaced.
            MediaInfoReader InfoReader = new MediaInfoReader();
            await InfoReader.LoadInfoAsync(localFile);
            if (!DownloadBusiness.DownloadedExtensions.Contains(LocalFileExt) || InfoReader.VideoFormat == "MPEG Video") {
                serverFile.StatusText = "Not from YouTube";
                return VideoListItemStatusEnum.OK;
            }

            // For server file size, estimate 10% extra for audio. Estimate 35% advantage for VP9 format. non-DASH WebM is VP8 and doesn't have that bonus.
            long ServerFileSize = (long)(serverFile.BestVideo.FileSize * 1.1);
            if (serverFile.BestVideo.VideoType == VideoType.WebM && serverFile.BestVideo.AdaptiveType == AdaptiveType.Video)
                ServerFileSize = (long)(ServerFileSize * 1.35);
            long LocalFileSize = new FileInfo(localFile).Length;
            if (InfoReader.VideoFormat == "VP9")
                LocalFileSize = (long)(LocalFileSize * 1.35);

            // If server resolution is better, download unless local file is bigger.
            int LocalFileHeight = InfoReader.Height ?? 0;
            if (serverFile.BestVideo.Resolution > LocalFileHeight) {
                if (ServerFileSize > LocalFileSize)
                    return VideoListItemStatusEnum.HigherQualityAvailable;
                else if (ServerFileSize != 0) {
                    // non-DASH videos have no file size specified, and we won't replace local video with non-DASH video.
                    serverFile.StatusText = "Local file larger";
                    return VideoListItemStatusEnum.OK;
                }
            }

            // Is estimated server file size is at least 15% larger than local file (for same resolution), download.
            if (ServerFileSize > LocalFileSize * 1.15)
                return VideoListItemStatusEnum.HigherQualityAvailable;

            // download audio and merge with local video. (that didn't work, ffmpeg failed to merge back)
            int? LocalAudioBitRate = InfoReader.AudioBitRate;
            int ServerAudioBitRate = serverFile.BestAudio != null ? serverFile.BestAudio.AudioBitrate : serverFile.BestVideo.AudioBitrate;
            // Fix a bug where MediaInfo returns no bitrate for MKV containers with AAC audio.
            if (LocalAudioBitRate != null || LocalFileExt != ".mkv") {
                if ((LocalAudioBitRate == null || LocalAudioBitRate < ServerAudioBitRate * .8) && serverFile.BestVideo.Resolution == LocalFileHeight) {
                    // Only redownload for audio if video file size is similar. Videos with AdaptiveType=None don't have file size.
                    if (ServerFileSize > LocalFileSize * .9 && serverFile.BestVideo.AdaptiveType == AdaptiveType.Video) {
                        serverFile.StatusText = "Audio";
                        return VideoListItemStatusEnum.HigherQualityAvailable;
                    } else {
                        serverFile.StatusText = "";
                        return VideoListItemStatusEnum.BetterAudioAvailable;
                    }
                }
            }

            return VideoListItemStatusEnum.OK;
        }
        /// <summary>
        /// Loops through the Media table to set the FileName, Length, Width and Height fields.
        /// </summary>
        /// <param name="progress">Reports the progress of the operation. First report is the amount of files to process, then subsequent reports represent the quantity done.</param>
        /// <returns>Whether some data was modified.</returns>
        public async Task<bool> LoadMediaInfoAsync(IProgress<int> progress) {
            // Calling this method when it is already running allows listening to the progress.
            if (progress != null) {
                loadingMediaInfoProgress = progress;
                // First progress report is total count.
                if (isLoadingMediaInfo)
                    loadingMediaInfoProgress.Report(loadingMediaInfoCount);
            }

            if (isLoadingMediaInfo)
                return false;
            isLoadingMediaInfo = true;

            bool HasChanges = false;
            using (Entities context = new Entities()) {
                // Loop through all media items with missing Length, Width or Height.
                var Query = (from v in context.Media
                             where v.FileName == null || v.Length == null ||
                                ((v.MediaTypeId == (int)MediaType.Video || v.MediaTypeId == (int)MediaType.Image) && v.Height == null)
                             select v);

                // First progress report contains the total count. Subsequent reports contain the quantity completed.
                loadingMediaInfoCount = Query.Count();
                if (loadingMediaInfoProgress != null)
                    loadingMediaInfoProgress.Report(loadingMediaInfoCount);

                int ItemsCompleted = 0;
                string DefaultFileName;
                string FilePath;
                DefaultMediaPath PathCalc = new DefaultMediaPath();
                PathCalc.LoadData();
                MediaInfoReader MediaInfo = new MediaInfoReader();

                foreach (Media item in Query) {
                    // Try to auto-attach file if default file name exists.
                    if (item.FileName == null) {
                        DefaultFileName = PathCalc.GetDefaultFileName(item.Artist, item.Title, item.MediaCategoryId, item.MediaType);
                        foreach (string ext in Settings.GetMediaTypeExtensions(item.MediaType)) {
                            FilePath = Settings.NaturalGroundingFolder + DefaultFileName + ext;
                            if (File.Exists(FilePath)) {
                                item.FileName = DefaultFileName + ext;
                                HasChanges = true;
                                await Task.Delay(1);
                                break;
                            }
                        }
                    }

                    // Load media file to set Length, Width and Height.
                    if (item.FileName != null && await MediaInfo.LoadInfoAsync(item))
                        HasChanges = true;

                    // Send update with the quantity of files completed.
                    if (loadingMediaInfoProgress != null)
                        loadingMediaInfoProgress.Report(++ItemsCompleted);
                }
                MediaInfo.Dispose();
                if (HasChanges)
                    context.SaveChanges();
            }
            isLoadingMediaInfo = false;
            loadingMediaInfoCount = 0;
            loadingMediaInfoProgress = null;
            return HasChanges;
        }
        private async Task DownloadCompletedAsync(DownloadItem downloadInfo) {
            // Separate file extension.
            string Destination = downloadInfo.Files[0].Destination;
            string DestinationExt = Path.GetExtension(Destination);
            Destination = Destination.Substring(0, Destination.Length - Path.GetExtension(Destination).Length);
            Media video = downloadInfo.Request;

            if (downloadInfo.Files.Count > 1) {
                VideoType File1Type = downloadInfo.Files[0].Source.VideoType;
                AudioType File2Type = downloadInfo.Files[1].Source.AudioType;
                string File1Ext = GetCodecExtension(File1Type);
                string File2Ext = GetAudioExtension(File2Type);
                DestinationExt = GetFinalExtension(File1Type, File2Type);

                // Make sure output file doesn't already exist.
                File.Delete(Destination + DestinationExt);

                // Merge audio and video files.
                await Task.Run(() => FfmpegBusiness.JoinAudioVideo(Destination + File1Ext, Destination + File2Ext, Destination + DestinationExt, true));

                // Delete source files
                File.Delete(Destination + File1Ext);
                File.Delete(Destination + File2Ext);
            } else if (downloadInfo.UpgradeAudio) {
                // Get original video format.
                MediaInfoReader MediaReader = new MediaInfoReader();
                //await MediaReader.LoadInfoAsync(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName);
                string VideoDestExt = ".mkv";
                //if (MediaReader.VideoFormat == "VP8" || MediaReader.VideoFormat == "VP9")
                //    VideoDestExt = ".webm";
                string VideoDest = downloadInfo.Destination + " (extract)" + VideoDestExt;

                // Keep existing video and upgrade audio.
                string AudioExt = GetAudioExtension(downloadInfo.Files[0].Source.AudioType);
                DestinationExt = GetFinalExtension(Path.GetExtension(downloadInfo.Request.FileName), AudioExt);

                // Merge audio and video files.
                await Task.Run(() => {
                    FfmpegBusiness.ExtractVideo(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName, VideoDest, true);
                    if (FileHasContent(VideoDest))
                        FfmpegBusiness.JoinAudioVideo(VideoDest, Destination + AudioExt, Destination + DestinationExt, true);
                });

                // Delete source files
                File.Delete(VideoDest);
                File.Delete(Destination + AudioExt);

                if (FileHasContent(Destination + DestinationExt) && File.Exists(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName))
                    FileOperationAPIWrapper.MoveToRecycleBin(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName);
            }

            // Ensure download and merge succeeded.
            if (FileHasContent(Destination + DestinationExt)) {
                // Get final file name.
                DefaultMediaPath PathCalc = new DefaultMediaPath();
                string NewFileName = PathCalc.GetDefaultFileName(video.Artist, video.Title, video.MediaCategoryId, (MediaType)video.MediaTypeId);
                Directory.CreateDirectory(Path.GetDirectoryName(Settings.NaturalGroundingFolder + NewFileName));
                video.FileName = NewFileName + DestinationExt;

                // Move file and overwrite.
                if (downloadInfo.Request.FileName != null && File.Exists(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName))
                    FileOperationAPIWrapper.MoveToRecycleBin(Settings.NaturalGroundingFolder + downloadInfo.Request.FileName);
                File.Move(Destination + DestinationExt, Settings.NaturalGroundingFolder + video.FileName);
            } else
                throw new FileNotFoundException("Audio and video streams could not be merged.");

            // Add to database
            EditVideoBusiness Business = new EditVideoBusiness();
            Media ExistingData = Business.GetVideoById(video.MediaId);
            if (ExistingData != null) {
                // Edit video info.
                ExistingData.FileName = video.FileName;
                ExistingData.Length = null;
                ExistingData.Height = null;
                Business.Save();
            } else {
                // Add new video info.
                Business.AddVideo(video);
                Business.Save();
            }
            downloadInfo.Request.FileName = video.FileName;

            downloadInfo.Status = DownloadStatus.Done;
        }
        private async void menuExtractAudio_Click(object sender, RoutedEventArgs e) {
            if (video.FileName != null && File.Exists(Settings.NaturalGroundingFolder + video.FileName)) {
                MediaInfoReader MInfo = new MediaInfoReader();
                await MInfo.LoadInfoAsync(Settings.NaturalGroundingFolder + video.FileName);
                string Ext = null;
                if (MInfo.AudioFormat == "MPEG Audio")
                    Ext = ".mp2";
                else if (MInfo.AudioFormat == "PCM")
                    Ext = ".wav";
                else if (MInfo.AudioFormat == "Vorbis")
                    Ext = ".ogg";
                else if (MInfo.AudioFormat == "Opus")
                    Ext = ".opus";
                else
                    Ext = ".aac";

                SaveFileDialog SaveDlg = new SaveFileDialog();
                SaveDlg.InitialDirectory = Settings.NaturalGroundingFolder + "Audios";
                SaveDlg.OverwritePrompt = true;
                SaveDlg.DefaultExt = ".mp3";
                SaveDlg.Filter = string.Format("Audio Files|*{0})", Ext); ;
                SaveDlg.FileName = Path.GetFileNameWithoutExtension(video.FileName) + Ext;

                if (SaveDlg.ShowDialog() == true) {
                    FfmpegBusiness.ExtractAudio(Settings.NaturalGroundingFolder + video.FileName, SaveDlg.FileName);
                }
            }
        }
 private async Task LoadMediaInfoAsync() {
     MediaInfoReader MediaInfo = new MediaInfoReader();
     await MediaInfo.LoadInfoAsync(video);
     MediaInfo.Dispose();
     DimensionText.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
 }
 private async Task LoadMediaInfoAsync() {
     using (MediaInfoReader MediaInfo = new MediaInfoReader()) {
         await MediaInfo.LoadInfoAsync(video);
         DimensionText.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
         DisablePitchCheckBox.IsEnabled = MediaInfo.PixelAspectRatio == 1;
         if (!DisablePitchCheckBox.IsEnabled)
             video.DisablePitch = false;
     }
 }
 /// <summary>
 /// Extracts the video stream from specified video file.
 /// </summary>
 /// <param name="source">The video file to extract from.</param>
 /// <param name="destination">The output file to create.</param>
 /// <param name="silent">If true, the FFMPEG window will be hidden.</param>
 /// <returns>Whether the operation was completed.</returns>
 public static bool ExtractVideo(string source, string destination, bool silent) {
     MediaInfoReader MediaReader = new MediaInfoReader();
     File.Delete(destination);
     return Run(string.Format(@"-i ""{0}"" -vcodec copy -an ""{1}""", source, destination), silent);
 }
 private async Task GetMediaInfo(string previewFile, MediaEncoderSettings settings) {
     MediaInfoReader InfoReader = new MediaInfoReader();
     await InfoReader.LoadInfoAsync(previewFile);
     settings.SourceWidth = InfoReader.Width;
     settings.SourceHeight = InfoReader.Height;
     settings.SourceAspectRatio = InfoReader.PixelAspectRatio ?? 1;
     // Fix last track of VCDs that is widescreen.
     if (settings.SourceHeight == 288 && settings.SourceWidth == 352 && settings.SourceAspectRatio == 1.485f)
         settings.SourceAspectRatio = 1.092f;
     settings.SourceFrameRate = InfoReader.FrameRate;
     if (settings.ConvertToAvi)
         await InfoReader.LoadInfoAsync(Settings.NaturalGroundingFolder + settings.FileName);
     settings.AudioRequiresMkv = (InfoReader.AudioFormat != "AAC");
     settings.Position = (InfoReader.Length ?? 0) / 2;
 }
 private async Task GetMediaInfo(string previewFile, MediaEncoderSettings settings) {
     using (MediaInfoReader InfoReader = new MediaInfoReader()) {
         await InfoReader.LoadInfoAsync(previewFile);
         settings.SourceWidth = InfoReader.Width;
         settings.SourceHeight = InfoReader.Height;
         settings.SourceAspectRatio = InfoReader.PixelAspectRatio ?? 1;
         if (settings.SourceAspectRatio != 1) {
             // Get aspect ratio from FFMPEG which is more accurate.
             float? SAR = FfmpegBusiness.GetPixelAspectRatio(settings);
             if (SAR.HasValue)
                 settings.SourceAspectRatio = SAR.Value;
         }
         // Fix last track of VCDs that is widescreen.
         if (settings.SourceHeight == 288 && settings.SourceWidth == 352 && settings.SourceAspectRatio == 1.485f)
         settings.SourceAspectRatio = 1.092f;
         settings.SourceFrameRate = InfoReader.FrameRate;
         if (settings.ConvertToAvi)
             await InfoReader.LoadInfoAsync(Settings.NaturalGroundingFolder + settings.FileName);
         settings.SourceAudioFormat = InfoReader.AudioFormat;
         settings.SourceVideoFormat = InfoReader.VideoFormat;
         settings.SourceColorMatrix = InfoReader.Height < 720 ? ColorMatrix.Rec601 : ColorMatrix.Rec709;
         if (!settings.CanCopyAudio)
             settings.EncodeFormat = VideoFormats.Mkv;
         settings.SourceAudioBitrate = InfoReader.AudioBitRate;
         settings.SourceBitDepth = InfoReader.BitDepth;
         settings.Position = (InfoReader.Length ?? 0) / 2;
         settings.CalculateSize();
     }
 }
        public async Task PreparePreviewFile(MediaEncoderSettings settings, bool overwrite) {
            if (string.IsNullOrEmpty(settings.FileName))
                return;

            if (overwrite) {
                File.Delete(PreviewSourceFile);
                // Select default open method.
                if (settings.FileName.ToLower().EndsWith(".avi"))
                    settings.ConvertToAvi = false;
                else {
                    using (MediaInfoReader InfoReader = new MediaInfoReader()) {
                        InfoReader.LoadInfo(Settings.NaturalGroundingFolder + settings.FileName);
                        if (settings.ConvertToAvi && InfoReader.Height.HasValue && InfoReader.Height >= 720)
                            settings.ConvertToAvi = false;
                    }
                }
            }

            bool AviFileReady = File.Exists(PreviewSourceFile);
            if (!AviFileReady && settings.ConvertToAvi) 
                AviFileReady = await Task.Run(() => FfmpegBusiness.ConvertToAVI(Settings.NaturalGroundingFolder + settings.FileName, PreviewSourceFile, false));

            if (AviFileReady && settings.ConvertToAvi)
                await GetMediaInfo(PreviewSourceFile, settings);
            else {
                settings.ConvertToAvi = false;
                await GetMediaInfo(Settings.NaturalGroundingFolder + settings.FileName, settings);
            }

            // Auto-calculate crop settings.
            if (settings.CropLeft == 0 && settings.CropTop == 0 && settings.CropRight == 0 && settings.CropBottom == 0) {
                Rect AutoCrop = await Task.Run(() => FfmpegBusiness.GetAutoCropRect(settings, true));
                if (settings.CropLeft == 0)
                    settings.CropLeft = AutoCrop.Left;
                if (settings.CropTop == 0)
                    settings.CropTop = AutoCrop.Top;
                if (settings.CropRight == 0)
                    settings.CropRight = AutoCrop.Right;
                if (settings.CropBottom == 0)
                    settings.CropBottom = AutoCrop.Bottom;
            }
        }