/**
  * Check for invalidly generated content files -
  * - Silent or black content for at least 50% of the total duration
  * - The detection duration - at least 2 sec
  * - Applicable only to Webex sources
  * @param KalturaBatchJob $job
  * @param KalturaPostConvertJobData $data
  * $param $mediaFile
  * #param KalturaMediaInfo $mediaInfo
  * @return boolean
  */
 private function checkForValidityOfWebexProduct(KalturaPostConvertJobData $data, $srcFileName, KalturaMediaInfo $mediaInfo, &$detectMsg)
 {
     $rv = true;
     $detectMsg = null;
     /*
      * Get silent and black portions
      *
     list($silenceDetect, $blackDetect) = KFFMpegMediaParser::checkForSilentAudioAndBlackVideo(KBatchBase::$taskConfig->params->FFMpegCmd, $srcFileName, $mediaInfo);
     
     $detectMsg = $silenceDetect;
     if(isset($blackDetect))
     	$detectMsg = isset($detectMsg)?"$detectMsg,$blackDetect":$blackDetect;
     */
     /*
      * Silent/Black does not cause validation failure, just a job message 
      */
     if (isset($detectMsg)) {
         //			return false;
     }
     /*
      * Get number of Webex operators that represent the number of conversion retries.
      * Return success after the last retry, independently of whether the result is garbled or not.
      * The assumption is that 3 retries will bring the number of garbled audios to acceptable rate.
      * Therefore if the audio is still garbled, it is probably due to false detection,
      * therefore DO NOT fail the asset.
      */
     $operators = json_decode($data->flavorParamsOutput->operators);
     if ($data->currentOperationSet < count($operators) - 1) {
         if (KFFMpegMediaParser::checkForGarbledAudio(KBatchBase::$taskConfig->params->FFMpegCmd, $srcFileName, $mediaInfo) == true) {
             $detectMsg .= " Garbled Audio!";
             $rv = false;
         }
     }
     return $rv;
 }
 protected function convertRecordedToMPEGTS($ffmpegBin, $ffprobeBin, $inFilename, $outFilename)
 {
     $cmdStr = "{$ffmpegBin} -i {$inFilename} -c copy -bsf:v h264_mp4toannexb -f mpegts -y {$outFilename} 2>&1";
     KalturaLog::debug("Executing [{$cmdStr}]");
     $output = system($cmdStr, $rv);
     /*
      * Anomaly detection -
      *	Look for the time of the first KF in the source file.
      *	Should be less than 200 msec
      *	Currnetly - just logging
      */
     $detectInterval = 10;
     // sec
     $maxKeyFrameTime = 0.2;
     // sec
     $kfArr = KFFMpegMediaParser::retrieveKeyFrames($ffprobeBin, $inFilename, 0, $detectInterval);
     KalturaLog::log("KeyFrames:" . print_r($kfArr, 1));
     if (count($kfArr) == 0) {
         KalturaLog::log("Anomaly detection: NO Keyframes in the detection interval ({$detectInterval} sec)");
     } else {
         if ($kfArr[0] > $maxKeyFrameTime) {
             KalturaLog::log("Anomaly detection: ERROR, first KF at ({$kfArr['0']} sec), max allowed ({$maxKeyFrameTime} sec)");
         } else {
             KalturaLog::log("Anomaly detection: OK, first KF at ({$kfArr['0']} sec), max allowed ({$maxKeyFrameTime} sec)");
         }
     }
     return $rv == 0 ? true : false;
 }
Example #3
0
 /**
  * @return KalturaMediaInfo
  */
 public function getMediaInfo()
 {
     /*
      * KFFMpegMediaParser is activated here as a fall back to mediainfo for M1S
      * and for test reasons prior to switching from mediainfo to ffprobe
      */
     $ffParser = new KFFMpegMediaParser($this->filePath);
     //, "ffmpeg-20140326", "ffprobe-20140326");
     $ffMi = null;
     try {
         $ffMi = $ffParser->getMediaInfo();
     } catch (Exception $ex) {
         KalturaLog::log(print_r($ex, 1));
     }
     $output = $this->getRawMediaInfo();
     $kMi = $this->parseOutput($output);
     if (!isset($kMi)) {
         $compareStr = self::compareFields($kMi, $ffMi);
         KalturaLog::log("compareFields(" . (isset($compareStr) ? $compareStr : "IDENTICAL") . "), file({$this->filePath})");
         return $ffMi;
     }
     /*
      * Interlaced mjpa sources - the height value is halved.
      */
     if (isset($kMi->videoHeightTmp) && isset($kMi->scanType) && $kMi->scanType == 1) {
         $kMi->videoHeight = $kMi->videoHeightTmp;
     }
     /*
      * WebM/VP8 misses video duration
      */
     if (isset($kMi->videoFormat) && $kMi->videoFormat == "vp8" && (!isset($kMi->videoDuration) || $kMi->videoDuration == 0)) {
         $kMi->videoDuration = $kMi->containerDuration;
     }
     $durLimit = 3600000;
     if (get_class($this) == 'KMediaInfoMediaParser' && (isset($kMi->containerDuration) && $kMi->containerDuration >= $durLimit || isset($kMi->videoDuration) && $kMi->videoDuration >= $durLimit || isset($kMi->audioDuration) && $kMi->audioDuration >= $durLimit)) {
         $cmd = "{$this->cmdPath} \"--Inform=General;done %Duration%\" \"{$this->filePath}\"";
         $output = 0;
         $output = shell_exec($cmd);
         $aux = explode(" ", trim($output));
         if (isset($aux) && count($aux) == 2 && $aux[0] == 'done') {
             $kMi->containerDuration = (int) $aux[1];
         }
         $cmd = "{$this->cmdPath} \"--Inform=Video;done %Duration%\" \"{$this->filePath}\"";
         $output = 0;
         $output = shell_exec($cmd);
         $aux = explode(" ", trim($output));
         if (isset($aux) && count($aux) == 2 && $aux[0] == 'done') {
             $kMi->videoDuration = (int) $aux[1];
         }
         $cmd = "{$this->cmdPath} \"--Inform=Audio;done %Duration%\" \"{$this->filePath}\"";
         $output = 0;
         $output = shell_exec($cmd);
         $aux = explode(" ", trim($output));
         if (isset($aux) && count($aux) == 2 && $aux[0] == 'done') {
             $kMi->audioDuration = (int) $aux[1];
         }
     }
     if (isset($ffMi)) {
         /*
          * Media info's vid/aud streams are unset - use object that was generated by ffprobe,
          * unless it is an ARF source.
          */
         if (!self::isAudioSet($kMi) && !self::isVideoSet($kMi) && $kMi->containerFormat != "arf") {
             $compareStr = self::compareFields($kMi, $ffMi);
             KalturaLog::log("compareFields(" . (isset($compareStr) ? $compareStr : "IDENTICAL") . "), file({$this->filePath})");
             return $ffMi;
         }
         /*
          * On off-sanity wid/height - use ffprobe object vals (overwrite the dar too)
          */
         if (isset($kMi->videoWidth) && isset($kMi->videoHeight) && ($kMi->videoWidth > KDLSanityLimits::MaxDimension || $kMi->videoWidth < KDLSanityLimits::MinDimension || $kMi->videoHeight > KDLSanityLimits::MaxDimension || $kMi->videoHeight < KDLSanityLimits::MinDimension)) {
             if (isset($ffMi->videoWidth) && isset($ffMi->videoHeight) && !($ffMi->videoWidth > KDLSanityLimits::MaxDimension || $ffMi->videoWidth < KDLSanityLimits::MinDimension || $ffMi->videoHeight > KDLSanityLimits::MaxDimension || $ffMi->videoHeight < KDLSanityLimits::MinDimension)) {
                 $kMi->videoWidth = $ffMi->videoWidth;
                 $kMi->videoHeight = $ffMi->videoHeight;
                 if (isset($ffMi->videoDar)) {
                     $kMi->videoDar = $ffMi->videoDar;
                 }
             }
         }
         /*
          * On off-sanity dar or if the is AR ambiguity, due to 'original dar'
          * - use ffprobe object dar
          */
         if (isset($kMi->videoDar) && ($kMi->videoDar > KDLSanityLimits::MaxDAR || $kMi->videoDar < KDLSanityLimits::MinDAR || isset($kMi->originalDar))) {
             if (isset($ffMi->videoDar) && !($ffMi->videoDar > KDLSanityLimits::MaxDAR || $ffMi->videoDar < KDLSanityLimits::MinDAR)) {
                 $kMi->videoDar = $ffMi->videoDar;
             }
         }
         /*
          * Update mediainfo generated object with fastStart and contentStreams fields 
          * that are available only on ffprobe
          */
         $kMi->isFastStart = $ffMi->isFastStart;
         $kMi->contentStreams = $ffMi->contentStreams;
     }
     $compareStr = self::compareFields($kMi, $ffMi);
     KalturaLog::log("compareFields(" . (isset($compareStr) ? $compareStr : "IDENTICAL") . "), file({$this->filePath})");
     return $kMi;
 }
Example #4
0
 /**
  * 
  * @param unknown_type $ffmpegBin
  * @param unknown_type $ffprobeBin
  * @param array $filesArr
  * @param unknown_type $outFilename
  * @param unknown_type $clipStart
  * @param unknown_type $clipDuration
  * @return boolean
  */
 protected static function concatFiles($ffmpegBin, $ffprobeBin, array $filesArr, $outFilename, $clipStart = null, $clipDuration = null)
 {
     $fixLargeDeltaFlag = null;
     $chunkBr = null;
     $concateStr = null;
     /*
      * Evaluate clipping arguments
      */
     $clipStr = null;
     if (isset($clipStart)) {
         $clipStr = "-ss {$clipStart}";
     }
     if (isset($clipDuration)) {
         $clipStr .= " -t {$clipDuration}";
     }
     sort($filesArr);
     $filesArrCnt = count($filesArr);
     $i = 0;
     $mi = null;
     foreach ($filesArr as $fileName) {
         $i++;
         /*
          * Get chunk file media-info
          */
         $ffParser = new KFFMpegMediaParser($fileName, $ffmpegBin, $ffprobeBin);
         $mi = null;
         try {
             $mi = $ffParser->getMediaInfo();
         } catch (Exception $ex) {
             KalturaLog::log(print_r($ex, 1));
         }
         /*
          * Calculate chunk-br for the cliping flow
          */
         if (isset($clipStr)) {
             if (isset($mi->containerBitRate) && $mi->containerBitRate > 0) {
                 $chunkBr += $mi->containerBitRate;
             } else {
                 if (isset($mi->videoBitRate) && $mi->videoBitRate > 0) {
                     $chunkBr += $mi->videoBitRate;
                 } else {
                     if (isset($mi->audioBitRate) && $mi->audioBitRate > 0) {
                         $chunkBr += $mi->audioBitRate;
                     }
                 }
             }
         }
         /*
          * On last chunk file - 
          * - no duration delta validity tests
          * - pack the final concat string and finish the loop
          */
         if ($i == $filesArrCnt) {
             $concateStr = "concat:\"{$concateStr}{$fileName}\"";
             break;
         } else {
             $concateStr .= "{$fileName}|";
         }
         /* 
          * ##############
          * ############## DISABLE the 'small-distortion-fix' code
          * ############## There were cases when the the 'fix' cuased another distortion
          * ############## Hopefully WWZ fixed their chunks generation procedure in 4.1.2
          * ############## If it does - all this code/remark should be removed,
          * ############## otherwise (in case that the fix will still be required) - it should be enhanced.
          * ############## 
          * Evaluate chunk duration for drift validation
          * - only one duration anomaly is required to set the drift fix flag, no need to check following chunk files
          *
         			if(!isset($fixLargeDeltaFlag)) {
         if(isset($mi->containerDuration) && $mi->containerDuration>0)
         	$duration = $mi->containerDuration;
         else if(isset($mi->videoDuration) && $mi->videoDuration>0)
         	$duration = $mi->videoDuration;
         else if(isset($mi->audioDuration) && $mi->audioDuration>0)
         	$duration = $mi->audioDuration;
         else
         	$duration = 0;
         
         if($duration>0){
         	 *
         	 * If the duration is too small - stop/start flow, don't fix 
         	 *
         	if(KAsyncConcat::LiveChunkDuration-$duration>30000){
         		$fixLargeDeltaFlag = false;
         	}
         	else if(abs($duration-KAsyncConcat::LiveChunkDuration)>KAsyncConcat::MaxChunkDelta){
         		$fixLargeDeltaFlag = true;
         	}
         }
         KalturaLog::log("Chunk duration($duration), Wowza chunk setting(".KAsyncConcat::LiveChunkDuration."),max-allowed-delta(".KAsyncConcat::MaxChunkDelta."),fixLargeDeltaFlag($fixLargeDeltaFlag) ");
         			}
         */
     }
     /*
      * For clip flow - set converion to x264,
      * otherwise - just copy video
      */
     if (isset($clipStr)) {
         $videoParamStr = "-c:v libx264";
         if (isset($chunkBr) && $chunkBr > 0 && $filesArrCnt > 0) {
             $chunkBr = round($chunkBr / $filesArrCnt);
             $videoParamStr .= " -b:v {$chunkBr}" . "k";
         }
         $videoParamStr .= " -subq 7 -qcomp 0.6 -qmin 10 -qmax 50 -qdiff 4 -bf 16 -coder 1 -refs 6 -x264opts b-pyramid:weightb:mixed-refs:8x8dct:no-fast-pskip=0 -vprofile high  -pix_fmt yuv420p -threads 4";
     } else {
         $videoParamStr = "-c:v copy";
     }
     /*
      * If no audio - skip.
      * For AAC source - copy audio,
      * otherwise - convert to AAC
      */
     $audioParamStr = null;
     if (isset($mi->audioFormat) || isset($mi->audioCodecId) || isset($mi->audioDuration)) {
         if (isset($mi->audioFormat) && $mi->audioFormat == "aac") {
             $audioParamStr = "-c:a copy";
         } else {
             $audioParamStr = "-c:a libfdk_aac";
         }
         $audioParamStr .= " -bsf:a aac_adtstoasc";
     }
     $cmdStr = "{$ffmpegBin} -probesize 15M -analyzeduration 25M -i {$concateStr} {$videoParamStr} {$audioParamStr}";
     $cmdStr .= " {$clipStr} -f mp4 -y {$outFilename} 2>&1";
     KalturaLog::debug("Executing [{$cmdStr}]");
     $output = system($cmdStr, $rv);
     return $rv == 0 ? true : false;
 }
 /**
  * 
  * @param unknown_type $cmdStr
  * @param unknown_type $flavorParamsOutput
  * @param unknown_type $binCmd
  * @param unknown_type $srcFilePath
  * @param unknown_type $outFilePath
  * @return unknown|string
  */
 public static function experimentalFixing($cmdStr, $flavorParamsOutput, $binCmd, $srcFilePath, $outFilePath)
 {
     /*
      * Samples - 
      * Original 
      * ffmpeg -i SOURCE 
      * 	   -c:v libx265 
      * 		-pix_fmt yuv420p -aspect 640:360 -b:v 8000k -s 640x360 -r 30 -g 60 
      * 		-c:a libfdk_aac -b:a 128k -ar 44100 -f mp4 -y OUTPUT
      * 
      * Switched - 
      * ffmpeg -i SOURCE 
      * 		-pix_fmt yuv420p -aspect 640:360 -b:v 8000k -s 640x360 -r 30 -g 60 -f yuv4mpegpipe -an - 
      * 		-vn 
      * 		-c:a libfdk_aac -b:a 128k -ar 44100 -f mp4 -y OUTPUT.aac 
      * 		| /home/dev/x265 - --y4m --scenecut 40 --keyint 60 --min-keyint 1 --bitrate 2000 --qpfile OUTPUT.qp OUTPUT.h265 
      * 		&& ~/ffmpeg-2.4.3 -i OUTPUT.aac -r 30 -i OUTPUT.h265 -c copy -f mp4 -y OUTPUT
      * 
      */
     /*
      * New binaries/aliases on transcoding servers
      */
     $x265bin = "x265";
     $ffmpegExperimBin = "ffmpeg-experim";
     if ($flavorParamsOutput->videoCodec == KDLVideoTarget::H265) {
         //video_codec	!!!flavorParamsOutput->videoCodec
         KalturaLog::log("trying to fix H265 conversion");
         $gop = $flavorParamsOutput->gopSize;
         //gop_size	!!!$flavorParamsOutput->gopSize;
         $vBr = $flavorParamsOutput->videoBitrate;
         //video_bitrate	!!!$flavorParamsOutput->videoBitrate;
         $frameRate = $flavorParamsOutput->frameRate;
         //frame_rate	!!!$flavorParamsOutput->frameRate;
         $threads = 4;
         $pixFmt = "yuv420p";
         $cmdValsArr = explode(' ', $cmdStr);
         /*
          * Rearrange the ffmpeg cmd-line into a complex pipe and multiple command
          * - ffmpeg transcodes audio into an output.AAC file and decodes video into a raw resized video to be piped
          * - into x265 that encodes raw output.h265
          * - upon completion- mux into an out.mp4
          * 
          * To Do's
          * - support other audio
          * - support other formats
          * 
          */
         /*
          * remove video codec
          */
         if (in_array('-c:v', $cmdValsArr)) {
             $key = array_search('-c:v', $cmdValsArr);
             unset($cmdValsArr[$key + 1]);
             unset($cmdValsArr[$key]);
         }
         if (in_array('-threads', $cmdValsArr)) {
             $key = array_search('-threads', $cmdValsArr);
             $threads = $cmdValsArr[$key + 1];
         }
         /*
          * add dual stream generation
          */
         if (in_array('-c:a', $cmdValsArr)) {
             $key = array_search('-c:a', $cmdValsArr);
             $cmdValsArr[$key] = "-f yuv4mpegpipe -an - -vn -c:a";
         }
         /*
          * handle pix-format (main vs main10)
          */
         if (in_array('-pix_fmt', $cmdValsArr)) {
             $key = array_search('-pix_fmt', $cmdValsArr);
             $pixFmt = $cmdValsArr[$key + 1];
         }
         switch ($pixFmt) {
             case "yuv420p10":
             case "yuv422p":
                 $profile = "main10";
                 break;
             case "yuv420p":
             default:
                 $profile = "main";
                 break;
         }
         /*
          * Get source duration
          */
         $ffParser = new KFFMpegMediaParser($srcFilePath);
         $ffMi = null;
         try {
             $ffMi = $ffParser->getMediaInfo();
         } catch (Exception $ex) {
             KalturaLog::log(print_r($ex, 1));
         }
         if (isset($ffMi->containerDuration) && $ffMi->containerDuration > 0) {
             $duration = $ffMi->containerDuration / 1000;
         } else {
             if (isset($ffMi->videoDuration) && $ffMi->videoDuration > 0) {
                 $duration = $ffMi->videoDuration / 1000;
             } else {
                 if (isset($ffMi->audioDuration) && $ffMi->audioDuration > 0) {
                     $duration = $ffMi->audioDuration / 1000;
                 } else {
                     $duration = 0;
                 }
             }
         }
         $keyFramesArr = array();
         /*
          * Generate x265 qpfile with forced key-frames
          */
         if (isset($gop) && $gop > 0 && isset($frameRate) && $frameRate > 0 && isset($duration) && $duration > 0) {
             $gopInSec = $gop / round($frameRate);
             $frameDur = 1 / $frameRate;
             for ($kfTime = 0, $kfId = 0, $kfTimeGop = 0; $kfTime < $duration;) {
                 $keyFramesArr[] = $kfId;
                 $kfId += $gop;
                 $kfTime = $kfId * $frameDur;
                 $kfTimeGop += $gopInSec;
                 $kfTimeDelta = $kfTime - $kfTimeGop;
                 /*
                  * Check for time derift conditions (for float fps, 29.97/23.947/etc) and fix when required
                  */
                 if (abs($kfTimeDelta) > $frameDur) {
                     $aaa = $kfId;
                     if ($kfTimeDelta > 0) {
                         $kfId--;
                     } else {
                         $kfId++;
                     }
                 }
             }
             $keyFramesStr = implode(" I\n", $keyFramesArr) . " I\n";
             file_put_contents("{$outFilePath}.qp", $keyFramesStr);
         } else {
             KalturaLog::log("Missing gop({$gop}) or frameRate({$frameRate}) or duration({$duration}) - will be generated without fixed keyframes!");
         }
         if (!in_array($outFilePath, $cmdValsArr)) {
             return $cmdStr;
         }
         $key = array_search($outFilePath, $cmdValsArr);
         $cmdValsArr[$key] = "{$outFilePath}.aac |";
         $cmdValsArr[$key] .= " {$x265bin} - --profile {$profile} --y4m --scenecut 40 --min-keyint 1";
         if (isset($gop)) {
             $cmdValsArr[$key] .= " --keyint {$gop}";
         }
         if (isset($vBr)) {
             $cmdValsArr[$key] .= " --bitrate {$vBr}";
         }
         if (count($keyFramesArr) > 0) {
             $cmdValsArr[$key] .= " --qpfile {$outFilePath}.qp";
         }
         $cmdValsArr[$key] .= " --threads {$threads} {$outFilePath}.h265";
         $cmdValsArr[$key] .= " && {$ffmpegExperimBin} -i {$outFilePath}.aac -r {$frameRate} -i {$outFilePath}.h265 -c copy -f mp4 -y {$outFilePath}";
         $cmdStr = implode(" ", $cmdValsArr);
     } else {
         if ($flavorParamsOutput->videoCodec == KDLVideoTarget::VP9) {
             //video_codec ||!flavorParamsOutput->videoCodec
             $cmdValsArr = explode(' ', $cmdStr);
             $cmdValsArr[0] = $ffmpegExperimBin;
             $cmdStr = implode(" ", $cmdValsArr);
         }
     }
     return $cmdStr;
 }
Example #6
0
 /**
  * 
  * @param unknown_type $ffmpegBin
  * @param unknown_type $srcFileName
  * @param KalturaMediaInfo $mediaInfo
  * @return boolean
  */
 public static function checkForGarbledAudio($ffmpegBin, $srcFileName, KalturaMediaInfo $mediaInfo)
 {
     KalturaLog::log("contDur:{$mediaInfo->containerDuration},audDur:{$mediaInfo->audioDuration}");
     if (isset($mediaInfo->audioDuration)) {
         $audDetectDur = $mediaInfo->audioDuration > 600000 ? 600 : round($mediaInfo->audioDuration / 1000, 2);
     } else {
         if (isset($mediaInfo->containerDuration)) {
             $audDetectDur = $mediaInfo->containerDuration > 600000 ? 600 : round($mediaInfo->containerDuration / 1000, 2);
         } else {
             if (isset($mediaInfo->videoDuration)) {
                 $audDetectDur = $mediaInfo->videoDuration > 600000 ? 600 : round($mediaInfo->videoDuration / 1000, 2);
             } else {
                 $audDetectDur = 0;
             }
         }
     }
     if ($audDetectDur > 0 && $audDetectDur < 10) {
         KalturaLog::log("Audio OK - short audio, audDetectDur({$audDetectDur})");
         return false;
     }
     list($silenceDetected, $blackDetected) = KFFMpegMediaParser::detectSilentAudioAndBlackVideoIntervals($ffmpegBin, $srcFileName, null, 0.05, $audDetectDur, "-90dB");
     $ticks = isset($silenceDetected) ? count($silenceDetected) : 0;
     if ($ticks <= 10) {
         KalturaLog::log("Audio OK - low numbers of ticks({$ticks})");
         return false;
     }
     KalturaLog::log("audDetectDur({$audDetectDur}),ticks({$ticks})");
     if ($audDetectDur > 0) {
         $ticksPerMin = $ticks / ($audDetectDur / 60);
         KalturaLog::log("ticksPerMin({$ticksPerMin})");
         if ($ticksPerMin < 15 || $audDetectDur < 60 && $ticksPerMin < 30 || $audDetectDur < 120 && $ticksPerMin < 20) {
             KalturaLog::log("Audio OK");
             return false;
         }
     } else {
         if ($ticks < 100) {
             KalturaLog::log("Audio OK - no duration, number of ticks smaller than threshold(100)");
             return false;
         }
     }
     KalturaLog::log("Detected garbled audio.");
     return true;
 }
 /**
  * extractFfmpegInfo extract the file info using FFmpeg and parse the returned data
  *  
  * @param string $mediaFile file full path
  * @return KalturaMediaInfo or null for failure
  */
 private function extractFfmpegInfo($mediaFile)
 {
     KalturaLog::debug("extractFfmpegInfo({$mediaFile})");
     $mediaParser = new KFFMpegMediaParser($mediaFile, $this->taskConfig->params->FFMpegCmd);
     return $mediaParser->getMediaInfo();
 }