Example #1
0
        /// <summary>
        /// Remux the source recorded file using the specified base parameters, but also check if the source file (or Remux file) has any zero channel audio streams in it.
        /// If so it tries to remux using FFMPEG and the given parameters but compensating to keep only one audio channel
        /// It also checks to see if it can locate the user identified audio language within the source recorded file if required
        /// </summary>
        /// <param name="baseRemuxParams">Base parameters used for remuxing</param>
        /// <param name="FPS">Frame rate for the Recorded file</param>
        /// <param name="fixRemuxedFile">True if Remuxed file file needs to be FIXED for Zero Channel Audio, false if Recorded file needs to be checked and then fixed</param>
        /// <param name="ffmpeg">Point to the last executed ffmpeg object (used for analysis) since this is a recursive reentrant function, we need to keep track</param>
        /// <returns>True if there are no zero channel audio tracks or on a successful remux</returns>
        private bool FFMPEGRemuxZeroChannelFix(string baseRemuxParams, float FPS, bool fixRemuxedFile, ref FFmpeg ffmpeg)
        {
            bool autoFPS = false; // Used to check if Auto FPS was used

            _jobLog.WriteEntry(this, "Verifying " + (fixRemuxedFile ? "Remuxed" : "Recorded") + " file audio streams for Zero Channel Audio", Log.LogEntryType.Debug);

            // Compensate for FFMPEG bug #2227 where the mjpeg is identified as a video stream hence breaking -map 0:v, rather replace 'v' with the actual video stream number
            if (_RecordingFileMediaInfo.Success && !_RecordingFileMediaInfo.ParseError)
            {
                if (baseRemuxParams.Contains("-map 0:v"))
                {
                    if (_RecordingFileMediaInfo.MediaInfo.VideoInfo.Stream != -1) // Check if we have a file with only audio streams
                        baseRemuxParams = baseRemuxParams.Replace("-map 0:v", "-map 0:" + _RecordingFileMediaInfo.MediaInfo.VideoInfo.Stream.ToString(CultureInfo.InvariantCulture)); // replace 0:v with the actual video stream number
                    else
                    {
                        _jobLog.WriteEntry(this, "No Video stream detected in original file, removing support for video stream selection", Log.LogEntryType.Warning);
                        baseRemuxParams = baseRemuxParams.Replace("-map 0:v", ""); // remove 0:v since we have no video stream
                    }
                }

                // Check of the original file has no audio, then remove support for audio mappings
                if (_RecordingFileMediaInfo.AudioTracks < 1)
                {
                    _jobLog.WriteEntry(this, "No Audio stream detected in original file, removing support for audio stream selection", Log.LogEntryType.Warning);
                    baseRemuxParams = baseRemuxParams.Replace("-map 0:a", ""); // remove 0:v since we have no video stream
                }
            }
            else
            {
                _jobLog.WriteEntry(this, "Error reading audio and video streams, removing support for audio and video stream selection", Log.LogEntryType.Warning);
                baseRemuxParams = Regex.Replace(baseRemuxParams, @"-map 0:[\S]*", ""); // Remove all patterns like -map 0:v or -map 0:4 since we cannot read ffmpeg stream info
            }

            // We have a 0 channel audio we try to compensate for it by selecting the appropriate audio channel
            if (CheckForNoneOrZeroChannelAudioTrack((fixRemuxedFile ? RemuxedFile : _RecordingFile), _jobStatus, _jobLog))
            {
                _jobLog.WriteEntry(this, Localise.GetPhrase("Found 0 channel audio while remuxing, re-remuxing using a single audio track"), Log.LogEntryType.Information);
                _jobStatus.CurrentAction = Localise.GetPhrase("Re-ReMuxing due to audio error");

                // DO NOT USE MAP ALL commands, we only need to copy one audio and one video stream
                baseRemuxParams = Regex.Replace(baseRemuxParams, @"-map 0:[\S]*", ""); // Remove all patterns like -map 0:v or -map 0:4

                if (_RecordingFileMediaInfo.Success && !_RecordingFileMediaInfo.ParseError)
                {
                    // Audio parameters - find the best Audio channel for the selected language or best audio track if there are imparired tracks otherwise by default the encoder will select the best audio channel (encoders do not do a good job of ignoring imparired tracks)
                    bool selectedTrack = false;
                    int audioChannels = 0;
                    int audioStream = -1;
                    int videoStream = -1;
                    bool selectedAudioImpaired = false;

                    if ((!String.IsNullOrEmpty(_requestedAudioLanguage) || (_RecordingFileMediaInfo.ImpariedAudioTrackCount > 0)) && (_RecordingFileMediaInfo.AudioTracks > 1)) // More than 1 audio track to choose from and either we have a language match request or a presence of an imparied channel (likely no audio)
                    {
                        for (int i = 0; i < _RecordingFileMediaInfo.AudioTracks; i++)
                        {
                            bool processTrack = false; // By default we don't need to process

                            // Language selection check, if the user has picked a specific language code, look for it
                            // If we find a match, we look the one with the highest number of channels in it
                            if (!String.IsNullOrEmpty(_requestedAudioLanguage))
                            {
                                if ((_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Language.ToLower() == _requestedAudioLanguage) && (_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Channels > 0))
                                {
                                    if (selectedTrack)
                                    {
                                        if (!( // take into account impaired tracks (since impaired tracks typically have no audio)
                                            ((_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Channels > audioChannels) && !_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Impaired) || // PREFERENCE to non-imparied Audio tracks with the most channels
                                            ((_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Channels > audioChannels) && selectedAudioImpaired) || // PREFERENCE to Audio tracks with most channels if currently selected track is impaired
                                            (!_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Impaired && selectedAudioImpaired) // PREFER non impaired audio over currently selected impaired
                                            ))
                                            continue; // we have found a lang match, now we are looking for more channels only now
                                    }

                                    processTrack = true; // All conditions met, we need to process this track
                                }
                            }
                            else if (_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Channels > 0)// we have a imparied audio track, select the non impaired track with the highest number of tracks or bitrate or frequency
                            {
                                if (selectedTrack)
                                {
                                    if (!( // take into account impaired tracks (since impaired tracks typically have no audio)
                                        ((_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Channels > audioChannels) && !_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Impaired) || // PREFERENCE to non-imparied Audio tracks with the most channels
                                        ((_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Channels > audioChannels) && selectedAudioImpaired) || // PREFERENCE to Audio tracks with most channels if currently selected track is impaired
                                        (!_RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Impaired && selectedAudioImpaired) // PREFER non impaired audio over currently selected impaired
                                        ))
                                        continue; // we have found a lang match, now we are looking for more channels only now
                                }
                             
                                processTrack = true; // All conditions met, we need to process this track
                            }

                            if (processTrack) // We need to process this track
                            {
                                audioChannels = _RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Channels;
                                audioStream = _RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Stream; // store the stream number for the selected audio channel
                                string audioCodec = _RecordingFileMediaInfo.MediaInfo.AudioInfo[i].AudioCodec;
                                int audioTrack = i; // Store the audio track number we selected
                                string audioLanguage = _RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Language.ToLower(); // this is what we selected
                                selectedAudioImpaired = _RecordingFileMediaInfo.MediaInfo.AudioInfo[i].Impaired; // Is this an imparied track?
                                videoStream = _RecordingFileMediaInfo.MediaInfo.VideoInfo.Stream; // Store the video information
                                selectedTrack = true; // We found a suitable track

                                if (!String.IsNullOrEmpty(_requestedAudioLanguage))
                                    _jobLog.WriteEntry(this, Localise.GetPhrase("Found Audio Language match for language") + " " + _requestedAudioLanguage.ToUpper() + ", " + Localise.GetPhrase("Audio Stream") + " " + audioStream.ToString(CultureInfo.InvariantCulture) + ", " + Localise.GetPhrase("Audio Track") + " " + audioTrack.ToString(CultureInfo.InvariantCulture) + ", " + Localise.GetPhrase("Channels") + " " + audioChannels.ToString(CultureInfo.InvariantCulture) + ", " + Localise.GetPhrase("Codec") + "->" + audioCodec + ", Audio Impaired->" + selectedAudioImpaired.ToString(), Log.LogEntryType.Debug);
                                else
                                    _jobLog.WriteEntry(this, Localise.GetPhrase("Compensating for audio impaired tracks, selected track with language") + " " + _requestedAudioLanguage.ToUpper() + ", " + Localise.GetPhrase("Audio Stream") + " " + audioStream.ToString(CultureInfo.InvariantCulture) + ", " + Localise.GetPhrase("Audio Track") + " " + audioTrack.ToString(CultureInfo.InvariantCulture) + ", " + Localise.GetPhrase("Channels") + " " + audioChannels.ToString(CultureInfo.InvariantCulture) + ", " + Localise.GetPhrase("Codec") + "->" + audioCodec + ", Audio Impaired->" + selectedAudioImpaired.ToString(), Log.LogEntryType.Debug);
                            }
                        }
                    }

                    // If we have a found a suitable language, select it else let FFMPEG select it automatically
                    if (selectedTrack)
                    {
                        if (audioStream != -1)
                            baseRemuxParams += " -map 0:" + audioStream.ToString(CultureInfo.InvariantCulture); // Select the Audiotrack we had isolated earlier
                        
                        if (videoStream != -1)
                            baseRemuxParams += " -map 0:" + videoStream.ToString(CultureInfo.InvariantCulture); // Check if we have a video stream
                    }
                    else
                        _jobLog.WriteEntry(this, "No audio language match found, letting encoder choose best audio language", Log.LogEntryType.Warning);
                }
                else
                    _jobLog.WriteEntry(this, "Error reading audio streams, letting encoder choose best audio language", Log.LogEntryType.Warning);
            }

            // Build the command line to remux the file now
            string ffmpegParams = "";

            // Check for auto frame rate and replace with video framerate
            if (baseRemuxParams.Contains("-r auto"))
            {
                if (FPS > 0)
                {
                    _jobLog.WriteEntry(this, "Detected Auto FPS request, setting frame rate to " + FPS.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug);
                    baseRemuxParams = baseRemuxParams.Replace("-r auto", "-r " + FPS.ToString(CultureInfo.InvariantCulture));
                    autoFPS = true;
                }
                else
                {
                    _jobLog.WriteEntry(this, Localise.GetPhrase("Cannot read frame rate from file, skipping frame rate adjustment"), Log.LogEntryType.Warning);
                    baseRemuxParams = baseRemuxParams.Replace("-r auto", ""); // no framerate since we can't read it
                    autoFPS = false;
                }
            }
            else
                autoFPS = false;

            // Check for input file placeholders and substitute
            if (baseRemuxParams.Contains("-i <source>")) // Check if we have a input file placeholder (useful if we need to put commands before -i)
                ffmpegParams = "-y " + baseRemuxParams.Replace("-i <source>", "-i " + Util.FilePaths.FixSpaces(_RecordingFile) + " ") + " " + Util.FilePaths.FixSpaces(RemuxedFile);
            else
                ffmpegParams = "-y -i " + Util.FilePaths.FixSpaces(_RecordingFile) + " " + baseRemuxParams + " " + Util.FilePaths.FixSpaces(RemuxedFile); // DO NOT USE -async 1 with COPY

            FFmpeg.FFMpegExecuteAndHandleErrors(ffmpegParams, _jobStatus, _jobLog, Util.FilePaths.FixSpaces(RemuxedFile), false, out ffmpeg); // Run ffmpeg and check for common errors, we will get back the final executed ffmpeg object which we can then check for errors/analyze
            if (!ffmpeg.Success || !RemuxedFileOK()) //check of file is created, outputhandler reports success (Percentage not requires since Success is more accurate)
            {
                // last ditch effort, try to fix for all errors
                if (ffmpeg.Success && !fixRemuxedFile) // avoid infinite loop, fix remuxed file only if we started out checking the recorded file
                {
                    if (!FFMPEGRemuxZeroChannelFix(baseRemuxParams, FPS, true, ref ffmpeg)) // Call ZeroChannelAudioFix this time to fix the remuxed file
                    {
                        _jobLog.WriteEntry(Localise.GetPhrase("0 Channel ReMux using FFMPEG failed at") + " " + _jobStatus.PercentageComplete.ToString(CultureInfo.InvariantCulture) + "%", Log.LogEntryType.Error);
                        Util.FileIO.TryFileDelete(RemuxedFile);
                        return false;
                    }
                }
                else
                {
                    Util.FileIO.TryFileDelete(RemuxedFile);
                    return false; // Avoid infinite loop, we are done here, nothing can be done, failed...
                }
            }
            else if (fixRemuxedFile) // If we are in the loop fixing the remuxed file, we did good, return now - don't process further yet
                return true;

            // Remux succeeded, check for Dropped or Duplicate packets due to incorrect FPS
            _jobLog.WriteEntry("Average rate of dropped frames :" + " " + ffmpeg.AverageDropROC.ToString("#0.00", CultureInfo.InvariantCulture), Log.LogEntryType.Debug);
            _jobLog.WriteEntry("Average rate of duplicate frames :" + " " + ffmpeg.AverageDupROC.ToString("#0.00", CultureInfo.InvariantCulture), Log.LogEntryType.Debug);

            // Read ReMux Parameters from Config Profile
            Ini configProfileIni = new Ini(GlobalDefs.ConfigFile);
            string profile = "FFMpegBackupRemux"; // This is where the Fallback Remux parameters are stored
            
            // Read the Drop frame threshhold
            double dropThreshold;
            double.TryParse(configProfileIni.ReadString(profile, "RemuxDropThreshold", "3.0"), System.Globalization.NumberStyles.Float, CultureInfo.InvariantCulture, out dropThreshold);

            // Read the Duplicate frame threshhold
            double dupThreshold;
            double.TryParse(configProfileIni.ReadString(profile, "RemuxDuplicateThreshold", "3.0"), System.Globalization.NumberStyles.Float, CultureInfo.InvariantCulture, out dupThreshold);

            if ((ffmpeg.AverageDropROC > dropThreshold) || (ffmpeg.AverageDupROC > dupThreshold))
            {
                if (autoFPS) // Check if we used AutoFPS and also if this isn't a going into an infinite loop
                    _jobLog.WriteEntry(Localise.GetPhrase("Remuxed file has too many dropped or duplicate frames, try to manually set the frame rate. Auto FPS used ->") + " " + FPS.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Warning);
                else
                    _jobLog.WriteEntry(Localise.GetPhrase("Remuxed file has too many dropped or duplicate frames, check/set the manual remux frame rate"), Log.LogEntryType.Warning);
            }

            // Since we are successful, keep track of how many seconds we skipped while remuxing the file
            FFMpegMEncoderParams checkParams = new FFMpegMEncoderParams(baseRemuxParams);
            float.TryParse(checkParams.ParameterValue("-ss"), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out skipInitialSeconds);

            return true; // All done here
        }
Example #2
0
        /// <summary>
        /// Remux the DVRMS file directly to TSMPEG using a special FFMPEG
        /// </summary>
        /// <returns>Success or Failure</returns>
        private bool RemuxDVRMSFFmpeg()
        {
            string profile = "DVRMSRemux"; // Reading the remux parameters from the file
            string ffmpegParams = "";
            Ini configProfileIni = new Ini(GlobalDefs.ConfigFile);

            // Threads 0 causes an error in some streams, avoid
            _jobLog.WriteEntry(this, Localise.GetPhrase("DVRMS file, using special FFMPEG to remux"), Log.LogEntryType.Information);

            // First try with regular FFMPEG and GenPTs, since the DVRMS tends to created corrupted files
            _jobStatus.CurrentAction = Localise.GetPhrase("Fast Remuxing");

            _jobLog.WriteEntry(this, Localise.GetPhrase("Reading DVRMS Remux parameters"), Log.LogEntryType.Information);

            // First try to copy all the streams directly (read parmeters for copy profile)
            string baseRemuxParams = configProfileIni.ReadString(profile, "Remux", "");

            if (String.IsNullOrWhiteSpace(baseRemuxParams)) // Have we used up all the CopyRemux profiles, then we're done here - try something else
            {
                _jobLog.WriteEntry(Localise.GetPhrase("DVRMS Remuxing disabled in config file, no parameters"), Log.LogEntryType.Error);
                Util.FileIO.TryFileDelete(RemuxedFile);
                return false;
            }

            // Check for input file placeholders and substitute
            if (baseRemuxParams.Contains("-i <source>")) // Check if we have a input file placeholder (useful if we need to put commands before -i)
            {
                baseRemuxParams = baseRemuxParams.Replace("-i <source>", "-i " + Util.FilePaths.FixSpaces(_RecordingFile) + " ");
                ffmpegParams = "-fflags +genpts -y " + baseRemuxParams + " " + Util.FilePaths.FixSpaces(RemuxedFile);
            }
            else
                ffmpegParams = "-fflags +genpts -y -i " + Util.FilePaths.FixSpaces(_RecordingFile) + " " + baseRemuxParams + " " + Util.FilePaths.FixSpaces(RemuxedFile); // DO NOT USE -async 1 with COPY

            FFmpeg ffmpeg;
            FFmpeg.FFMpegExecuteAndHandleErrors(ffmpegParams, _jobStatus, _jobLog, Util.FilePaths.FixSpaces(RemuxedFile), false, out ffmpeg); // Run and handle errors, don't need to check output file here
            if (!ffmpeg.Success || !RemuxedFileOK()) //check of file is created, outputhandler reports success (Percentage not requires since Success is more accurate)
            {
                // Use Special build of DVRMS FFMPEG to handle now
                _jobLog.WriteEntry(Localise.GetPhrase("DVRMS ReMux using FFMPEG GenPTS failed at") + " " + _jobStatus.PercentageComplete.ToString(CultureInfo.InvariantCulture) + "%. Retrying using special DVRMS FFMpeg", Log.LogEntryType.Warning);

                // DVR-MS supports only one audio stream
                ffmpegParams = ffmpegParams.Replace("-fflags +genpts", ""); // don't need genpts for special build dvrms ffmpeg

                ffmpeg = new FFmpeg(ffmpegParams, true, _jobStatus, _jobLog); // use SPECIAL BUILD DVRMS-FFMPEG, so we can't use FFMpegExecuteAndHandleErrors
                ffmpeg.Run();

                if (!ffmpeg.Success || !RemuxedFileOK()) //check of file is created, outputhandler reports success (Percentage not requires since Success is more accurate)
                {
                    _jobLog.WriteEntry(Localise.GetPhrase("DVRMS ReMux using FFMPEG failed at") + " " + _jobStatus.PercentageComplete.ToString(CultureInfo.InvariantCulture) + "%", Log.LogEntryType.Error);
                    Util.FileIO.TryFileDelete(RemuxedFile);
                    return false;
                }
            }

            // Since we are successful, keep track of how many seconds we skipped while remuxing the file
            FFMpegMEncoderParams checkParams = new FFMpegMEncoderParams(baseRemuxParams);
            float.TryParse(checkParams.ParameterValue("-ss"), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out skipInitialSeconds);

            return true;
        }