private static async Task <FFProbeResult> ProbeVideo(string inputPath) { var p = await Runner.ExecWithStdoutRedirect("ffprobe", new[] { "-show_format", "-show_streams", "-print_format", "json", "-loglevel", "quiet", inputPath }); if (p.ExitCode != 0) { throw new Exception("could not probe video format"); } var probeResult = p.StandardOutput.ReadToEnd(); var result = FFProbeResult.FromJson(probeResult); return(result); }
/// <summary> /// Normalizes the FF probe result. /// </summary> /// <param name="result">The result.</param> private void NormalizeFFProbeResult(FFProbeResult result) { if (result.format != null && result.format.tags != null) { result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags); } if (result.streams != null) { // Convert all dictionaries to case insensitive foreach (var stream in result.streams) { if (stream.tags != null) { stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags); } if (stream.disposition != null) { stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition); } } } }
/// <summary> /// Fetches the specified video. /// </summary> /// <param name="video">The video.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="data">The data.</param> /// <param name="isoMount">The iso mount.</param> /// <returns>Task.</returns> protected override Task Fetch(Video video, CancellationToken cancellationToken, FFProbeResult data, IIsoMount isoMount) { return(Task.Run(() => { if (data.format != null) { // For dvd's this may not always be accurate, so don't set the runtime if the item already has one var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0; if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration)) { video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration)).Ticks; } } if (data.streams != null) { video.MediaStreams = data.streams.Select(s => GetMediaStream(s, data.format)).ToList(); } if (data.Chapters != null) { video.Chapters = data.Chapters; } if (video.Chapters == null || video.Chapters.Count == 0) { AddDummyChapters(video); } if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay)) { var inputPath = isoMount != null ? isoMount.MountedPath : video.Path; FetchBdInfo(video, inputPath, BdInfoCache, cancellationToken); } AddExternalSubtitles(video); })); }
/// <summary> /// Fetches the specified audio. /// </summary> /// <param name="audio">The audio.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="data">The data.</param> /// <param name="isoMount">The iso mount.</param> /// <returns>Task.</returns> protected override Task Fetch(Audio audio, CancellationToken cancellationToken, FFProbeResult data, IIsoMount isoMount) { return(Task.Run(() => { if (data.streams == null) { Logger.Error("Audio item has no streams: " + audio.Path); return; } audio.MediaStreams = data.streams.Select(s => GetMediaStream(s, data.format)).ToList(); // Get the first audio stream var stream = data.streams.First(s => s.codec_type.Equals("audio", StringComparison.OrdinalIgnoreCase)); // Get duration from stream properties var duration = stream.duration; // If it's not there go into format properties if (string.IsNullOrEmpty(duration)) { duration = data.format.duration; } // If we got something, parse it if (!string.IsNullOrEmpty(duration)) { audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration)).Ticks; } if (data.format.tags != null) { FetchDataFromTags(audio, data.format.tags); } })); }
public override async Task <ResultType> Run(CancellationToken cancellationToken) { JobStatus = VideoJobStatus.Running; Status = "Checking files..."; if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } string file = VideoInfo.VideoId; string path = Path.GetDirectoryName(file); string name = Path.GetFileNameWithoutExtension(file); List <string> ffmpegOptions; string postfixOld; string postfixNew; string outputformat; if (VideoInfo is FFMpegReencodeJobVideoInfo) { FFMpegReencodeJobVideoInfo ffvi = VideoInfo as FFMpegReencodeJobVideoInfo; ffmpegOptions = ffvi.FFMpegOptions; postfixOld = ffvi.PostfixOld; postfixNew = ffvi.PostfixNew; outputformat = ffvi.OutputFormat; } else { ffmpegOptions = new List <string>() { "-c:v", "libx264", "-preset", "slower", "-crf", "23", "-g", "2000", "-c:a", "copy", "-max_muxing_queue_size", "100000", }; postfixOld = "_chunked"; postfixNew = "_x264crf23"; outputformat = "mp4"; } string ext = "." + outputformat; string chunked = postfixOld; string postfix = postfixNew; string newfile = Path.Combine(path, postfix, name.Substring(0, name.Length - chunked.Length) + postfix + ext); string newfileinlocal = Path.Combine(path, name.Substring(0, name.Length - chunked.Length) + postfix + ext); string tempfile = Path.Combine(path, name.Substring(0, name.Length - chunked.Length) + postfix + "_TEMP" + ext); string chunkeddir = Path.Combine(path, chunked); string postfixdir = Path.Combine(path, postfix); string oldfileinchunked = Path.Combine(chunkeddir, Path.GetFileName(file)); FFProbeResult probe = null; string encodeinput = null; if (await Util.FileExists(file)) { probe = await FFMpegUtil.Probe(file); encodeinput = file; } else if (await Util.FileExists(oldfileinchunked)) { probe = await FFMpegUtil.Probe(oldfileinchunked); encodeinput = oldfileinchunked; } if (probe != null) { VideoInfo = new FFMpegReencodeJobVideoInfo(file, probe, ffmpegOptions, postfixOld, postfixNew, outputformat); } // if the input file doesn't exist we might still be in a state where we can set this to finished if the output file already exists, so continue anyway bool newfileexists = await Util.FileExists(newfile); bool newfilelocalexists = await Util.FileExists(newfileinlocal); if (!newfileexists && !newfilelocalexists) { if (encodeinput == null) { // neither input nor output exist, bail Status = "Missing!"; return(ResultType.Failure); } if (await Util.FileExists(tempfile)) { await Util.DeleteFile(tempfile); } if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } Status = "Encoding " + newfile + "..."; await StallWrite(newfile, new FileInfo( encodeinput ).Length, cancellationToken); if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } Directory.CreateDirectory(postfixdir); FFMpegReencodeJobVideoInfo ffmpegVideoInfo = VideoInfo as FFMpegReencodeJobVideoInfo; await Reencode(newfile, encodeinput, tempfile, ffmpegVideoInfo.FFMpegOptions); } if (!newfileexists && newfilelocalexists) { Directory.CreateDirectory(postfixdir); File.Move(newfileinlocal, newfile); } if (await Util.FileExists(file) && !await Util.FileExists(oldfileinchunked)) { Directory.CreateDirectory(chunkeddir); File.Move(file, oldfileinchunked); } Status = "Done!"; JobStatus = VideoJobStatus.Finished; return(ResultType.Success); }
private static MediaStream FindVideoStrean(FFProbeResult probeResult) { var ms = probeResult.Streams.Where(s => s.CodecType == "video").First(); return(ms); }
/// <summary> /// Subclasses must set item values using this /// </summary> /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="result">The result.</param> /// <param name="isoMount">The iso mount.</param> /// <returns>Task.</returns> protected abstract Task Fetch(T item, CancellationToken cancellationToken, FFProbeResult result, IIsoMount isoMount);